diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..14e843e --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,36 @@ +image: Visual Studio 2017 +configuration: Release +environment: + GLEW_BASE: glew-2.1.0 + GLEW_URL: https://github.com/nigels-com/glew/releases/download/glew-2.1.0/%GLEW_BASE%-win32.zip + GLFW_BASE: glfw-3.2.1.bin.WIN64 + GLFW_URL: https://github.com/glfw/glfw/releases/download/3.2.1/%GLFW_BASE%.zip + SDL2_BASE: SDL2-devel-2.0.8-VC + SDL2_URL: https://www.libsdl.org/release/%SDL2_BASE%.zip + SDL2_DIRAME: SDL2-2.0.8 + PREMAKE5_URL: https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-windows.zip + matrix: + - PLATFORM: win-amd64-null + - PLATFORM: win-amd64-gl3 + PREMAKE5_EXTRA_ARGS: --gfxlib=glfw + - PLATFORM: win-amd64-gl3 + PREMAKE5_EXTRA_ARGS: --gfxlib=sdl2 + - PLATFORM: win-amd64-d3d9 + +install: + - appveyor DownloadFile %GLFW_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE%.zip" + - appveyor DownloadFile %GLEW_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%GLEW_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%GLEW_BASE%.zip" + - appveyor DownloadFile %SDL2_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/%SDL2_BASE%.zip" + - 7z x "%APPVEYOR_BUILD_FOLDER%/%SDL2_BASE%.zip" + - appveyor DownloadFile %PREMAKE5_URL% -FileName "%APPVEYOR_BUILD_FOLDER%/premake5.zip" + - mkdir "%APPVEYOR_BUILD_FOLDER%/bin" && cd "%APPVEYOR_BUILD_FOLDER%/bin" && 7z x "%APPVEYOR_BUILD_FOLDER%/premake5.zip" + - set PATH=%APPVEYOR_BUILD_FOLDER%/bin;%PATH% +before_build: + - mkdir "%APPVEYOR_BUILD_FOLDER%/build" + - cd "%APPVEYOR_BUILD_FOLDER%" + - premake5 vs2017 --glewdir=%APPVEYOR_BUILD_FOLDER%/%GLEW_BASE% --glfwdir64=%APPVEYOR_BUILD_FOLDER%/%GLFW_BASE% --sdl2dir=%APPVEYOR_BUILD_FOLDER%/%SDL2_DIRAME% %PREMAKE5_EXTRA_ARGS% +build: + project: c:\projects\librw\build\librw.sln + verbosity: minimal diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dbc333 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/bin +/build +/obj +/lib +/output +/.vs +librw.vcxproj.user +librw.VC.db +librw.VC.VC.opendb \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..91411e6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,46 @@ +language: cpp + +matrix: + include: + - os: linux + env: TARGET=release_linux-amd64-null + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 gmake && cd /librw/build && make config=$TARGET verbose=1" + - os: linux + env: TARGET=release_linux-amd64-gl3 GFXLIB=glfw + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 --gfxlib=$GFXLIB gmake && cd /librw/build && make config=$TARGET verbose=1" + - os: linux + env: TARGET=release_linux-amd64-gl3 GFXLIB=sdl2 + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 --gfxlib=$GFXLIB gmake && cd /librw/build && make config=$TARGET verbose=1" + - name: "ps2" + os: linux + env: TARGET=release_ps2 + services: docker + script: + - mkdir -p "$TRAVIS_BUILD_DIR/build" + #- docker build -t librw "$TRAVIS_BUILD_DIR" + #- docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" --name librw_instance -d librw sleep infinity + - docker pull librw/librw + - docker run -v "$TRAVIS_BUILD_DIR:/librw:rw,z" -v "$TRAVIS_BUILD_DIR/build:/build:rw,z" --name librw_instance -d librw/librw sleep infinity + - docker exec -u builder librw_instance /bin/bash -c "cd /librw && premake5 gmake && cd /librw/build && make config=$TARGET verbose=1" + allow_failures: + - name: "ps2" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..695e259 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +FROM ubuntu:rolling + +ENV PS2DEV /ps2dev +ENV PS2SDK $PS2DEV/ps2sdk +ENV PATH $PATH:$PS2DEV/bin:$PS2DEV/ee/bin:$PS2DEV/iop/bin:$PS2DEV/dvp/bin:$PS2SDK/bin + +ENV DEBIAN_FRONTEND noninteractive + +ENV TOOLCHAIN_GIT_URL git://github.com/ps2dev/ps2toolchain.git +ENV TOOLCHAIN_GIT_BRANCH master + +ENV PREMAKE5_URL=https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-linux.tar.gz + +RUN mkdir -p "$PS2DEV" "$PS2SDK" \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y \ + build-essential \ + cmake \ + autoconf \ + bzip2 \ + gcc \ + git \ + libucl-dev \ + make \ + patch \ + vim \ + wget \ + zip \ + zlib1g-dev \ + libglew-dev \ + libglfw3-dev \ + libsdl2-dev \ + && git clone -b $TOOLCHAIN_GIT_BRANCH $TOOLCHAIN_GIT_URL /toolchain \ + && cd /toolchain \ + && ./toolchain.sh \ + && git clone git://github.com/ps2dev/ps2eth.git /ps2dev/ps2eth \ + && make -C /ps2dev/ps2eth \ + && git clone git://github.com/ps2dev/ps2-packer.git /ps2-packer \ + && make install -C /ps2-packer \ + && rm -rf \ + /ps2-packer \ + /ps2dev/ps2eth/.git \ + /ps2dev/ps2sdk/test.tmp \ + /ps2dev/test.tmp \ + /toolchain \ + && rm -rf /var/lib/apt/lists/* \ + && wget "$PREMAKE5_URL" -O /tmp/premake5.tar.gz \ + && tar xf /tmp/premake5.tar.gz -C /usr/bin/ \ + && rm /tmp/premake5.tar.gz \ + && groupadd 1000 -g 1000 \ + && groupadd 1001 -g 1001 \ + && groupadd 2000 -g 2000 \ + && groupadd 999 -g 999 \ + && useradd -ms /bin/bash builder -g 1001 -G 1000,2000,999 \ + && printf "builder:builder" | chpasswd \ + && adduser builder sudo \ + && printf "builder ALL= NOPASSWD: ALL\\n" >> /etc/sudoers + +USER builder +WORKDIR /home/builder diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ef53cc2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 aap + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aa5cb60 --- /dev/null +++ b/Makefile @@ -0,0 +1,152 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +#TARGET := $(notdir $(CURDIR)) +TARGET := rw +SOURCES := src src/d3d src/gl src/lodepng src/ps2 +DATA := data +INCLUDES := +#PORTLIBS := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec + +CFLAGS := -g \ + -ffunction-sections \ + -fdata-sections \ + $(ARCH) \ + $(BUILD_CFLAGS) + +CFLAGS += $(INCLUDE) -DSWITCH -D__SWITCH__ -DDEBUG -DRW_GL3 -DLIBRW_GLAD + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) + +LIBS := + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) #$(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: clean all + +#--------------------------------------------------------------------------------- +all: lib/lib$(TARGET).a lib/lib$(TARGET)d.a + +lib: + @[ -d $@ ] || mkdir -p $@ + +release: + @[ -d $@ ] || mkdir -p $@ + +debug: + @[ -d $@ ] || mkdir -p $@ + +lib/lib$(TARGET).a : lib release $(SOURCES) $(INCLUDES) + @$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DNDEBUG=1 -O3" \ + DEPSDIR=$(CURDIR)/release \ + --no-print-directory -C release \ + -f $(CURDIR)/Makefile + +lib/lib$(TARGET)d.a : lib debug $(SOURCES) $(INCLUDES) + @$(MAKE) BUILD=debug OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DDEBUG=1 -Og" \ + DEPSDIR=$(CURDIR)/debug \ + --no-print-directory -C debug \ + -f $(CURDIR)/Makefile + +dist-bin: all + @tar --exclude=*~ -cjf lib$(TARGET).tar.bz2 include lib + +dist-src: + @tar --exclude=*~ -cjf lib$(TARGET)-src.tar.bz2 include source Makefile + +dist: dist-src dist-bin + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr release debug lib *.bz2 + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES_SRC) : $(HFILES) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- + diff --git a/README.md b/README.md new file mode 100644 index 0000000..f20ae75 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +librw +===== + +This library is supposed to be a re-implementation of RenderWare graphics, +or a good part of it anyway. + +It is intended to be cross-platform in two senses eventually: +support rendering on different platforms similar to RW; +supporting all file formats for all platforms at all times and provide +way to convert to all other platforms. + +File formats are already supported rather well, although rasters +as used by TXD files still need some work, especially for PS2. + +As for rendering, D3D9 and OpenGL 3 work somewhat well but both still need +work. Rendering some things on the PS2 is working as a test only. + +# Building + +Get premake5. Generate a config, e.g. with ``premake5 gmake``, +and look in the build directory. diff --git a/TODO b/TODO new file mode 100644 index 0000000..9df3fd0 --- /dev/null +++ b/TODO @@ -0,0 +1,40 @@ +TODO: +- maintain the TODO list properly +- get the error system in better shape +- move everything to custom malloc +- proper cleanup! and check for memory leaks +- skeleton (partially done) +- examples +- morph targets and morphing +- geometry lock/unlock + -> reinstance + -> revisit pipelines + -> implement tristrips +- rasters + - lock/unlock + - camera rasters + - more file formats (PNG) +- PS2 rendering! +- Im2d and Im3d +- change naming convention? make globals stand out more +- Pipelines (PDS, Xbox, PC) +- ADC conversion + +BUGS: + - fseek with negative offset on ps2 over ps2link messes up the current position + +Clump & related: + - implement plugins: + Clump + Animation (old III dffs) + Frame + Animation (old III dffs) + Atomic + ((Particles)) 0x118 + Skin (old III dffs) + Geometry + ((Morph)) 0x105 + + - only stubs: + R* 2dfx 0x253F2F8 + R* Collision 0x253F2FA diff --git a/args.h b/args.h new file mode 100644 index 0000000..0e666c1 --- /dev/null +++ b/args.h @@ -0,0 +1,24 @@ +extern char *argv0; +#define USED(x) ((void)x) +#define SET(x) ((x)=0) + +#define ARGBEGIN for((argv0||(argv0=*argv)),argv++,argc--;\ + argv[0] && argv[0][0]=='-' && argv[0][1];\ + argc--, argv++) {\ + char *_args, *_argt;\ + char _argc;\ + _args = &argv[0][1];\ + if(_args[0]=='-' && _args[1]==0){\ + argc--; argv++; break;\ + }\ + _argc = 0;\ + while(*_args && (_argc = *_args++))\ + switch(_argc) +#define ARGEND SET(_argt);USED(_argt);USED(_argc);USED(_args);}USED(argv);USED(argc); +#define ARGF() (_argt=_args, _args=(char*)"",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): 0)) +#define EARGF(x) (_argt=_args, _args=(char*)"",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0))) + +#define ARGC() _argc + diff --git a/docker_rebuild_ps2.sh b/docker_rebuild_ps2.sh new file mode 100644 index 0000000..cf8148f --- /dev/null +++ b/docker_rebuild_ps2.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +TARGET=release_ps2 + +set -e + +LIBRWDIR=$(dirname "$(readlink -f "$0")") +echo "LIBRWDIR is $LIBRWDIR" + +cd "$LIBRWDIR" + +premake5 gmake + +docker rm librw_instance -f >/dev/null 2>&1 || true +docker stop librw_instance -t 0 >/dev/null 2>&1 || true + +docker pull librw/librw +docker run -v "$LIBRWDIR:/librw:rw,z" --name librw_instance -d librw/librw sleep infinity +docker exec -u builder librw_instance /bin/bash -c "cd /librw/build && make clean config=$TARGET && make config=$TARGET verbose=1" + +docker rm librw_instance -f >/dev/null 2>&1 || true +docker stop librw_instance -t 0 >/dev/null 2>&1 || true diff --git a/premake-vs2019.cmd b/premake-vs2019.cmd new file mode 100644 index 0000000..18298f5 --- /dev/null +++ b/premake-vs2019.cmd @@ -0,0 +1 @@ +premake5 vs2019 \ No newline at end of file diff --git a/premake5.exe b/premake5.exe new file mode 100644 index 0000000..9048d51 Binary files /dev/null and b/premake5.exe differ diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 0000000..940ee1c --- /dev/null +++ b/premake5.lua @@ -0,0 +1,263 @@ +newoption { + trigger = "glewdir", + value = "PATH", + description = "Directory of GLEW", + default = "../glew-2.1.0", +} + +newoption { + trigger = "gfxlib", + value = "LIBRARY", + description = "Choose a particular development library", + default = "glfw", + allowed = { + { "glfw", "GLFW" }, + { "sdl2", "SDL2" }, + }, +} + +newoption { + trigger = "glfwdir64", + value = "PATH", + description = "Directory of glfw", + default = "../glfw-3.3.2.bin.WIN64", +} + +newoption { + trigger = "glfwdir32", + value = "PATH", + description = "Directory of glfw", + default = "../glfw-3.3.2.bin.WIN32", +} + +newoption { + trigger = "sdl2dir", + value = "PATH", + description = "Directory of sdl2", + default = "../SDL2-2.0.8", +} + +workspace "librw" + location "build" + language "C++" + + configurations { "Release", "Debug" } + filter { "system:windows" } + configurations { "ReleaseStatic" } + platforms { "win-x86-null", "win-x86-gl3", "win-x86-d3d9", + "win-amd64-null", "win-amd64-gl3", "win-amd64-d3d9" } + filter { "system:linux" } + platforms { "linux-x86-null", "linux-x86-gl3", + "linux-amd64-null", "linux-amd64-gl3", + "linux-arm-null", "linux-arm-gl3", + "ps2" } + if _OPTIONS["gfxlib"] == "sdl2" then + includedirs { "/usr/include/SDL2" } + end + filter {} + + filter "configurations:Debug" + defines { "DEBUG" } + symbols "On" + filter "configurations:Release*" + defines { "NDEBUG" } + optimize "On" + filter "configurations:ReleaseStatic" + flags { "StaticRuntime" } + + filter { "platforms:*null" } + defines { "RW_NULL" } + filter { "platforms:*gl3" } + defines { "RW_GL3" } + if _OPTIONS["gfxlib"] == "sdl2" then + defines { "LIBRW_SDL2" } + end + filter { "platforms:*d3d9" } + defines { "RW_D3D9" } + filter { "platforms:ps2" } + defines { "RW_PS2" } + toolset "gcc" + gccprefix 'ee-' + buildoptions { "-nostdlib", "-fno-common" } + includedirs { "$(PS2SDK)/ee/include", "$(PS2SDK)/common/include" } + optimize "Off" + + filter { "platforms:*amd64*" } + architecture "x86_64" + filter { "platforms:*x86*" } + architecture "x86" + filter { "platforms:*arm*" } + architecture "ARM" + + filter { "platforms:win*" } + system "windows" + filter { "platforms:linux*" } + system "linux" + + filter { "platforms:win*gl3" } + defines { "GLEW_STATIC" } + includedirs { path.join(_OPTIONS["glewdir"], "include") } + includedirs { path.join(_OPTIONS["sdl2dir"], "include") } + filter { "platforms:win-x86-gl3" } + includedirs { path.join(_OPTIONS["glfwdir32"], "include") } + filter { "platforms:win-amd64-gl3" } + includedirs { path.join(_OPTIONS["glfwdir64"], "include") } + + filter "action:vs*" + buildoptions { "/wd4996", "/wd4244" } + + filter { "platforms:win*gl3", "action:not vs*" } + if _OPTIONS["gfxlib"] == "sdl2" then + includedirs { "/mingw/include/SDL2" } -- TODO: Detect this properly + end + + filter {} + + Libdir = "lib/%{cfg.platform}/%{cfg.buildcfg}" + Bindir = "bin/%{cfg.platform}/%{cfg.buildcfg}" + +project "librw" + kind "StaticLib" + targetname "rw" + targetdir (Libdir) + defines { "LODEPNG_NO_COMPILE_CPP" } + files { "src/*.*" } + files { "src/*/*.*" } + +project "dumprwtree" + kind "ConsoleApp" + targetdir (Bindir) + removeplatforms { "*gl3", "*d3d9", "ps2" } + files { "tools/dumprwtree/*" } + includedirs { "." } + libdirs { Libdir } + links { "librw" } + +function findlibs() + filter { "platforms:linux*gl3" } + links { "GL", "GLEW" } + if _OPTIONS["gfxlib"] == "glfw" then + links { "glfw" } + else + links { "SDL2" } + end + filter { "platforms:win*gl3", "action:vs*" } + defines { "GLEW_STATIC" } + links { "glew32s" } + filter { "platforms:win*gl3", "action:not vs*" } + links { "glew32" } + filter { "platforms:win-amd64-gl3" } + libdirs { path.join(_OPTIONS["glewdir"], "lib/Release/x64") } + libdirs { path.join(_OPTIONS["glfwdir64"], "lib-vc2015") } + libdirs { path.join(_OPTIONS["sdl2dir"], "lib/x64") } + filter { "platforms:win-x86-gl3" } + libdirs { path.join(_OPTIONS["glewdir"], "lib/Release/Win32") } + libdirs { path.join(_OPTIONS["glfwdir32"], "lib-vc2015") } + libdirs { path.join(_OPTIONS["sdl2dir"], "lib/x86") } + filter { "platforms:win*gl3" } + links { "opengl32" } + if _OPTIONS["gfxlib"] == "glfw" then + links { "glfw3" } + else + links { "SDL2" } + end + filter { "platforms:*d3d9" } + links { "gdi32", "d3d9" } + filter { "platforms:*d3d9", "action:vs*" } + links { "Xinput9_1_0" } + filter {} +end + +function skeleton() + files { "skeleton/*.cpp", "skeleton/*.h" } + files { "skeleton/imgui/*.cpp", "skeleton/imgui/*.h" } + includedirs { "skeleton" } +end + +function skeltool(dir) + targetdir (Bindir) + files { path.join("tools", dir, "*.cpp"), + path.join("tools", dir, "*.h") } + vpaths { + {["src"] = { path.join("tools", dir, "*") }}, + {["skeleton"] = { "skeleton/*" }}, + } + skeleton() + debugdir ( path.join("tools", dir) ) + includedirs { "." } + libdirs { Libdir } + links { "librw" } + findlibs() +end + +function vucode() + filter "files:**.dsm" + buildcommands { + 'cpp "%{file.relpath}" | dvp-as -o "%{cfg.objdir}/%{file.basename}.o"' + } + buildoutputs { '%{cfg.objdir}/%{file.basename}.o' } + filter {} +end + +project "playground" + kind "WindowedApp" + characterset ("MBCS") + skeltool("playground") + flags { "WinMain" } + removeplatforms { "*null" } + removeplatforms { "ps2" } -- for now + +project "imguitest" + kind "WindowedApp" + characterset ("MBCS") + skeltool("imguitest") + flags { "WinMain" } + removeplatforms { "*null" } + removeplatforms { "ps2" } + +project "lights" + kind "WindowedApp" + characterset ("MBCS") + skeltool("lights") + flags { "WinMain" } + removeplatforms { "*null" } + removeplatforms { "ps2" } + +project "ska2anm" + kind "ConsoleApp" + characterset ("MBCS") + targetdir (Bindir) + files { path.join("tools/ska2anm", "*.cpp"), + path.join("tools/ska2anm", "*.h") } + debugdir ( path.join("tools/ska2nm") ) + includedirs { "." } + libdirs { Libdir } + links { "librw" } + findlibs() + removeplatforms { "*gl3", "*d3d9", "*ps2" } + +project "ps2test" + kind "ConsoleApp" + targetdir (Bindir) + vucode() + removeplatforms { "*gl3", "*d3d9", "*null" } + targetextension '.elf' + includedirs { "." } + files { "tools/ps2test/*.cpp", + "tools/ps2test/vu/*.dsm", + "tools/ps2test/*.h" } + linkoptions '$(PS2SDK)/ee/startup/crt0.o' + linkoptions { '-mno-crt0', "-T$(PS2SDK)/ee/startup/linkfile" } + libdirs { "$(PS2SDK)/ee/lib" } + links { "librw" } + -- "c -lc" is a hack because we need -lc twice for some reason + links { "c -lc", "kernel", "mf" } + +--project "ps2rastertest" +-- kind "ConsoleApp" +-- targetdir (Bindir) +-- removeplatforms { "*gl3", "*d3d9" } +-- files { "tools/ps2rastertest/*.cpp" } +-- includedirs { "." } +-- libdirs { Libdir } +-- links { "librw" } diff --git a/rw.h b/rw.h new file mode 100644 index 0000000..fb9829f --- /dev/null +++ b/rw.h @@ -0,0 +1,25 @@ +#include +#include +#include + +#include "src/rwbase.h" +#include "src/rwerror.h" +#include "src/rwplg.h" +#include "src/rwrender.h" +#include "src/rwengine.h" +#include "src/rwpipeline.h" +#include "src/rwobjects.h" +#include "src/rwanim.h" +#include "src/rwplugins.h" +#include "src/rwuserdata.h" +#include "src/rwcharset.h" +#include "src/ps2/rwps2.h" +#include "src/ps2/rwps2plg.h" +#include "src/d3d/rwxbox.h" +#include "src/d3d/rwd3d.h" +#include "src/d3d/rwd3d8.h" +#include "src/d3d/rwd3d9.h" +#include "src/gl/rwwdgl.h" +#include "src/gl/rwgl3.h" +#include "src/gl/rwgl3shader.h" +#include "src/gl/rwgl3plg.h" diff --git a/skeleton/glfw.cpp b/skeleton/glfw.cpp new file mode 100644 index 0000000..23832c0 --- /dev/null +++ b/skeleton/glfw.cpp @@ -0,0 +1,262 @@ +#ifndef LIBRW_SDL2 + +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +#ifdef RW_OPENGL + +GLFWwindow *window; +int keymap[GLFW_KEY_LAST+1]; + +static void +initkeymap(void) +{ + int i; + for(i = 0; i < GLFW_KEY_LAST+1; i++) + keymap[i] = KEY_NULL; + keymap[GLFW_KEY_SPACE] = ' '; + keymap[GLFW_KEY_APOSTROPHE] = '\''; + keymap[GLFW_KEY_COMMA] = ','; + keymap[GLFW_KEY_MINUS] = '-'; + keymap[GLFW_KEY_PERIOD] = '.'; + keymap[GLFW_KEY_SLASH] = '/'; + keymap[GLFW_KEY_0] = '0'; + keymap[GLFW_KEY_1] = '1'; + keymap[GLFW_KEY_2] = '2'; + keymap[GLFW_KEY_3] = '3'; + keymap[GLFW_KEY_4] = '4'; + keymap[GLFW_KEY_5] = '5'; + keymap[GLFW_KEY_6] = '6'; + keymap[GLFW_KEY_7] = '7'; + keymap[GLFW_KEY_8] = '8'; + keymap[GLFW_KEY_9] = '9'; + keymap[GLFW_KEY_SEMICOLON] = ';'; + keymap[GLFW_KEY_EQUAL] = '='; + keymap[GLFW_KEY_A] = 'A'; + keymap[GLFW_KEY_B] = 'B'; + keymap[GLFW_KEY_C] = 'C'; + keymap[GLFW_KEY_D] = 'D'; + keymap[GLFW_KEY_E] = 'E'; + keymap[GLFW_KEY_F] = 'F'; + keymap[GLFW_KEY_G] = 'G'; + keymap[GLFW_KEY_H] = 'H'; + keymap[GLFW_KEY_I] = 'I'; + keymap[GLFW_KEY_J] = 'J'; + keymap[GLFW_KEY_K] = 'K'; + keymap[GLFW_KEY_L] = 'L'; + keymap[GLFW_KEY_M] = 'M'; + keymap[GLFW_KEY_N] = 'N'; + keymap[GLFW_KEY_O] = 'O'; + keymap[GLFW_KEY_P] = 'P'; + keymap[GLFW_KEY_Q] = 'Q'; + keymap[GLFW_KEY_R] = 'R'; + keymap[GLFW_KEY_S] = 'S'; + keymap[GLFW_KEY_T] = 'T'; + keymap[GLFW_KEY_U] = 'U'; + keymap[GLFW_KEY_V] = 'V'; + keymap[GLFW_KEY_W] = 'W'; + keymap[GLFW_KEY_X] = 'X'; + keymap[GLFW_KEY_Y] = 'Y'; + keymap[GLFW_KEY_Z] = 'Z'; + keymap[GLFW_KEY_LEFT_BRACKET] = '['; + keymap[GLFW_KEY_BACKSLASH] = '\\'; + keymap[GLFW_KEY_RIGHT_BRACKET] = ']'; + keymap[GLFW_KEY_GRAVE_ACCENT] = '`'; + keymap[GLFW_KEY_ESCAPE] = KEY_ESC; + keymap[GLFW_KEY_ENTER] = KEY_ENTER; + keymap[GLFW_KEY_TAB] = KEY_TAB; + keymap[GLFW_KEY_BACKSPACE] = KEY_BACKSP; + keymap[GLFW_KEY_INSERT] = KEY_INS; + keymap[GLFW_KEY_DELETE] = KEY_DEL; + keymap[GLFW_KEY_RIGHT] = KEY_RIGHT; + keymap[GLFW_KEY_LEFT] = KEY_LEFT; + keymap[GLFW_KEY_DOWN] = KEY_DOWN; + keymap[GLFW_KEY_UP] = KEY_UP; + keymap[GLFW_KEY_PAGE_UP] = KEY_PGUP; + keymap[GLFW_KEY_PAGE_DOWN] = KEY_PGDN; + keymap[GLFW_KEY_HOME] = KEY_HOME; + keymap[GLFW_KEY_END] = KEY_END; + keymap[GLFW_KEY_CAPS_LOCK] = KEY_CAPSLK; + keymap[GLFW_KEY_SCROLL_LOCK] = KEY_NULL; + keymap[GLFW_KEY_NUM_LOCK] = KEY_NULL; + keymap[GLFW_KEY_PRINT_SCREEN] = KEY_NULL; + keymap[GLFW_KEY_PAUSE] = KEY_NULL; + + keymap[GLFW_KEY_F1] = KEY_F1; + keymap[GLFW_KEY_F2] = KEY_F2; + keymap[GLFW_KEY_F3] = KEY_F3; + keymap[GLFW_KEY_F4] = KEY_F4; + keymap[GLFW_KEY_F5] = KEY_F5; + keymap[GLFW_KEY_F6] = KEY_F6; + keymap[GLFW_KEY_F7] = KEY_F7; + keymap[GLFW_KEY_F8] = KEY_F8; + keymap[GLFW_KEY_F9] = KEY_F9; + keymap[GLFW_KEY_F10] = KEY_F10; + keymap[GLFW_KEY_F11] = KEY_F11; + keymap[GLFW_KEY_F12] = KEY_F12; + keymap[GLFW_KEY_F13] = KEY_NULL; + keymap[GLFW_KEY_F14] = KEY_NULL; + keymap[GLFW_KEY_F15] = KEY_NULL; + keymap[GLFW_KEY_F16] = KEY_NULL; + keymap[GLFW_KEY_F17] = KEY_NULL; + keymap[GLFW_KEY_F18] = KEY_NULL; + keymap[GLFW_KEY_F19] = KEY_NULL; + keymap[GLFW_KEY_F20] = KEY_NULL; + keymap[GLFW_KEY_F21] = KEY_NULL; + keymap[GLFW_KEY_F22] = KEY_NULL; + keymap[GLFW_KEY_F23] = KEY_NULL; + keymap[GLFW_KEY_F24] = KEY_NULL; + keymap[GLFW_KEY_F25] = KEY_NULL; + keymap[GLFW_KEY_KP_0] = KEY_NULL; + keymap[GLFW_KEY_KP_1] = KEY_NULL; + keymap[GLFW_KEY_KP_2] = KEY_NULL; + keymap[GLFW_KEY_KP_3] = KEY_NULL; + keymap[GLFW_KEY_KP_4] = KEY_NULL; + keymap[GLFW_KEY_KP_5] = KEY_NULL; + keymap[GLFW_KEY_KP_6] = KEY_NULL; + keymap[GLFW_KEY_KP_7] = KEY_NULL; + keymap[GLFW_KEY_KP_8] = KEY_NULL; + keymap[GLFW_KEY_KP_9] = KEY_NULL; + keymap[GLFW_KEY_KP_DECIMAL] = KEY_NULL; + keymap[GLFW_KEY_KP_DIVIDE] = KEY_NULL; + keymap[GLFW_KEY_KP_MULTIPLY] = KEY_NULL; + keymap[GLFW_KEY_KP_SUBTRACT] = KEY_NULL; + keymap[GLFW_KEY_KP_ADD] = KEY_NULL; + keymap[GLFW_KEY_KP_ENTER] = KEY_NULL; + keymap[GLFW_KEY_KP_EQUAL] = KEY_NULL; + keymap[GLFW_KEY_LEFT_SHIFT] = KEY_LSHIFT; + keymap[GLFW_KEY_LEFT_CONTROL] = KEY_LCTRL; + keymap[GLFW_KEY_LEFT_ALT] = KEY_LALT; + keymap[GLFW_KEY_LEFT_SUPER] = KEY_NULL; + keymap[GLFW_KEY_RIGHT_SHIFT] = KEY_RSHIFT; + keymap[GLFW_KEY_RIGHT_CONTROL] = KEY_RCTRL; + keymap[GLFW_KEY_RIGHT_ALT] = KEY_RALT; + keymap[GLFW_KEY_RIGHT_SUPER] = KEY_NULL; + keymap[GLFW_KEY_MENU] = KEY_NULL; +} + +static void KeyUp(int key) { EventHandler(KEYUP, &key); } +static void KeyDown(int key) { EventHandler(KEYDOWN, &key); } + +static void +keypress(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + if(key >= 0 && key <= GLFW_KEY_LAST){ + if(action == GLFW_RELEASE) KeyUp(keymap[key]); + else if(action == GLFW_PRESS) KeyDown(keymap[key]); + else if(action == GLFW_REPEAT) KeyDown(keymap[key]); + } +} + +static void +charinput(GLFWwindow *window, unsigned int c) +{ + EventHandler(CHARINPUT, (void*)(uintptr)c); +} + +static void +resize(GLFWwindow *window, int w, int h) +{ + rw::Rect r; + r.x = 0; + r.y = 0; + r.w = w; + r.h = h; + EventHandler(RESIZE, &r); +} + +static void +mousemove(GLFWwindow *window, double x, double y) +{ + sk::MouseState ms; + ms.posx = x; + ms.posy = y; + EventHandler(MOUSEMOVE, &ms); +} + +static void +mousebtn(GLFWwindow *window, int button, int action, int mods) +{ + static int buttons = 0; + sk::MouseState ms; + + switch(button){ + case GLFW_MOUSE_BUTTON_LEFT: + if(action == GLFW_PRESS) + buttons |= 1; + else + buttons &= ~1; + break; + case GLFW_MOUSE_BUTTON_MIDDLE: + if(action == GLFW_PRESS) + buttons |= 2; + else + buttons &= ~2; + break; + case GLFW_MOUSE_BUTTON_RIGHT: + if(action == GLFW_PRESS) + buttons |= 4; + else + buttons &= ~4; + break; + } + + ms.buttons = buttons; + EventHandler(MOUSEBTN, &ms); +} + +int +main(int argc, char *argv[]) +{ + args.argc = argc; + args.argv = argv; + + if(EventHandler(INITIALIZE, nil) == EVENTERROR) + return 0; + + engineOpenParams.width = sk::globals.width; + engineOpenParams.height = sk::globals.height; + engineOpenParams.windowtitle = sk::globals.windowtitle; + engineOpenParams.window = &window; + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + initkeymap(); + glfwSetKeyCallback(window, keypress); + glfwSetCharCallback(window, charinput); + glfwSetWindowSizeCallback(window, resize); + glfwSetCursorPosCallback(window, mousemove); + glfwSetMouseButtonCallback(window, mousebtn); + + float lastTime = glfwGetTime()*1000; + while(!sk::globals.quit && !glfwWindowShouldClose(window)){ + float currTime = glfwGetTime()*1000; + float timeDelta = (currTime - lastTime)*0.001f; + glfwPollEvents(); + + EventHandler(IDLE, &timeDelta); + + lastTime = currTime; + } + + EventHandler(RWTERMINATE, nil); + + return 0; +} + +namespace sk { + +void +SetMousePosition(int x, int y) +{ + glfwSetCursorPos(*engineOpenParams.window, (double)x, (double)y); +} + +} + +#endif +#endif diff --git a/skeleton/imgui/ImGuizmo.cpp b/skeleton/imgui/ImGuizmo.cpp new file mode 100644 index 0000000..384e752 --- /dev/null +++ b/skeleton/imgui/ImGuizmo.cpp @@ -0,0 +1,1834 @@ +// The MIT License(MIT) +// +// Copyright(c) 2016 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "imgui.h" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" +#include "ImGuizmo.h" + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace ImGuizmo +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + + const float screenRotateSize = 0.06f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float *a, const float *b, float *r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + //template T LERP(T x, T y, float z) { return (x + (y - x)*z); } + template T Clamp(T x, T y, T z) { return ((xz) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x>=y) && (x<=z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x*x + y*y + z*z); }; + float LengthSq() const { return (x*x + y*y + z*z); }; + vec_t Normalize() { (*this) *= (1.f / Length()); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + float Dot(const vec_t &v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + float Dot3(const vec_t &v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t & s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w *f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t &v1, const vec_t &v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t & p_point1, const vec_t & p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + matrix_t(const matrix_t& other) { memcpy(&m16[0], &other.m16[0], sizeof(float) * 16); } + matrix_t() {} + + operator float * () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t &matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t &m1, const matrix_t &m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t &srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t & axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t & s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t &srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t & axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + NONE, + MOVE_X, + MOVE_Y, + MOVE_Z, + MOVE_XY, + MOVE_XZ, + MOVE_YZ, + MOVE_SCREEN, + ROTATE_X, + ROTATE_Y, + ROTATE_Z, + ROTATE_SCREEN, + SCALE_X, + SCALE_Y, + SCALE_Z, + SCALE_XYZ, + BOUNDS + }; + + struct Context + { + Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false) + { + } + + ImDrawList* mDrawList; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbEnable; + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + }; + + static Context gContext; + + static const float angleLimit = 0.96f; + static const float planeLimit = 0.2f; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const ImU32 directionColor[3] = { 0xFF0000AA, 0xFF00AA00, 0xFFAA0000 }; + + // Alpha: 100%: FF, 87%: DE, 70%: B3, 54%: 8A, 50%: 80, 38%: 61, 12%: 1F + static const ImU32 planeBorderColor[3] = { 0xFFAA0000, 0xFF0000AA, 0xFF00AA00 }; + static const ImU32 planeColor[3] = { 0x610000AA, 0x6100AA00, 0x61AA0000 }; + static const ImU32 selectionColor = 0x8A1080FF; + static const ImU32 inactiveColor = 0x99999999; + static const ImU32 translationLineColor = 0xAAAAAAAA; + static const char *translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", "X : %5.3f Y : %5.3f", "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char *scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char *rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 0,1,0, 1,2,0, 0,2,1, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(vec_t *gizmoHitProportion); + static int GetRotateType(); + static int GetScaleType(); + + static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= gContext.mWidth; + trans.y *= gContext.mHeight; + trans.x += gContext.mX; + trans.y += gContext.mY; + return ImVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t &rayOrigin, vec_t &rayDir) + { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + float mox = ((io.MousePos.x - gContext.mX) / gContext.mWidth) * 2.f - 1.f; + float moy = (1.f - ((io.MousePos.y - gContext.mY) / gContext.mHeight)) * 2.f - 1.f; + + rayOrigin.Transform(makeVect(mox, moy, 0.f, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, 1.f, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float IntersectRayPlane(const vec_t & rOrigin, const vec_t& rVector, const vec_t& plan) + { + float numer = plan.Dot3(rOrigin) - plan.w; + float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + return -1.0f; + + return -(numer / denom); + } + + static bool IsInContextRect( ImVec2 p ) + { + return IsWithin( p.x, gContext.mX, gContext.mXMax ) && IsWithin(p.y, gContext.mY, gContext.mYMax ); + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + } + + void SetDrawlist() + { + gContext.mDrawList = ImGui::GetWindowDrawList(); + } + + void BeginFrame() + { + ImGuiIO& io = ImGui::GetIO(); + + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + ImGui::SetNextWindowSize(io.DisplaySize); + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + ImGui::End(); + ImGui::PopStyleColor(); + } + + bool IsUsing() + { + return gContext.mbUsing||gContext.mbUsingBounds; + } + + bool IsOver() + { + return (GetMoveType(NULL) != NONE) || GetRotateType() != NONE || GetScaleType() != NONE || IsUsing(); + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static float GetUniform(const vec_t& position, const matrix_t& mat) + { + vec_t trf = makeVect(position.x, position.y, position.z, 1.f); + trf.Transform(mat); + return trf.w; + } + + static void ComputeContext(const float *view, const float *projection, float *matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + + if (mode == LOCAL) + { + gContext.mModel = *(matrix_t*)matrix; + gContext.mModel.OrthoNormalize(); + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + gContext.mScreenFactor = 0.1f * GetUniform(gContext.mModel.v.position, gContext.mViewProjection); + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(ImU32 *colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MOVE_SCREEN) ? selectionColor : 0xFFFFFFFF; + for (int i = 0; i < 3; i++) + { + int colorPlaneIndex = (i + 2) % 3; + colors[i + 1] = (type == (int)(MOVE_X + i)) ? selectionColor : directionColor[i]; + colors[i + 4] = (type == (int)(MOVE_XY + i)) ? selectionColor : planeColor[colorPlaneIndex]; + colors[i + 4] = (type == MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == ROTATE_SCREEN) ? selectionColor : 0xFFFFFFFF; + for (int i = 0; i < 3; i++) + colors[i + 1] = (type == (int)(ROTATE_X + i)) ? selectionColor : directionColor[i]; + break; + case SCALE: + colors[0] = (type == SCALE_XYZ) ? selectionColor : 0xFFFFFFFF; + for (int i = 0; i < 3; i++) + colors[i + 1] = (type == (int)(SCALE_X + i)) ? selectionColor : directionColor[i]; + break; + } + } + else + { + for (int i = 0; i < 7; i++) + colors[i] = inactiveColor; + } + } + + static void ComputeTripodAxisAndVisibility(int axisIndex, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit) + { + const int planNormal = (axisIndex + 2) % 3; + dirPlaneX = directionUnary[axisIndex]; + dirPlaneY = directionUnary[(axisIndex + 1) % 3]; + + if (gContext.mbUsing) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; + belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; + + dirPlaneX *= gContext.mAxisFactor[axisIndex]; + dirPlaneY *= gContext.mAxisFactor[(axisIndex + 1) % 3]; + } + else + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[planNormal], gContext.mModel); + dirPlaneNormalWorld.Normalize(); + + vec_t dirPlaneXWorld(dirPlaneX); + dirPlaneXWorld.TransformVector(gContext.mModel); + dirPlaneXWorld.Normalize(); + + vec_t dirPlaneYWorld(dirPlaneY); + dirPlaneYWorld.TransformVector(gContext.mModel); + dirPlaneYWorld.Normalize(); + + vec_t cameraEyeToGizmo = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + float dotCameraDirX = cameraEyeToGizmo.Dot3(dirPlaneXWorld); + float dotCameraDirY = cameraEyeToGizmo.Dot3(dirPlaneYWorld); + + // compute factor values +#ifdef FLIP + float mulAxisX = (dotCameraDirX > 0.f) ? -1.f : 1.f; + float mulAxisY = (dotCameraDirY > 0.f) ? -1.f : 1.f; +#else + float mulAxisX = 1.f; + float mulAxisY = 1.f; +#endif + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + belowAxisLimit = fabsf(dotCameraDirX) < angleLimit; + belowPlaneLimit = (fabsf(cameraEyeToGizmo.Dot3(dirPlaneNormalWorld)) > planeLimit); + + // and store values + gContext.mAxisFactor[axisIndex] = mulAxisX; + gContext.mAxisFactor[(axisIndex+1)%3] = mulAxisY; + gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; + gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; + } + } + + static void ComputeSnap(float*value, float snap) + { + if (snap <= FLT_EPSILON) + return; + float modulo = fmodf(*value, snap); + float moduloRatio = fabsf(modulo) / snap; + if (moduloRatio < snapTension) + *value -= modulo; + else if (moduloRatio >(1.f - snapTension)) + *value = *value - modulo + snap * ((*value<0.f) ? -1.f : 1.f); + } + static void ComputeSnap(vec_t& value, float *snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -0.9999f, 0.9999f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(int type) + { + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + cameraToModelNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + for (int axis = 0; axis < 3; axis++) + { + ImVec2 circlePos[halfCircleSegmentCount]; + + float angleStart = atan2f(cameraToModelNormalized[(4-axis)%3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; + + for (unsigned int i = 0; i < halfCircleSegmentCount; i++) + { + float ng = angleStart + ZPI * ((float)i / (float)halfCircleSegmentCount); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis+1)%3], axisPos[(axis+2)%3]) * gContext.mScreenFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + + float radiusAxis = sqrtf( (ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]) )); + if(radiusAxis > gContext.mRadiusSquareCenter) + gContext.mRadiusSquareCenter = radiusAxis; + + drawList->AddPolyline(circlePos, halfCircleSegmentCount, colors[3 - axis], false, 2); + } + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, 3.f); + + if (gContext.mbUsing) + { + ImVec2 circlePos[halfCircleSegmentCount +1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount; i++) + { + float ng = gContext.mRotationAngle * ((float)(i-1) / (float)(halfCircleSegmentCount -1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount, 0x801080FF); + drawList->AddPolyline(circlePos, halfCircleSegmentCount, 0xFF1080FF, true, 2); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - ROTATE_X], (gContext.mRotationAngle/ZPI)*180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), 0xFF000000, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), 0xFFFFFFFF, tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, 0x80000000, 6.f); + } + } + + static void DrawScaleGizmo(int type) + { + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing) + scaleDisplay = gContext.mScale; + + for (unsigned int i = 0; i < 3; i++) + { + vec_t dirPlaneX, dirPlaneY; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + // draw axis + if (belowAxisLimit) + { + ImVec2 baseSSpace = worldToPos(dirPlaneX * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirPlaneX * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirPlaneX * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, 0xFF404040, 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, 0xFF404040); + } + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + drawList->AddCircleFilled(worldDirSSpace, 6.f, colors[i + 1]); + +#ifdef HATCHED + if (gContext.mAxisFactor[i] < 0.f) + DrawHatchedAxis(dirPlaneX * scaleDisplay[i]); +#endif + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); + + if (gContext.mbUsing) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), 0xFF000000, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), 0xFFFFFFFF, tmps); + } + } + + + static void DrawTranslationGizmo(int type) + { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + return; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (unsigned int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY; + ComputeTripodAxisAndVisibility(i, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + // draw axis + if (belowAxisLimit) + { + ImVec2 baseSSpace = worldToPos(dirPlaneX * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirPlaneX * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= 6.0f; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + +#ifdef HATCHED + if (gContext.mAxisFactor[i] < 0.f) + DrawHatchedAxis(dirPlaneX); +#endif + } + + // draw plane + if (belowPlaneLimit) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, planeBorderColor[i], true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, 6.f, colors[0], 32); + + if (gContext.mbUsing) + { + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), 0xFF000000, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), 0xFFFFFFFF, tmps); + } + } + + static bool CanActivate() + { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + return true; + return false; + } + + static void HandleAndDrawLocalBounds(float *bounds, matrix_t *matrix, float *snapValues) + { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (unsigned int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf( Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld) ); + if ( dt >= bestDot ) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if( dt >= 0.1f ) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if( numAxes == 0 ) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + else if( bestAxis != axes[0] ) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if( axes[i] == bestAxis ) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? 0xFF000000 : 0x80000000; + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4;i++) + { + ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(aabb[(i+1)%4], boundsMVP); + if( !IsInContextRect( worldBound1 ) || !IsInContextRect( worldBound2 ) ) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min( stepCount, 1000 ); + float stepLength = 1.f / (float)stepCount; + for (int j = 0; j < stepCount; j++) + { + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + drawList->AddLine(worldBoundSS1, worldBoundSS2, 0xAAAAAA + anchorAlpha, 3.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4] ) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius*AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius*AnchorBigRadius); + + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (0xAAAAAA + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (0xAAAAAA + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, bigAnchorColor); + drawList->AddCircleFilled(midBound, AnchorSmallRadius, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i%2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + continue; + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + ratioAxis = length / boundSize; + } + scale.component[axisIndex1] *= ratioAxis; + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z:%.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), 0xFF000000, tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), 0xFFFFFFFF, tmps); + } + + if (!io.MouseDown[0]) + gContext.mbUsingBounds = false; + + if( gContext.mbUsingBounds ) + break; + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType() + { + ImGuiIO& io = ImGui::GetIO(); + int type = NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y) + type = SCALE_XYZ; + + const vec_t direction[3] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + // compute + for (unsigned int i = 0; i < 3 && type == NONE; i++) + { + vec_t dirPlaneX, dirPlaneY; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const int planNormal = (i + 2) % 3; + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, direction[planNormal])); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowAxisLimit && dy > -0.1f && dy < 0.1f && dx > 0.1f && dx < 1.f) + type = SCALE_X + i; + } + return type; + } + + static int GetRotateType() + { + ImGuiIO& io = ImGui::GetIO(); + int type = NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (dist >= (gContext.mRadiusSquareCenter - 1.0f) && dist < (gContext.mRadiusSquareCenter + 1.0f)) + type = ROTATE_SCREEN; + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir}; + + for (unsigned int i = 0; i < 3 && type == NONE; i++) + { + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + + if (Dot(Normalized(localPos), gContext.mRayVector) > FLT_EPSILON) + continue; + + float distance = localPos.Length() / gContext.mScreenFactor; + if (distance > 0.9f && distance < 1.1f) + type = ROTATE_X + i; + } + + return type; + } + + static int GetMoveType(vec_t *gizmoHitProportion) + { + ImGuiIO& io = ImGui::GetIO(); + int type = NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y) + type = MOVE_SCREEN; + + const vec_t direction[3] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + // compute + for (unsigned int i = 0; i < 3 && type == NONE; i++) + { + vec_t dirPlaneX, dirPlaneY; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const int planNormal = (i + 2) % 3; + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, direction[planNormal])); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowAxisLimit && dy > -0.1f && dy < 0.1f && dx > 0.1f && dx < 1.f) + type = MOVE_X + i; + + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3]) + type = MOVE_XY + i; + + if (gizmoHitProportion) + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + return type; + } + + static void HandleTranslation(float *matrix, float *deltaMatrix, int& type, float *snap) + { + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL || type == MOVE_SCREEN; + + // move + if (gContext.mbUsing) + { + ImGui::CaptureMouseFromApp(); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MOVE_X && gContext.mCurrentOperation <= MOVE_Z) + { + int axisIndex = gContext.mCurrentOperation - MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + + + matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + gContext.mbUsing = false; + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = GetMoveType(&gizmoHitProportion); + if (CanActivate() && type != NONE) + { + ImGui::CaptureMouseFromApp(); + gContext.mbUsing = true; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.up, -gContext.mCameraDir }; + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + } + + static void HandleScale(float *matrix, float *deltaMatrix, int& type, float *snap) + { + ImGuiIO& io = ImGui::GetIO(); + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = GetScaleType(); + if (CanActivate() && type != NONE) + { + ImGui::CaptureMouseFromApp(); + gContext.mbUsing = true; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing) + { + ImGui::CaptureMouseFromApp(); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= SCALE_X && gContext.mCurrentOperation <= SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModel.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3;i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModel; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + deltaMatrixScale.Scale(gContext.mScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + gContext.mbUsing = false; + + type = gContext.mCurrentOperation; + } + } + + static void HandleRotation(float *matrix, float *deltaMatrix, int& type, float *snap) + { + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + + if (!gContext.mbUsing) + { + type = GetRotateType(); + + if (type == ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != NONE) + { + ImGui::CaptureMouseFromApp(); + gContext.mbUsing = true; + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing) + { + ImGui::CaptureMouseFromApp(); + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModel; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + gContext.mbUsing = false; + + type = gContext.mCurrentOperation; + } + } + + void DecomposeMatrixToComponents(const float *matrix, float *translation, float *rotation, float *scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2]* mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float *translation, const float *rotation, const float *scale, float *matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3;i++) + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + validScale[i] = 0.001f; + else + validScale[i] = scale[i]; + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void Manipulate(const float *view, const float *projection, OPERATION operation, MODE mode, float *matrix, float *deltaMatrix, float *snap, float *localBounds, float *boundsSnap) + { + ComputeContext(view, projection, matrix, mode); + + // set delta to identity + if (deltaMatrix) + ((matrix_t*)deltaMatrix)->SetToIdentity(); + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (camSpacePosition.z < 0.001f) + return; + + // -- + int type = NONE; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + switch (operation) + { + case ROTATE: + HandleRotation(matrix, deltaMatrix, type, snap); + break; + case TRANSLATE: + HandleTranslation(matrix, deltaMatrix, type, snap); + break; + case SCALE: + HandleScale(matrix, deltaMatrix, type, snap); + break; + } + } + } + + if (localBounds && !gContext.mbUsing) + HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap); + + if (!gContext.mbUsingBounds) + { + switch (operation) + { + case ROTATE: + DrawRotationGizmo(type); + break; + case TRANSLATE: + DrawTranslationGizmo(type); + break; + case SCALE: + DrawScaleGizmo(type); + break; + } + } + } + + void DrawCube(const float *view, const float *projection, float *matrix) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + const matrix_t& model = *(matrix_t*)matrix; + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, gContext.mMVP); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + continue; + + // 3D->2D + ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + + // back face culling + vec_t cullPos, cullNormal; + cullPos.TransformPoint(faceCoords[0] * 0.5f * invert, model); + cullNormal.TransformVector(directionUnary[normalIndex] * invert, model); + float dt = Dot(Normalized(cullPos - viewInverse.v.position), Normalized(cullNormal)); + if (dt>0.f) + continue; + + // draw face with lighter color + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, directionColor[normalIndex] | 0x808080); + } + } +}; + diff --git a/skeleton/imgui/ImGuizmo.h b/skeleton/imgui/ImGuizmo.h new file mode 100644 index 0000000..d0ec81e --- /dev/null +++ b/skeleton/imgui/ImGuizmo.h @@ -0,0 +1,169 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.04 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2016 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +#include "imconfig.h" +#endif +#ifndef IMGUI_API +#define IMGUI_API +#endif + +namespace ImGuizmo +{ + // call inside your own window and before Manipulate() in order to draw gizmo to that window. + IMGUI_API void SetDrawlist(); + + // call BeginFrame right after ImGui_XXXX_NewFrame(); + IMGUI_API void BeginFrame(); + + // return true if mouse cursor is over any gizmo control (axis, plan or screen component) + IMGUI_API bool IsOver(); + + // return true if mouse IsOver or if the gizmo is in moving state + IMGUI_API bool IsUsing(); + + // enable/disable the gizmo. Stay in the state until next call to Enable. + // gizmo is rendered with gray half transparent color when disabled + IMGUI_API void Enable(bool enable); + + // helper functions for manualy editing translation/rotation/scale with an input float + // translation, rotation and scale float points to 3 floats each + // Angles are in degrees (more suitable for human editing) + // example: + // float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + // ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + // ImGui::InputFloat3("Tr", matrixTranslation, 3); + // ImGui::InputFloat3("Rt", matrixRotation, 3); + // ImGui::InputFloat3("Sc", matrixScale, 3); + // ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); + // + // These functions have some numerical stability issues for now. Use with caution. + IMGUI_API void DecomposeMatrixToComponents(const float *matrix, float *translation, float *rotation, float *scale); + IMGUI_API void RecomposeMatrixFromComponents(const float *translation, const float *rotation, const float *scale, float *matrix); + + IMGUI_API void SetRect(float x, float y, float width, float height); + + // Render a cube with face color corresponding to face normal. Usefull for debug/tests + IMGUI_API void DrawCube(const float *view, const float *projection, float *matrix); + + // call it when you want a gizmo + // Needs view and projection matrices. + // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional + // translation is applied in world space + enum OPERATION + { + TRANSLATE, + ROTATE, + SCALE + }; + + enum MODE + { + LOCAL, + WORLD + }; + + IMGUI_API void Manipulate(const float *view, const float *projection, OPERATION operation, MODE mode, float *matrix, float *deltaMatrix = 0, float *snap = 0, float *localBounds = NULL, float *boundsSnap = NULL); +}; diff --git a/skeleton/imgui/LICENSE_imgui.txt b/skeleton/imgui/LICENSE_imgui.txt new file mode 100644 index 0000000..5a9b98b --- /dev/null +++ b/skeleton/imgui/LICENSE_imgui.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2017 Omar Cornut and ImGui contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skeleton/imgui/LICENSE_imguizmo.txt b/skeleton/imgui/LICENSE_imguizmo.txt new file mode 100644 index 0000000..dcbee45 --- /dev/null +++ b/skeleton/imgui/LICENSE_imguizmo.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cedric Guillemet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skeleton/imgui/imconfig.h b/skeleton/imgui/imconfig.h new file mode 100644 index 0000000..824a81a --- /dev/null +++ b/skeleton/imgui/imconfig.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// USER IMPLEMENTATION +// This file contains compile-time options for ImGui. +// Other options (memory allocation overrides, callbacks, etc.) can be set at runtime via the ImGuiIO structure - ImGui::GetIO(). +//----------------------------------------------------------------------------- + +#pragma once + +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) + +//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. +//#define IMGUI_API __declspec( dllexport ) +//#define IMGUI_API __declspec( dllimport ) + +//---- Don't define obsolete functions names. Consider enabling from time to time or when updating to reduce like hood of using already obsolete function/names +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//---- Include imgui_user.h at the end of imgui.h +//#define IMGUI_INCLUDE_IMGUI_USER_H + +//---- Don't implement default handlers for Windows (so as not to link with OpenClipboard() and others Win32 functions) +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS +//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS + +//---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) +//---- It is very strongly recommended to NOT disable the demo windows. Please read the comment at the top of imgui_demo.cpp to learn why. +//#define IMGUI_DISABLE_DEMO_WINDOWS + +//---- Don't implement ImFormatString(), ImFormatStringV() so you can reimplement them yourself. +//#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +//---- Pack colors to BGRA instead of RGBA (remove need to post process vertex buffer in back ends) +//#define IMGUI_USE_BGRA_PACKED_COLOR + +//---- Implement STB libraries in a namespace to avoid linkage conflicts +//#define IMGUI_STB_NAMESPACE ImGuiStb + +//---- Define constructor and implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. +/* +#define IM_VEC2_CLASS_EXTRA \ + ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ + operator MyVec2() const { return MyVec2(x,y); } + +#define IM_VEC4_CLASS_EXTRA \ + ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ + operator MyVec4() const { return MyVec4(x,y,z,w); } +*/ + +//---- Use 32-bit vertex indices (instead of default: 16-bit) to allow meshes with more than 64K vertices +//#define ImDrawIdx unsigned int + +//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. +//---- e.g. create variants of the ImGui::Value() helper for your low-level math types, or your own widgets/helpers. +/* +namespace ImGui +{ + void Value(const char* prefix, const MyMatrix44& v, const char* float_format = NULL); +} +*/ + diff --git a/skeleton/imgui/imgui.cpp b/skeleton/imgui/imgui.cpp new file mode 100644 index 0000000..e0f1af4 --- /dev/null +++ b/skeleton/imgui/imgui.cpp @@ -0,0 +1,11739 @@ +// dear imgui, v1.54 WIP +// (main code and documentation) + +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui +// Releases change-log at https://github.com/ocornut/imgui/releases +// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269 +// Developed by Omar Cornut and every direct or indirect contributors to the GitHub. +// This library is free but I need your support to sustain development and maintenance. +// If you work for a company, please consider financial support, see Readme. For individuals: https://www.patreon.com/imgui + +/* + + Index + - MISSION STATEMENT + - END-USER GUIDE + - PROGRAMMER GUIDE (read me!) + - Read first + - How to update to a newer version of Dear ImGui + - Getting started with integrating Dear ImGui in your code/engine + - API BREAKING CHANGES (read me when you update!) + - ISSUES & TODO LIST + - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + - How can I help? + - What is ImTextureID and how do I display an image? + - I integrated Dear ImGui in my engine and the text or lines are blurry.. + - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + - How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on labels/IDs. + - How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + - How can I load a different font than the default? + - How can I easily use icons in my application? + - How can I load multiple fonts? + - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? + - How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + - ISSUES & TODO-LIST + - CODE + + + MISSION STATEMENT + ================= + + - Easy to use to create code-driven and data-driven tools + - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools + - Easy to hack and improve + - Minimize screen real-estate usage + - Minimize setup and maintenance + - Minimize state storage on user side + - Portable, minimize dependencies, run on target (consoles, phones, etc.) + - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window, opening a tree node + for the first time, etc. but a typical frame won't allocate anything) + + Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes: + - Doesn't look fancy, doesn't animate + - Limited layout features, intricate layouts are typically crafted in code + + + END-USER GUIDE + ============== + + - Double-click on title bar to collapse window. + - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin(). + - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents). + - Click and drag on any empty space to move window. + - TAB/SHIFT+TAB to cycle through keyboard editable fields. + - CTRL+Click on a slider or drag box to input value as text. + - Use mouse wheel to scroll. + - Text editor: + - Hold SHIFT or use mouse to select text. + - CTRL+Left/Right to word jump. + - CTRL+Shift+Left/Right to select words. + - CTRL+A our Double-Click to select all. + - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ + - CTRL+Z,CTRL+Y to undo/redo. + - ESCAPE to revert text to its original value. + - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!) + - Controls are automatically adjusted for OSX to match standard OSX text editing operations. + + + PROGRAMMER GUIDE + ================ + + READ FIRST + + - Read the FAQ below this section! + - Your code creates the UI, if your code doesn't run the UI is gone! == very dynamic UI, no construction/destructions steps, less data retention + on your side, no state duplication, less sync, less bugs. + - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. + - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861 + + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI + + - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h) + - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. + If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed from the public API. + If you have a problem with a missing function/symbols, search for its name in the code, there will likely be a comment about it. + Please report any issue to the GitHub page! + - Try to keep your copy of dear imgui reasonably up to date. + + GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE + + - Add the Dear ImGui source files to your projects, using your preferred build system. + It is recommended you build the .cpp files as part of your project and not as a library. + - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types. + - See examples/ folder for standalone sample applications. + - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/. + - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. + + - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize + (application resolution). Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic + integration you don't need to worry about it all. + - Init: call io.Fonts->GetTexDataAsRGBA32(...), it will build the font atlas texture, then load the texture pixels into graphics memory. + - Every frame: + - In your main loop as early a possible, fill the IO fields marked 'Input' (e.g. mouse position, buttons, keyboard info, etc.) + - Call ImGui::NewFrame() to begin the frame + - You can use any ImGui function you want between NewFrame() and Render() + - Call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your io.RenderDrawListFn handler. + (Even if you don't render, call Render() and ignore the callback, or call EndFrame() instead. Otherwhise some features will break) + - All rendering information are stored into command-lists until ImGui::Render() is called. + - Dear ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide. + - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases + of your own application. + - Refer to the examples applications in the examples/ folder for instruction on how to setup your code. + - A minimal application skeleton may be: + + // Application init + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize.x = 1920.0f; + io.DisplaySize.y = 1280.0f; + io.RenderDrawListsFn = MyRenderFunction; // Setup a render function, or set to NULL and call GetDrawData() after Render() to access render data. + // TODO: Fill others settings of the io structure later. + + // Load texture atlas (there is a default font so you don't need to care about choosing a font yet) + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height); + // TODO: At this points you've got the texture data and you need to upload that your your graphic system: + MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA) + // TODO: Store your texture pointer/identifier (whatever your engine uses) in 'io.Fonts->TexID'. This will be passed back to your via the renderer. + io.Fonts->TexID = (void*)texture; + + // Application main loop + while (true) + { + // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or write to those fields from your Windows message loop handlers, etc.) + ImGuiIO& io = ImGui::GetIO(); + io.DeltaTime = 1.0f/60.0f; + io.MousePos = mouse_pos; + io.MouseDown[0] = mouse_button_0; + io.MouseDown[1] = mouse_button_1; + + // Call NewFrame(), after this point you can use ImGui::* functions anytime + ImGui::NewFrame(); + + // Most of your application code here + MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); + MyGameRender(); // may use any ImGui functions as well! + + // Render & swap video buffers + ImGui::Render(); + SwapBuffers(); + } + + - A minimal render function skeleton may be: + + void void MyRenderFunction(ImDrawData* draw_data)(ImDrawData* draw_data) + { + // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled + // TODO: Setup viewport, orthographic projection matrix + // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // The texture for the draw call is specified by pcmd->TextureId. + // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization. + MyEngineBindTexture(pcmd->TextureId); + + // We are using scissoring to clip some objects. All low-level graphics API supports it. + // If your engine doesn't support scissoring yet, you will get some small glitches (some elements outside their bounds) which you can fix later. + MyEngineScissor((int)pcmd->ClipRect.x, (int)pcmd->ClipRect.y, (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + + // Render 'pcmd->ElemCount/3' indexed triangles. + // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices. + MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + } + + - The examples/ folders contains many functional implementation of the pseudo-code above. + - When calling NewFrame(), the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'io.WantTextInput' flags are updated. + They tell you if ImGui intends to use your inputs. So for example, if 'io.WantCaptureMouse' is set you would typically want to hide + mouse inputs from the rest of your application. Read the FAQ below for more information about those flags. + + + + API BREAKING CHANGES + ==================== + + Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. + Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. + Also read releases logs https://github.com/ocornut/imgui/releases for more details. + + - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete). + - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags + - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame. + - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. + - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete). + - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete). + - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete). + - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete). + - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). + - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. + - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. + Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. + - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. + - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg. + - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding. + - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); + - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. + - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. + - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. + removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting. + - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! + - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). + - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). + - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! + - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). + - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). + - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. + - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. + - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame. + - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. + - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete). + - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete). + - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton(). + - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu. + - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the various new options. + - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0))' + - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse + - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. + - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. + - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetId() and use it instead of passing string to BeginChild(). + - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. + - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc. + - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully breakage should be minimal. + - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. + If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you. + However if your TitleBg/TitleBgActive alpha was <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. + This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color. + ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) + { + float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; + return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); + } + If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color. + - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext(). + - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. + - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen). + - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDraw::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer. + - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref github issue #337). + - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337) + - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). + - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert. + - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you. + - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. + - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. + - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. + GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side. + GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out! + - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize + - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project. + - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason + - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure. + you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. + - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost. + this necessary change will break your rendering function! the fix should be very easy. sorry for that :( + - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest. + - the signature of the io.RenderDrawListsFn handler has changed! + ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) + became: + ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). + argument 'cmd_lists' -> 'draw_data->CmdLists' + argument 'cmd_lists_count' -> 'draw_data->CmdListsCount' + ImDrawList 'commands' -> 'CmdBuffer' + ImDrawList 'vtx_buffer' -> 'VtxBuffer' + ImDrawList n/a -> 'IdxBuffer' (new) + ImDrawCmd 'vtx_count' -> 'ElemCount' + ImDrawCmd 'clip_rect' -> 'ClipRect' + ImDrawCmd 'user_callback' -> 'UserCallback' + ImDrawCmd 'texture_id' -> 'TextureId' + - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer. + - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering! + - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! + - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. + - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). + - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount. + - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence + - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry! + - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). + - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). + - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. + - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened. + - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). + - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. + - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API + - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. + - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. + - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50. + - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing + - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. + - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) + - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. + - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. + - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. + - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior + - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() + - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) + - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions. + - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. + (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. + this sequence: + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + // + became: + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + // + io.Fonts->TexID = (your_texture_identifier); + you now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. + it is now recommended that you sample the font texture with bilinear interpolation. + (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. + (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) + (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets + - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) + - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) + - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility + - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() + - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) + - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) + - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() + - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn + - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) + - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite + - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes + + + ISSUES & TODO-LIST + ================== + See TODO.txt + + + FREQUENTLY ASKED QUESTIONS (FAQ), TIPS + ====================================== + + Q: How can I help? + A: - If you are experienced enough with Dear ImGui and with C/C++, look at the todo list and see how you want/can help! + - Become a Patron/donate! Convince your company to become a Patron or provide serious funding for development time! See http://www.patreon.com/imgui + + Q: What is ImTextureID and how do I display an image? + A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function. + Dear ImGui knows nothing about what those bits represent, it just passes them around. It is up to you to decide what you want the void* to carry! + It could be an identifier to your OpenGL texture (cast GLuint to void*), a pointer to your custom engine material (cast MyMaterial* to void*), etc. + At the end of the chain, your renderer takes this void* to cast it back into whatever it needs to select a current texture to render. + Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing. + (c++ tip: OpenGL uses integers to identify textures. You can safely store an integer into a void*, just cast it to void*, don't take it's address!) + To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions. + Dear ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use. + It is your responsibility to get textures uploaded to your GPU. + + Q: I integrated Dear ImGui in my engine and the text or lines are blurry.. + A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f). + Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension. + + Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. + A: You are probably mishandling the clipping rectangles in your render function. + Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height). + + Q: Can I have multiple widgets with the same label? Can I have widget without a label? + A: Yes. A primer on the use of labels/IDs in Dear ImGui.. + + - Elements that are not clickable, such as Text() items don't need an ID. + + - Interactive widgets require state to be carried over multiple frames (most typically Dear ImGui often needs to remember what is + the "active" widget). to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer. + + Button("OK"); // Label = "OK", ID = hash of "OK" + Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel" + + - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK" + in two different windows or in two different locations of a tree. + + - If you have a same ID twice in the same location, you'll have a conflict: + + Button("OK"); + Button("OK"); // ID collision! Both buttons will be treated as the same. + + Fear not! this is easy to solve and there are many ways to solve it! + + - When passing a label you can optionally specify extra unique ID information within string itself. + This helps solving the simpler collision cases. Use "##" to pass a complement to the ID that won't be visible to the end-user: + + Button("Play"); // Label = "Play", ID = hash of "Play" + Button("Play##foo1"); // Label = "Play", ID = hash of "Play##foo1" (different from above) + Button("Play##foo2"); // Label = "Play", ID = hash of "Play##foo2" (different from above) + + - If you want to completely hide the label, but still need an ID: + + Checkbox("##On", &b); // Label = "", ID = hash of "##On" (no label!) + + - Occasionally/rarely you might want change a label while preserving a constant ID. This allows you to animate labels. + For example you may want to include varying information in a window title bar (and windows are uniquely identified by their ID.. obviously) + Use "###" to pass a label that isn't part of ID: + + Button("Hello###ID"; // Label = "Hello", ID = hash of "ID" + Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above) + + sprintf(buf, "My game (%f FPS)###MyGame"); + Begin(buf); // Variable label, ID = hash of "MyGame" + + - Use PushID() / PopID() to create scopes and avoid ID conflicts within the same Window. + This is the most convenient way of distinguishing ID if you are iterating and creating many UI elements. + You can push a pointer, a string or an integer value. Remember that ID are formed from the concatenation of everything in the ID stack! + + for (int i = 0; i < 100; i++) + { + PushID(i); + Button("Click"); // Label = "Click", ID = hash of integer + "label" (unique) + PopID(); + } + + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj); + Button("Click"); // Label = "Click", ID = hash of pointer + "label" (unique) + PopID(); + } + + for (int i = 0; i < 100; i++) + { + MyObject* obj = Objects[i]; + PushID(obj->Name); + Button("Click"); // Label = "Click", ID = hash of string + "label" (unique) + PopID(); + } + + - More example showing that you can stack multiple prefixes into the ID stack: + + Button("Click"); // Label = "Click", ID = hash of "Click" + PushID("node"); + Button("Click"); // Label = "Click", ID = hash of "node" + "Click" + PushID(my_ptr); + Button("Click"); // Label = "Click", ID = hash of "node" + ptr + "Click" + PopID(); + PopID(); + + - Tree nodes implicitly creates a scope for you by calling PushID(). + + Button("Click"); // Label = "Click", ID = hash of "Click" + if (TreeNode("node")) + { + Button("Click"); // Label = "Click", ID = hash of "node" + "Click" + TreePop(); + } + + - When working with trees, ID are used to preserve the open/close state of each tree node. + Depending on your use cases you may want to use strings, indices or pointers as ID. + e.g. when displaying a single object that may change over time (dynamic 1-1 relationship), using a static string as ID will preserve your + node open/closed state when the targeted object change. + e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently. + experiment and see what makes more sense! + + Q: How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application? + A: You can read the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'ioWantTextInput' flags from the ImGuiIO structure. + - When 'io.WantCaptureMouse' or 'io.WantCaptureKeyboard' flags are set you may want to discard/hide the inputs from the rest of your application. + - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). + Preferably read the flags after calling ImGui::NewFrame() to avoid them lagging by one frame. But reading those flags before calling NewFrame() is + also generally ok, as the bool toggles fairly rarely and you don't generally expect to interact with either Dear ImGui or your application during + the same frame when that transition occurs. Dear ImGui is tracking dragging and widget activity that may occur outside the boundary of a window, + so 'io.WantCaptureMouse' is more accurate and correct than checking if a window is hovered. + (Advanced note: text input releases focus on Return 'KeyDown', so the following Return 'KeyUp' event that your application receive will typically + have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs + were for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.) + + Q: How can I load a different font than the default? (default is an embedded version of ProggyClean.ttf, rendered at size 13) + A: Use the font atlas to load the TTF/OTF file you want: + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + + Q: How can I easily use icons in my application? + A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you main font. Then you can refer to icons within your + strings. Read 'How can I load multiple fonts?' and the file 'extra_fonts/README.txt' for instructions and useful header files. + + Q: How can I load multiple fonts? + A: Use the font atlas to pack them into a single texture: + (Read extra_fonts/README.txt and the code in ImFontAtlas for more details.) + + ImGuiIO& io = ImGui::GetIO(); + ImFont* font0 = io.Fonts->AddFontDefault(); + ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels); + io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() + // the first loaded font gets used by default + // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime + + // Options + ImFontConfig config; + config.OversampleH = 3; + config.OversampleV = 1; + config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up + config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config); + + // Combine multiple fonts into one (e.g. for icon fonts) + ImWchar ranges[] = { 0xf000, 0xf3ff, 0 }; + ImFontConfig config; + config.MergeMode = true; + io.Fonts->AddFontDefault(); + io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs + + Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? + A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. + + // Add default Japanese ranges + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); + + // Or create your own custom ranges (e.g. for a game you can feed your entire game script and only build the characters the game need) + ImVector ranges; + ImFontAtlas::GlyphRangesBuilder builder; + builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters) + builder.AddChar(0x7262); // Add a specific character + builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges + builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted) + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data); + + All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8 by using the u8"hello" syntax. + Specifying literal in your source code using a local code page (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work! + Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8. + + Text input: it is up to your application to pass the right character code to io.AddInputCharacter(). The applications in examples/ are doing that. + For languages using IME, on Windows you can copy the Hwnd of your application to io.ImeWindowHandle. + The default implementation of io.ImeSetInputScreenPosFn() on Windows will set your IME position correctly. + + Q: How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables) + A: Create your own context 'ctx = CreateContext()' + 'SetCurrentContext(ctx)' and your own font atlas 'ctx->GetIO().Fonts = new ImFontAtlas()' + so you don't rely on the default globals. + + Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) + A: The easiest way is to create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag, + zero background alpha, then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. + You can also perfectly create a standalone ImDrawList instance _but_ you need ImGui to be initialized because ImDrawList pulls from ImGui + data to retrieve the coordinates of the white pixel. + + - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. + this is also useful to set yourself in the context of another window (to get/set other settings) + - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug". + - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle + of a deep nested inner loop in your code. + - tip: you can call Render() multiple times (e.g for VR renders). + - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui! + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include // toupper, isprint +#include // NULL, malloc, free, qsort, atoi +#include // vsnprintf, sscanf, printf +#include // INT_MIN, INT_MAX +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang warnings with -Weverything +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown warning group '-Wformat-pedantic *' // not all warnings are known by all clang versions.. so ignoring warnings triggers new warnings on some configuration. great! +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#pragma clang diagnostic ignored "-Wformat-pedantic" // warning : format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' // +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#endif + +//------------------------------------------------------------------------- +// Forward Declarations +//------------------------------------------------------------------------- + +static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); + +static ImFont* GetDefaultFont(); +static void SetCurrentFont(ImFont* font); +static void SetCurrentWindow(ImGuiWindow* window); +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond); +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond); +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond); +static ImGuiWindow* FindHoveredWindow(ImVec2 pos); +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags); +static void ClearSetNextWindowData(); +static void CheckStacksSize(ImGuiWindow* window, bool write); +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); + +static void AddDrawListToRenderList(ImVector& out_render_list, ImDrawList* draw_list); +static void AddWindowToRenderList(ImVector& out_render_list, ImGuiWindow* window); +static void AddWindowToSortedBuffer(ImVector& out_sorted_windows, ImGuiWindow* window); + +static ImGuiWindowSettings* AddWindowSettings(const char* name); + +static void LoadIniSettingsFromDisk(const char* ini_filename); +static void LoadIniSettingsFromMemory(const char* buf); +static void SaveIniSettingsToDisk(const char* ini_filename); +static void SaveIniSettingsToMemory(ImVector& out_buf); +static void MarkIniSettingsDirty(ImGuiWindow* window); + +static ImRect GetVisibleRect(); + +static void CloseInactivePopups(ImGuiWindow* ref_window); +static void ClosePopupToLevel(int remaining); +static ImGuiWindow* GetFrontMostModalRootWindow(); + +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size); +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size); +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2); +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format); + +namespace ImGui +{ +static void FocusPreviousWindow(); +} + +//----------------------------------------------------------------------------- +// Platform dependent default implementations +//----------------------------------------------------------------------------- + +static const char* GetClipboardTextFn_DefaultImpl(void* user_data); +static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +// Default font atlas storage. +// New contexts always point by default to this font atlas. It can be changed by reassigning the GetIO().Fonts variable. +static ImFontAtlas GImDefaultFontAtlas; + +// Default context storage + current context pointer. +// Implicitely used by all ImGui functions. Always assumed to be != NULL. Change to a different context by calling ImGui::SetCurrentContext() +// If you are hot-reloading this code in a DLL you will lose the static/global variables. Create your own context+font atlas instead of relying on those default (see FAQ entry "How can I preserve my ImGui context across reloading a DLL?"). +// ImGui is currently not thread-safe because of this variable. If you want thread-safety to allow N threads to access N different contexts, you might work around it by: +// - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts) +// - or: Changing this variable to be TLS. You may #define GImGui in imconfig.h for further custom hackery. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 +#ifndef GImGui +static ImGuiContext GImDefaultContext; +ImGuiContext* GImGui = &GImDefaultContext; +#endif + +//----------------------------------------------------------------------------- +// User facing structures +//----------------------------------------------------------------------------- + +ImGuiStyle::ImGuiStyle() +{ + Alpha = 1.0f; // Global alpha applies to everything in ImGui + WindowPadding = ImVec2(8,8); // Padding within a window + WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows + WindowBorderSize = 0.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32,32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns + ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. + DisplayWindowPadding = ImVec2(22,22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + DisplaySafeAreaPadding = ImVec2(4,4); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. + AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + + ImGui::StyleColorsClassic(this); +} + +// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. +// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. +void ImGuiStyle::ScaleAllSizes(float scale_factor) +{ + WindowPadding = ImFloor(WindowPadding * scale_factor); + WindowRounding = ImFloor(WindowRounding * scale_factor); + WindowMinSize = ImFloor(WindowMinSize * scale_factor); + ChildRounding = ImFloor(ChildRounding * scale_factor); + PopupRounding = ImFloor(PopupRounding * scale_factor); + FramePadding = ImFloor(FramePadding * scale_factor); + FrameRounding = ImFloor(FrameRounding * scale_factor); + ItemSpacing = ImFloor(ItemSpacing * scale_factor); + ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); + IndentSpacing = ImFloor(IndentSpacing * scale_factor); + ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); + ScrollbarSize = ImFloor(ScrollbarSize * scale_factor); + ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor); + GrabMinSize = ImFloor(GrabMinSize * scale_factor); + GrabRounding = ImFloor(GrabRounding * scale_factor); + DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); + DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); +} + +ImGuiIO::ImGuiIO() +{ + // Most fields are initialized with zero + memset(this, 0, sizeof(*this)); + + // Settings + DisplaySize = ImVec2(-1.0f, -1.0f); + DeltaTime = 1.0f/60.0f; + IniSavingRate = 5.0f; + IniFilename = "imgui.ini"; + LogFilename = "imgui_log.txt"; + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; + for (int i = 0; i < ImGuiKey_COUNT; i++) + KeyMap[i] = -1; + KeyRepeatDelay = 0.250f; + KeyRepeatRate = 0.050f; + UserData = NULL; + + Fonts = &GImDefaultFontAtlas; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f); + + // Advanced/subtle behaviors +#ifdef __APPLE__ + OptMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag +#else + OptMacOSXBehaviors = false; +#endif + OptCursorBlink = true; + + // Settings (User Functions) + RenderDrawListsFn = NULL; + MemAllocFn = malloc; + MemFreeFn = free; + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; + ClipboardUserData = NULL; + ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; + ImeWindowHandle = NULL; + + // Input (NB: we already have memset zero the entire structure) + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); + MouseDragThreshold = 6.0f; + for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f; +} + +// Pass in translated ASCII characters for text input. +// - with glfw you can get those from the callback set in glfwSetCharCallback() +// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message +void ImGuiIO::AddInputCharacter(ImWchar c) +{ + const int n = ImStrlenW(InputCharacters); + if (n + 1 < IM_ARRAYSIZE(InputCharacters)) + { + InputCharacters[n] = c; + InputCharacters[n+1] = '\0'; + } +} + +void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) +{ + // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more + const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar); + ImWchar wchars[wchars_buf_len]; + ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL); + for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++) + AddInputCharacter(wchars[i]); +} + +//----------------------------------------------------------------------------- +// HELPERS +//----------------------------------------------------------------------------- + +#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose +#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 + +// Play it nice with Windows users. Notepad in 2015 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) +{ + ImVec2 ap = p - a; + ImVec2 ab_dir = b - a; + float ab_len = sqrtf(ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y); + ab_dir *= 1.0f / ab_len; + float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; + if (dot < 0.0f) + return a; + if (dot > ab_len) + return b; + return a + ab_dir * dot; +} + +bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; + bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; + bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; + return ((b1 == b2) && (b2 == b3)); +} + +void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) +{ + ImVec2 v0 = b - a; + ImVec2 v1 = c - a; + ImVec2 v2 = p - a; + const float denom = v0.x * v1.y - v1.x * v0.y; + out_v = (v2.x * v1.y - v1.x * v2.y) / denom; + out_w = (v0.x * v2.y - v2.x * v0.y) / denom; + out_u = 1.0f - out_v - out_w; +} + +ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) +{ + ImVec2 proj_ab = ImLineClosestPoint(a, b, p); + ImVec2 proj_bc = ImLineClosestPoint(b, c, p); + ImVec2 proj_ca = ImLineClosestPoint(c, a, p); + float dist2_ab = ImLengthSqr(p - proj_ab); + float dist2_bc = ImLengthSqr(p - proj_bc); + float dist2_ca = ImLengthSqr(p - proj_ca); + float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)); + if (m == dist2_ab) + return proj_ab; + if (m == dist2_bc) + return proj_bc; + return proj_ca; +} + +int ImStricmp(const char* str1, const char* str2) +{ + int d; + while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } + return d; +} + +int ImStrnicmp(const char* str1, const char* str2, size_t count) +{ + int d = 0; + while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } + return d; +} + +void ImStrncpy(char* dst, const char* src, size_t count) +{ + if (count < 1) return; + strncpy(dst, src, count); + dst[count-1] = 0; +} + +char* ImStrdup(const char *str) +{ + size_t len = strlen(str) + 1; + void* buf = ImGui::MemAlloc(len); + return (char*)memcpy(buf, (const void*)str, len); +} + +char* ImStrchrRange(const char* str, const char* str_end, char c) +{ + for ( ; str < str_end; str++) + if (*str == c) + return (char*)str; + return NULL; +} + +int ImStrlenW(const ImWchar* str) +{ + int n = 0; + while (*str++) n++; + return n; +} + +const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + buf_mid_line--; + return buf_mid_line; +} + +const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) +{ + if (!needle_end) + needle_end = needle + strlen(needle); + + const char un0 = (char)toupper(*needle); + while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) + { + if (toupper(*haystack) == un0) + { + const char* b = needle + 1; + for (const char* a = haystack + 1; b < needle_end; a++, b++) + if (toupper(*a) != toupper(*b)) + break; + if (b == needle_end) + return haystack; + } + haystack++; + } + return NULL; +} + +static const char* ImAtoi(const char* src, int* output) +{ + int negative = 0; + if (*src == '-') { negative = 1; src++; } + if (*src == '+') { src++; } + int v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *output = negative ? -v : v; + return src; +} + +// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). +// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. +// B) When buf==NULL vsnprintf() will return the output size. +#ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS +int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + int w = vsnprintf(buf, buf_size, fmt, args); + va_end(args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} + +int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) +{ + int w = vsnprintf(buf, buf_size, fmt, args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} +#endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS + +// Pass data_size==0 for zero-terminated strings +// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. +ImU32 ImHash(const void* data, int data_size, ImU32 seed) +{ + static ImU32 crc32_lut[256] = { 0 }; + if (!crc32_lut[1]) + { + const ImU32 polynomial = 0xEDB88320; + for (ImU32 i = 0; i < 256; i++) + { + ImU32 crc = i; + for (ImU32 j = 0; j < 8; j++) + crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial); + crc32_lut[i] = crc; + } + } + + seed = ~seed; + ImU32 crc = seed; + const unsigned char* current = (const unsigned char*)data; + + if (data_size > 0) + { + // Known size + while (data_size--) + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++]; + } + else + { + // Zero-terminated string + while (unsigned char c = *current++) + { + // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. + // Because this syntax is rarely used we are optimizing for the common case. + // - If we reach ### in the string we discard the hash so far and reset to the seed. + // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller. + if (c == '#' && current[0] == '#' && current[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } + return ~crc; +} + +//----------------------------------------------------------------------------- +// ImText* helpers +//----------------------------------------------------------------------------- + +// Convert UTF-8 to 32-bits character, process single character input. +// Based on stb_from_utf8() from github.com/nothings/stb/ +// We handle UTF-8 decoding error by skipping forward. +int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) +{ + unsigned int c = (unsigned int)-1; + const unsigned char* str = (const unsigned char*)in_text; + if (!(*str & 0x80)) + { + c = (unsigned int)(*str++); + *out_char = c; + return 1; + } + if ((*str & 0xe0) == 0xc0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 2) return 1; + if (*str < 0xc2) return 2; + c = (unsigned int)((*str++ & 0x1f) << 6); + if ((*str & 0xc0) != 0x80) return 2; + c += (*str++ & 0x3f); + *out_char = c; + return 2; + } + if ((*str & 0xf0) == 0xe0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 3) return 1; + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3; + if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x0f) << 12); + if ((*str & 0xc0) != 0x80) return 3; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 3; + c += (*str++ & 0x3f); + *out_char = c; + return 3; + } + if ((*str & 0xf8) == 0xf0) + { + *out_char = 0xFFFD; // will be invalid but not end of string + if (in_text_end && in_text_end - (const char*)str < 4) return 1; + if (*str > 0xf4) return 4; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4; + if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below + c = (unsigned int)((*str++ & 0x07) << 18); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 12); + if ((*str & 0xc0) != 0x80) return 4; + c += (unsigned int)((*str++ & 0x3f) << 6); + if ((*str & 0xc0) != 0x80) return 4; + c += (*str++ & 0x3f); + // utf-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xFFFFF800) == 0xD800) return 4; + *out_char = c; + return 4; + } + *out_char = 0; + return 0; +} + +int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) +{ + ImWchar* buf_out = buf; + ImWchar* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes + *buf_out++ = (ImWchar)c; + } + *buf_out = 0; + if (in_text_remaining) + *in_text_remaining = in_text; + return (int)(buf_out - buf); +} + +int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) +{ + int char_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + if (c == 0) + break; + if (c < 0x10000) + char_count++; + } + return char_count; +} + +// Based on stb_to_utf8() from github.com/nothings/stb/ +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +{ + if (c < 0x80) + { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) + { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c ) & 0x3f)); + return 4; + } + //else if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c>> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c ) & 0x3f)); + return 3; + } +} + +static inline int ImTextCountUtf8BytesFromChar(unsigned int c) +{ + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c >= 0xdc00 && c < 0xe000) return 0; + if (c >= 0xd800 && c < 0xdc00) return 4; + return 3; +} + +int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) +{ + char* buf_out = buf; + const char* buf_end = buf + buf_size; + while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_out++ = (char)c; + else + buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c); + } + *buf_out = 0; + return (int)(buf_out - buf); +} + +int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) +{ + int bytes_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += ImTextCountUtf8BytesFromChar(c); + } + return bytes_count; +} + +ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) +{ + float s = 1.0f/255.0f; + return ImVec4( + ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); +} + +ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) +{ + ImU32 out; + out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; + return out; +} + +ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = style.Colors[idx]; + c.w *= style.Alpha * alpha_mul; + return ColorConvertFloat4ToU32(c); +} + +ImU32 ImGui::GetColorU32(const ImVec4& col) +{ + ImGuiStyle& style = GImGui->Style; + ImVec4 c = col; + c.w *= style.Alpha; + return ColorConvertFloat4ToU32(c); +} + +const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) +{ + ImGuiStyle& style = GImGui->Style; + return style.Colors[idx]; +} + +ImU32 ImGui::GetColorU32(ImU32 col) +{ + float style_alpha = GImGui->Style.Alpha; + if (style_alpha >= 1.0f) + return col; + int a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (int)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + +// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 +// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv +void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) +{ + float K = 0.f; + if (g < b) + { + ImSwap(g, b); + K = -1.f; + } + if (r < g) + { + ImSwap(r, g); + K = -2.f / 6.f - K; + } + + const float chroma = r - (g < b ? g : b); + out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f)); + out_s = chroma / (r + 1e-20f); + out_v = r; +} + +// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 +// also http://en.wikipedia.org/wiki/HSL_and_HSV +void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) +{ + if (s == 0.0f) + { + // gray + out_r = out_g = out_b = v; + return; + } + + h = fmodf(h, 1.0f) / (60.0f/360.0f); + int i = (int)h; + float f = h - (float)i; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch (i) + { + case 0: out_r = v; out_g = t; out_b = p; break; + case 1: out_r = q; out_g = v; out_b = p; break; + case 2: out_r = p; out_g = v; out_b = t; break; + case 3: out_r = p; out_g = q; out_b = v; break; + case 4: out_r = t; out_g = p; out_b = v; break; + case 5: default: out_r = v; out_g = p; out_b = q; break; + } +} + +FILE* ImFileOpen(const char* filename, const char* mode) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can) + const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1; + const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1; + ImVector buf; + buf.resize(filename_wsize + mode_wsize); + ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL); + ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL); + return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]); +#else + return fopen(filename, mode); +#endif +} + +// Load file content into memory +// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree() +void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes) +{ + IM_ASSERT(filename && file_open_mode); + if (out_file_size) + *out_file_size = 0; + + FILE* f; + if ((f = ImFileOpen(filename, file_open_mode)) == NULL) + return NULL; + + long file_size_signed; + if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) + { + fclose(f); + return NULL; + } + + int file_size = (int)file_size_signed; + void* file_data = ImGui::MemAlloc(file_size + padding_bytes); + if (file_data == NULL) + { + fclose(f); + return NULL; + } + if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size) + { + fclose(f); + ImGui::MemFree(file_data); + return NULL; + } + if (padding_bytes > 0) + memset((void *)(((char*)file_data) + file_size), 0, padding_bytes); + + fclose(f); + if (out_file_size) + *out_file_size = file_size; + + return file_data; +} + +//----------------------------------------------------------------------------- +// ImGuiStorage +// Helper: Key->value storage +//----------------------------------------------------------------------------- + +// std::lower_bound but without the bullshit +static ImVector::iterator LowerBound(ImVector& data, ImGuiID key) +{ + ImVector::iterator first = data.begin(); + ImVector::iterator last = data.end(); + size_t count = (size_t)(last - first); + while (count > 0) + { + size_t count2 = count >> 1; + ImVector::iterator mid = first + count2; + if (mid->key < key) + { + first = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return first; +} + +// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. +void ImGuiStorage::BuildSortByKey() +{ + struct StaticFunc + { + static int PairCompareByID(const void* lhs, const void* rhs) + { + // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. + if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1; + if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1; + return 0; + } + }; + if (Data.Size > 1) + qsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID); +} + +int ImGuiStorage::GetInt(ImGuiID key, int default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_i; +} + +bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const +{ + return GetInt(key, default_val ? 1 : 0) != 0; +} + +float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_f; +} + +void* ImGuiStorage::GetVoidPtr(ImGuiID key) const +{ + ImVector::iterator it = LowerBound(const_cast&>(Data), key); + if (it == Data.end() || it->key != key) + return NULL; + return it->val_p; +} + +// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. +int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_i; +} + +bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) +{ + return (bool*)GetIntRef(key, default_val ? 1 : 0); +} + +float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_f; +} + +void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, Pair(key, default_val)); + return &it->val_p; +} + +// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) +void ImGuiStorage::SetInt(ImGuiID key, int val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_i = val; +} + +void ImGuiStorage::SetBool(ImGuiID key, bool val) +{ + SetInt(key, val ? 1 : 0); +} + +void ImGuiStorage::SetFloat(ImGuiID key, float val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_f = val; +} + +void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) +{ + ImVector::iterator it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + { + Data.insert(it, Pair(key, val)); + return; + } + it->val_p = val; +} + +void ImGuiStorage::SetAllInt(int v) +{ + for (int i = 0; i < Data.Size; i++) + Data[i].val_i = v; +} + +//----------------------------------------------------------------------------- +// ImGuiTextFilter +//----------------------------------------------------------------------------- + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) +{ + if (default_filter) + { + ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); + Build(); + } + else + { + InputBuf[0] = 0; + CountGrep = 0; + } +} + +bool ImGuiTextFilter::Draw(const char* label, float width) +{ + if (width != 0.0f) + ImGui::PushItemWidth(width); + bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); + if (width != 0.0f) + ImGui::PopItemWidth(); + if (value_changed) + Build(); + return value_changed; +} + +void ImGuiTextFilter::TextRange::split(char separator, ImVector& out) +{ + out.resize(0); + const char* wb = b; + const char* we = wb; + while (we < e) + { + if (*we == separator) + { + out.push_back(TextRange(wb, we)); + wb = we + 1; + } + we++; + } + if (wb != we) + out.push_back(TextRange(wb, we)); +} + +void ImGuiTextFilter::Build() +{ + Filters.resize(0); + TextRange input_range(InputBuf, InputBuf+strlen(InputBuf)); + input_range.split(',', Filters); + + CountGrep = 0; + for (int i = 0; i != Filters.Size; i++) + { + Filters[i].trim_blanks(); + if (Filters[i].empty()) + continue; + if (Filters[i].front() != '-') + CountGrep += 1; + } +} + +bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const +{ + if (Filters.empty()) + return true; + + if (text == NULL) + text = ""; + + for (int i = 0; i != Filters.Size; i++) + { + const TextRange& f = Filters[i]; + if (f.empty()) + continue; + if (f.front() == '-') + { + // Subtract + if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL) + return false; + } + else + { + // Grep + if (ImStristr(text, text_end, f.begin(), f.end()) != NULL) + return true; + } + } + + // Implicit * grep + if (CountGrep == 0) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// ImGuiTextBuffer +//----------------------------------------------------------------------------- + +// On some platform vsnprintf() takes va_list by reference and modifies it. +// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. +#ifndef va_copy +#define va_copy(dest, src) (dest = src) +#endif + +// Helper: Text buffer for logging/accumulating text +void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + + int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. + if (len <= 0) + return; + + const int write_off = Buf.Size; + const int needed_sz = write_off + len; + if (write_off + len >= Buf.Capacity) + { + int double_capacity = Buf.Capacity * 2; + Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity); + } + + Buf.resize(needed_sz); + ImFormatStringV(&Buf[write_off - 1], len + 1, fmt, args_copy); +} + +void ImGuiTextBuffer::appendf(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + appendfv(fmt, args); + va_end(args); +} + +//----------------------------------------------------------------------------- +// ImGuiSimpleColumns (internal use only) +//----------------------------------------------------------------------------- + +ImGuiSimpleColumns::ImGuiSimpleColumns() +{ + Count = 0; + Spacing = Width = NextWidth = 0.0f; + memset(Pos, 0, sizeof(Pos)); + memset(NextWidths, 0, sizeof(NextWidths)); +} + +void ImGuiSimpleColumns::Update(int count, float spacing, bool clear) +{ + IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); + Count = count; + Width = NextWidth = 0.0f; + Spacing = spacing; + if (clear) memset(NextWidths, 0, sizeof(NextWidths)); + for (int i = 0; i < Count; i++) + { + if (i > 0 && NextWidths[i] > 0.0f) + Width += Spacing; + Pos[i] = (float)(int)Width; + Width += NextWidths[i]; + NextWidths[i] = 0.0f; + } +} + +float ImGuiSimpleColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double +{ + NextWidth = 0.0f; + NextWidths[0] = ImMax(NextWidths[0], w0); + NextWidths[1] = ImMax(NextWidths[1], w1); + NextWidths[2] = ImMax(NextWidths[2], w2); + for (int i = 0; i < 3; i++) + NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); + return ImMax(Width, NextWidth); +} + +float ImGuiSimpleColumns::CalcExtraSpace(float avail_w) +{ + return ImMax(0.0f, avail_w - Width); +} + +//----------------------------------------------------------------------------- +// ImGuiListClipper +//----------------------------------------------------------------------------- + +static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) +{ + // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor. + // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. Consider moving within SetCursorXXX functions? + ImGui::SetCursorPosY(pos_y); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHere() can properly function after the end of our clipper usage. + window->DC.PrevLineHeight = (line_height - GImGui->Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. + if (window->DC.ColumnsSet) + window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly +} + +// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1 +// Use case B: Begin() called from constructor with items_height>0 +// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. +void ImGuiListClipper::Begin(int count, float items_height) +{ + StartPosY = ImGui::GetCursorPosY(); + ItemsHeight = items_height; + ItemsCount = count; + StepNo = 0; + DisplayEnd = DisplayStart = -1; + if (ItemsHeight > 0.0f) + { + ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display + if (DisplayStart > 0) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor + StepNo = 2; + } +} + +void ImGuiListClipper::End() +{ + if (ItemsCount < 0) + return; + // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. + if (ItemsCount < INT_MAX) + SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor + ItemsCount = -1; + StepNo = 3; +} + +bool ImGuiListClipper::Step() +{ + if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems) + { + ItemsCount = -1; + return false; + } + if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height. + { + DisplayStart = 0; + DisplayEnd = 1; + StartPosY = ImGui::GetCursorPosY(); + StepNo = 1; + return true; + } + if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. + { + if (ItemsCount == 1) { ItemsCount = -1; return false; } + float items_height = ImGui::GetCursorPosY() - StartPosY; + IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically + Begin(ItemsCount-1, items_height); + DisplayStart++; + DisplayEnd++; + StepNo = 3; + return true; + } + if (StepNo == 2) // Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3. + { + IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0); + StepNo = 3; + return true; + } + if (StepNo == 3) // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. + End(); + return false; +} + +//----------------------------------------------------------------------------- +// ImGuiWindow +//----------------------------------------------------------------------------- + +ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) +{ + Name = ImStrdup(name); + ID = ImHash(name, 0); + IDStack.push_back(ID); + Flags = 0; + PosFloat = Pos = ImVec2(0.0f, 0.0f); + Size = SizeFull = ImVec2(0.0f, 0.0f); + SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); + WindowPadding = ImVec2(0.0f, 0.0f); + WindowRounding = 0.0f; + WindowBorderSize = 0.0f; + MoveId = GetID("#MOVE"); + Scroll = ImVec2(0.0f, 0.0f); + ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); + ScrollbarX = ScrollbarY = false; + ScrollbarSizes = ImVec2(0.0f, 0.0f); + Active = WasActive = false; + WriteAccessed = false; + Collapsed = false; + SkipItems = false; + Appearing = false; + CloseButton = false; + BeginOrderWithinParent = -1; + BeginOrderWithinContext = -1; + BeginCount = 0; + PopupId = 0; + AutoFitFramesX = AutoFitFramesY = -1; + AutoFitOnlyGrows = false; + AutoFitChildAxises = 0x00; + AutoPosLastDirection = ImGuiDir_None; + HiddenFrames = 0; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); + + LastFrameActive = -1; + ItemWidthDefault = 0.0f; + FontWindowScale = 1.0f; + + DrawList = IM_NEW(ImDrawList)(&context->DrawListSharedData); + DrawList->_OwnerName = Name; + ParentWindow = NULL; + RootWindow = NULL; + RootNonPopupWindow = NULL; + + FocusIdxAllCounter = FocusIdxTabCounter = -1; + FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; + FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX; +} + +ImGuiWindow::~ImGuiWindow() +{ + IM_DELETE(DrawList); + IM_DELETE(Name); +} + +ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetID(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHash(&ptr, sizeof(void*), seed); + ImGui::KeepAliveID(id); + return id; +} + +ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + return ImHash(str, str_end ? (int)(str_end - str) : 0, seed); +} + +// This is only used in rare/specific situations to manufacture an ID out of nowhere. +ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) +{ + ImGuiID seed = IDStack.back(); + const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) }; + ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed); + ImGui::KeepAliveID(id); + return id; +} + +//----------------------------------------------------------------------------- +// Internal API exposed in imgui_internal.h +//----------------------------------------------------------------------------- + +static void SetCurrentWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow = window; + if (window) + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + g.ActiveIdIsJustActivated = (g.ActiveId != id); + if (g.ActiveIdIsJustActivated) + g.ActiveIdTimer = 0.0f; + g.ActiveId = id; + g.ActiveIdAllowOverlap = false; + g.ActiveIdIsAlive |= (id != 0); + g.ActiveIdWindow = window; +} + +void ImGui::ClearActiveID() +{ + SetActiveID(0, NULL); +} + +void ImGui::SetHoveredID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.HoveredId = id; + g.HoveredIdAllowOverlap = false; + g.HoveredIdTimer = (id != 0 && g.HoveredIdPreviousFrame == id) ? (g.HoveredIdTimer + g.IO.DeltaTime) : 0.0f; +} + +void ImGui::KeepAliveID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = true; +} + +static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) +{ + // An active popup disable hovering on other windows (apart from its own children) + // FIXME-OPT: This could be cached/stored within the window. + ImGuiContext& g = *GImGui; + if (g.NavWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The order of those two tests is important because Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } + + return true; +} + +// Advance cursor given item size for layout. +void ImGui::ItemSize(const ImVec2& size, float text_offset_y) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // Always align ourselves on pixel boundaries + const float line_height = ImMax(window->DC.CurrentLineHeight, size.y); + const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y); + //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] + window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y); + window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y)); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); + //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + + window->DC.PrevLineHeight = line_height; + window->DC.PrevLineTextBaseOffset = text_base_offset; + window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f; + + // Horizontal layout mode + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + SameLine(); +} + +void ImGui::ItemSize(const ImRect& bb, float text_offset_y) +{ + ItemSize(bb.GetSize(), text_offset_y); +} + +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declares their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const bool is_clipped = IsClippedEx(bb, id, false); + window->DC.LastItemId = id; + window->DC.LastItemRect = bb; + window->DC.LastItemRectHoveredRect = false; + if (is_clipped) + return false; + //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + window->DC.LastItemRectHoveredRect = IsMouseHoveringRect(bb.Min, bb.Max); + return true; +} + +// This is roughly matching the behavior of internal-facing ItemHoverable() +// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() +// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Test for bounding box overlap, as updated as ItemAdd() + if (!window->DC.LastItemRectHoveredRect) + return false; + IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function + + // Test if we are hovering the right window (our window could be behind another window) + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. + // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. + //if (g.HoveredWindow != window) + // return false; + if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) + return false; + + // Test if another item is active (e.g. being dragged) + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + return false; + + // Test if interactions on this window are blocked by an active popup or modal + if (!IsWindowContentHoverable(window, flags)) + return false; + + // Test if the item is disabled + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case. + if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) + return false; + return true; +} + +// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow != window) + return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + return false; + if (!IsMouseHoveringRect(bb.Min, bb.Max)) + return false; + if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_Default)) + return false; + if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) + return false; + + SetHoveredID(id); + return true; +} + +bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (!bb.Overlaps(window->ClipRect)) + if (id == 0 || id != g.ActiveId) + if (clip_even_when_logged || !g.LogEnabled) + return true; + return false; +} + +bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop) +{ + ImGuiContext& g = *GImGui; + + const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus; + window->FocusIdxAllCounter++; + if (allow_keyboard_focus) + window->FocusIdxTabCounter++; + + // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item. + // Note that we can always TAB out of a widget that doesn't allow tabbing in. + if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)) + window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. + + if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent) + return true; + + if (allow_keyboard_focus) + if (window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent) + return true; + + return false; +} + +void ImGui::FocusableItemUnregister(ImGuiWindow* window) +{ + window->FocusIdxAllCounter--; + window->FocusIdxTabCounter--; +} + +ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y) +{ + ImGuiContext& g = *GImGui; + ImVec2 content_max; + if (size.x < 0.0f || size.y < 0.0f) + content_max = g.CurrentWindow->Pos + GetContentRegionMax(); + if (size.x <= 0.0f) + size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x; + if (size.y <= 0.0f) + size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y; + return size; +} + +float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) +{ + if (wrap_pos_x < 0.0f) + return 0.0f; + + ImGuiWindow* window = GetCurrentWindowRead(); + if (wrap_pos_x == 0.0f) + wrap_pos_x = GetContentRegionMax().x + window->Pos.x; + else if (wrap_pos_x > 0.0f) + wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space + + return ImMax(wrap_pos_x - pos.x, 1.0f); +} + +//----------------------------------------------------------------------------- + +void* ImGui::MemAlloc(size_t sz) +{ + GImGui->IO.MetricsAllocs++; + return GImGui->IO.MemAllocFn(sz); +} + +void ImGui::MemFree(void* ptr) +{ + if (ptr) GImGui->IO.MetricsAllocs--; + return GImGui->IO.MemFreeFn(ptr); +} + +const char* ImGui::GetClipboardText() +{ + return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : ""; +} + +void ImGui::SetClipboardText(const char* text) +{ + if (GImGui->IO.SetClipboardTextFn) + GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text); +} + +const char* ImGui::GetVersion() +{ + return IMGUI_VERSION; +} + +// Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself +// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module +ImGuiContext* ImGui::GetCurrentContext() +{ + return GImGui; +} + +void ImGui::SetCurrentContext(ImGuiContext* ctx) +{ +#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC + IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. +#else + GImGui = ctx; +#endif +} + +ImGuiContext* ImGui::CreateContext(void* (*malloc_fn)(size_t), void (*free_fn)(void*)) +{ + if (!malloc_fn) malloc_fn = malloc; + ImGuiContext* ctx = (ImGuiContext*)malloc_fn(sizeof(ImGuiContext)); + IM_PLACEMENT_NEW(ctx) ImGuiContext(); + ctx->IO.MemAllocFn = malloc_fn; + ctx->IO.MemFreeFn = free_fn ? free_fn : free; + return ctx; +} + +void ImGui::DestroyContext(ImGuiContext* ctx) +{ + void (*free_fn)(void*) = ctx->IO.MemFreeFn; + ctx->~ImGuiContext(); + free_fn(ctx); + if (GImGui == ctx) + SetCurrentContext(NULL); +} + +ImGuiIO& ImGui::GetIO() +{ + return GImGui->IO; +} + +ImGuiStyle& ImGui::GetStyle() +{ + return GImGui->Style; +} + +// Same value as passed to your RenderDrawListsFn() function. valid after Render() and until the next call to NewFrame() +ImDrawData* ImGui::GetDrawData() +{ + return GImGui->RenderDrawData.Valid ? &GImGui->RenderDrawData : NULL; +} + +float ImGui::GetTime() +{ + return GImGui->Time; +} + +int ImGui::GetFrameCount() +{ + return GImGui->FrameCount; +} + +ImDrawList* ImGui::GetOverlayDrawList() +{ + return &GImGui->OverlayDrawList; +} + +ImDrawListSharedData* ImGui::GetDrawListSharedData() +{ + return &GImGui->DrawListSharedData; +} + +void ImGui::NewFrame() +{ + ImGuiContext& g = *GImGui; + + // Check user data + // (We pass an error message in the assert expression as a trick to get it visible to programmers who are not using a debugger, as most assert handlers display their argument) + IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)"); + IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value"); + IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?"); + IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?"); + IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting"); + IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations)"); + IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); + + // Initialize on first frame + if (!g.Initialized) + Initialize(); + + SetCurrentFont(GetDefaultFont()); + IM_ASSERT(g.Font->IsLoaded()); + g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + + g.Time += g.IO.DeltaTime; + g.FrameCount += 1; + g.TooltipOverrideCount = 0; + g.WindowsActiveCount = 0; + g.OverlayDrawList.Clear(); + g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID); + g.OverlayDrawList.PushClipRectFullScreen(); + g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + + // Mark rendering data as invalid to prevent user who may have a handle on it to use it + g.RenderDrawData.Valid = false; + g.RenderDrawData.CmdLists = NULL; + g.RenderDrawData.CmdListsCount = g.RenderDrawData.TotalVtxCount = g.RenderDrawData.TotalIdxCount = 0; + + // Clear reference to active widget if the widget isn't alive anymore + if (!g.HoveredIdPreviousFrame) + g.HoveredIdTimer = 0.0f; + g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) + ClearActiveID(); + if (g.ActiveId) + g.ActiveIdTimer += g.IO.DeltaTime; + g.ActiveIdPreviousFrame = g.ActiveId; + g.ActiveIdIsAlive = false; + g.ActiveIdIsJustActivated = false; + if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) + g.ScalarAsInputTextId = 0; + + // Elapse drag & drop payload + if (g.DragDropActive && g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) + { + ClearDragDrop(); + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + } + g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; + g.DragDropAcceptIdCurr = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + + // Update keyboard input state + memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) + g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Update mouse input state + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX component, but in reality we test for -256000.0f) we cancel out movement in MouseDelta + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) + g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; + else + g.IO.MouseDelta = ImVec2(0.0f, 0.0f); + g.IO.MousePosPrev = g.IO.MousePos; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; + g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; + g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; + g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; + g.IO.MouseDoubleClicked[i] = false; + if (g.IO.MouseClicked[i]) + { + if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime) + { + if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) + g.IO.MouseDoubleClicked[i] = true; + g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click + } + else + { + g.IO.MouseClickedTime[i] = g.Time; + } + g.IO.MouseClickedPos[i] = g.IO.MousePos; + g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); + g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; + } + else if (g.IO.MouseDown[i]) + { + ImVec2 mouse_delta = g.IO.MousePos - g.IO.MouseClickedPos[i]; + g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, mouse_delta.x < 0.0f ? -mouse_delta.x : mouse_delta.x); + g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, mouse_delta.y < 0.0f ? -mouse_delta.y : mouse_delta.y); + g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(mouse_delta)); + } + } + + // Calculate frame-rate for the user, as a purely luxurious feature + g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; + g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; + g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); + g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame)); + + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering). Only valid for root windows. + if (g.MovingWindowMoveId && g.MovingWindowMoveId == g.ActiveId) + { + KeepAliveID(g.MovingWindowMoveId); + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); + IM_ASSERT(g.MovingWindow->MoveId == g.MovingWindowMoveId); + if (g.IO.MouseDown[0]) + { + ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; + if (g.MovingWindow->RootWindow->PosFloat.x != pos.x || g.MovingWindow->RootWindow->PosFloat.y != pos.y) + MarkIniSettingsDirty(g.MovingWindow->RootWindow); + g.MovingWindow->RootWindow->PosFloat = pos; + FocusWindow(g.MovingWindow); + } + else + { + ClearActiveID(); + g.MovingWindow = NULL; + g.MovingWindowMoveId = 0; + } + } + else + { + g.MovingWindow = NULL; + g.MovingWindowMoveId = 0; + } + + // Delay saving settings so we don't spam disk too much + if (g.SettingsDirtyTimer > 0.0f) + { + g.SettingsDirtyTimer -= g.IO.DeltaTime; + if (g.SettingsDirtyTimer <= 0.0f) + SaveIniSettingsToDisk(g.IO.IniFilename); + } + + // Find the window we are hovering + // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. + // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point. + // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. + g.HoveredWindow = (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) ? g.MovingWindow : FindHoveredWindow(g.IO.MousePos); + g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; + + ImGuiWindow* modal_window = GetFrontMostModalRootWindow(); + if (modal_window != NULL) + { + g.ModalWindowDarkeningRatio = ImMin(g.ModalWindowDarkeningRatio + g.IO.DeltaTime * 6.0f, 1.0f); + if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window)) + g.HoveredRootWindow = g.HoveredWindow = NULL; + } + else + { + g.ModalWindowDarkeningRatio = 0.0f; + } + + // Update the WantCaptureMouse/WantCAptureKeyboard flags, so user can capture/discard the inputs away from the rest of their application. + // When clicking outside of a window we assume the click is owned by the application and won't request capture. We need to track click ownership. + int mouse_earliest_button_down = -1; + bool mouse_any_down = false; + for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) + { + if (g.IO.MouseClicked[i]) + g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); + mouse_any_down |= g.IO.MouseDown[i]; + if (g.IO.MouseDown[i]) + if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) + mouse_earliest_button_down = i; + } + bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; + if (g.WantCaptureMouseNextFrame != -1) + g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); + else + g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty()); + if (g.WantCaptureKeyboardNextFrame != -1) + g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); + else + g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); + g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : 0; + g.MouseCursor = ImGuiMouseCursor_Arrow; + g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; + g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default + + // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. + // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) + bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; + if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload) + g.HoveredWindow = g.HoveredRootWindow = NULL; + + // Scale & Scrolling + if (g.HoveredWindow && g.IO.MouseWheel != 0.0f && !g.HoveredWindow->Collapsed) + { + ImGuiWindow* window = g.HoveredWindow; + if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling) + { + // Zoom / Scale window + const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); + const float scale = new_font_scale / window->FontWindowScale; + window->FontWindowScale = new_font_scale; + + const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; + window->Pos += offset; + window->PosFloat += offset; + window->Size *= scale; + window->SizeFull *= scale; + } + else if (!g.IO.KeyCtrl) + { + // Mouse wheel Scrolling + // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent (unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set). + ImGuiWindow* scroll_window = window; + while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow) + scroll_window = scroll_window->ParentWindow; + + if (!(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs)) + { + float scroll_amount = 5 * scroll_window->CalcFontSize(); + scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f); + SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount); + } + } + } + + // Pressing TAB activate widget focus + if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && IsKeyPressedMap(ImGuiKey_Tab, false)) + g.NavWindow->FocusIdxTabRequestNext = 0; + + // Mark all windows as not visible + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + window->WasActive = window->Active; + window->Active = false; + window->WriteAccessed = false; + } + + // Closing the focused window restore focus to the first active root window in descending z-order + if (g.NavWindow && !g.NavWindow->WasActive) + FocusPreviousWindow(); + + // No window should be open at the beginning of the frame. + // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. + g.CurrentWindowStack.resize(0); + g.CurrentPopupStack.resize(0); + CloseInactivePopups(g.NavWindow); + + // Create implicit window - we will only render it if the user has added something to it. + // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. + SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver); + Begin("Debug##Default"); +} + +static void* SettingsHandlerWindow_ReadOpen(ImGuiContext&, const char* name) +{ + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0)); + if (!settings) + settings = AddWindowSettings(name); + return (void*)settings; +} + +static void SettingsHandlerWindow_ReadLine(ImGuiContext&, void* entry, const char* line) +{ + ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; + float x, y; + int i; + if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y); + else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); + else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0); +} + +static void SettingsHandlerWindow_WriteAll(ImGuiContext& g, ImGuiTextBuffer* buf) +{ + // Gather data from windows that were active during this session + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) + continue; + ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID); + if (!settings) + settings = AddWindowSettings(window->Name); + settings->Pos = window->Pos; + settings->Size = window->SizeFull; + settings->Collapsed = window->Collapsed; + } + + // Write a buffer + // If a window wasn't opened in this session we preserve its settings + buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve + for (int i = 0; i != g.SettingsWindows.Size; i++) + { + const ImGuiWindowSettings* settings = &g.SettingsWindows[i]; + if (settings->Pos.x == FLT_MAX) + continue; + const char* name = settings->Name; + if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() + name = p; + buf->appendf("[Window][%s]\n", name); + buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); + buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + buf->appendf("\n"); + } +} + +void ImGui::Initialize() +{ + ImGuiContext& g = *GImGui; + g.LogClipboard = IM_NEW(ImGuiTextBuffer)(); + + // Add .ini handle for ImGuiWindow type + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHash("Window", 0, 0); + ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen; + ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; + ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; + g.SettingsHandlers.push_front(ini_handler); + + // Load .ini file + IM_ASSERT(g.SettingsWindows.empty()); + LoadIniSettingsFromDisk(g.IO.IniFilename); + g.Initialized = true; +} + +// This function is merely here to free heap allocations. +void ImGui::Shutdown() +{ + ImGuiContext& g = *GImGui; + + // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) + if (g.IO.Fonts) // Testing for NULL to allow user to NULLify in case of running Shutdown() on multiple contexts. Bit hacky. + g.IO.Fonts->Clear(); + + // Cleanup of other data are conditional on actually having initialize ImGui. + if (!g.Initialized) + return; + + SaveIniSettingsToDisk(g.IO.IniFilename); + + for (int i = 0; i < g.Windows.Size; i++) + IM_DELETE(g.Windows[i]); + g.Windows.clear(); + g.WindowsSortBuffer.clear(); + g.CurrentWindow = NULL; + g.CurrentWindowStack.clear(); + g.WindowsById.Clear(); + g.NavWindow = NULL; + g.HoveredWindow = NULL; + g.HoveredRootWindow = NULL; + g.ActiveIdWindow = NULL; + g.MovingWindow = NULL; + for (int i = 0; i < g.SettingsWindows.Size; i++) + IM_DELETE(g.SettingsWindows[i].Name); + g.ColorModifiers.clear(); + g.StyleModifiers.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.CurrentPopupStack.clear(); + g.SetNextWindowSizeConstraintCallback = NULL; + g.SetNextWindowSizeConstraintCallbackUserData = NULL; + for (int i = 0; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) + g.RenderDrawLists[i].clear(); + g.OverlayDrawList.ClearFreeMemory(); + g.PrivateClipboard.clear(); + g.InputTextState.Text.clear(); + g.InputTextState.InitialText.clear(); + g.InputTextState.TempTextBuffer.clear(); + + g.SettingsWindows.clear(); + g.SettingsHandlers.clear(); + + if (g.LogFile && g.LogFile != stdout) + { + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard) + IM_DELETE(g.LogClipboard); + + g.Initialized = false; +} + +ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (int i = 0; i != g.SettingsWindows.Size; i++) + if (g.SettingsWindows[i].Id == id) + return &g.SettingsWindows[i]; + return NULL; +} + +static ImGuiWindowSettings* AddWindowSettings(const char* name) +{ + ImGuiContext& g = *GImGui; + g.SettingsWindows.push_back(ImGuiWindowSettings()); + ImGuiWindowSettings* settings = &g.SettingsWindows.back(); + settings->Name = ImStrdup(name); + settings->Id = ImHash(name, 0); + return settings; +} + +static void LoadIniSettingsFromDisk(const char* ini_filename) +{ + if (!ini_filename) + return; + char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", NULL, +1); + if (!file_data) + return; + LoadIniSettingsFromMemory(file_data); + ImGui::MemFree(file_data); +} + +ImGuiSettingsHandler* ImGui::FindSettingsHandler(ImGuiID type_hash) +{ + ImGuiContext& g = *GImGui; + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + return &g.SettingsHandlers[handler_n]; + return NULL; +} + +// Zero-tolerance, no error reporting, cheap .ini parsing +static void LoadIniSettingsFromMemory(const char* buf_readonly) +{ + // For convenience and to make the code simpler, we'll write zero terminators inside the buffer. So let's create a writable copy. + char* buf = ImStrdup(buf_readonly); + char* buf_end = buf + strlen(buf); + + ImGuiContext& g = *GImGui; + void* entry_data = NULL; + const ImGuiSettingsHandler* entry_handler = NULL; + + char* line_end = NULL; + for (char* line = buf; line < buf_end; line = line_end + 1) + { + // Skip new lines markers, then find end of the line + while (*line == '\n' || *line == '\r') + line++; + line_end = line; + while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') + line_end++; + line_end[0] = 0; + + if (line[0] == '[' && line_end > line && line_end[-1] == ']') + { + // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. + line_end[-1] = 0; + const char* name_end = line_end - 1; + const char* type_start = line + 1; + char* type_end = ImStrchrRange(type_start, name_end, ']'); + const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + if (!type_end || !name_start) + { + name_start = type_start; // Import legacy entries that have no type + type_start = "Window"; + } + else + { + *type_end = 0; // Overwrite first ']' + name_start++; // Skip second '[' + } + const ImGuiID type_hash = ImHash(type_start, 0, 0); + entry_handler = ImGui::FindSettingsHandler(type_hash); + entry_data = entry_handler ? entry_handler->ReadOpenFn(g, name_start) : NULL; + } + else if (entry_handler != NULL && entry_data != NULL) + { + // Let type handler parse the line + entry_handler->ReadLineFn(g, entry_data, line); + } + } + ImGui::MemFree(buf); +} + +static void SaveIniSettingsToDisk(const char* ini_filename) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + if (!ini_filename) + return; + + ImVector buf; + SaveIniSettingsToMemory(buf); + + FILE* f = ImFileOpen(ini_filename, "wt"); + if (!f) + return; + fwrite(buf.Data, sizeof(char), (size_t)buf.Size, f); + fclose(f); +} + +static void SaveIniSettingsToMemory(ImVector& out_buf) +{ + ImGuiContext& g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + + ImGuiTextBuffer buf; + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + g.SettingsHandlers[handler_n].WriteAllFn(g, &buf); + + buf.Buf.pop_back(); // Remove extra zero-terminator used by ImGuiTextBuffer + out_buf.swap(buf.Buf); +} + +void ImGui::MarkIniSettingsDirty() +{ + ImGuiContext& g = *GImGui; + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +static void MarkIniSettingsDirty(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +// FIXME: Add a more explicit sort order in the window structure. +static int ChildWindowComparer(const void* lhs, const void* rhs) +{ + const ImGuiWindow* a = *(const ImGuiWindow**)lhs; + const ImGuiWindow* b = *(const ImGuiWindow**)rhs; + if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) + return d; + if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) + return d; + return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); +} + +static void AddWindowToSortedBuffer(ImVector& out_sorted_windows, ImGuiWindow* window) +{ + out_sorted_windows.push_back(window); + if (window->Active) + { + int count = window->DC.ChildWindows.Size; + if (count > 1) + qsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer); + for (int i = 0; i < count; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (child->Active) + AddWindowToSortedBuffer(out_sorted_windows, child); + } + } +} + +static void AddDrawListToRenderList(ImVector& out_render_list, ImDrawList* draw_list) +{ + if (draw_list->CmdBuffer.empty()) + return; + + // Remove trailing command if unused + ImDrawCmd& last_cmd = draw_list->CmdBuffer.back(); + if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL) + { + draw_list->CmdBuffer.pop_back(); + if (draw_list->CmdBuffer.empty()) + return; + } + + // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. May trigger for you if you are using PrimXXX functions incorrectly. + IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); + IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); + IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); + + // Check that draw_list doesn't use more vertices than indexable in a single draw call (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) + // If this assert triggers because you are drawing lots of stuff manually: + // A) Make sure you are coarse clipping, because ImDrawList let all your vertices pass. You can use thre Metrics window to inspect draw list contents. + // B) If you need/want meshes with more than 64K vertices, uncomment the '#define ImDrawIdx unsigned int' line in imconfig.h to set the index size to 4 bytes. + // You'll need to handle the 4-bytes indices to your renderer. For example, the OpenGL example code detect index size at compile-time by doing: + // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + // Your own engine or render API may use different parameters or function calls to specify index sizes. 2 and 4 bytes indices are generally supported by most API. + // C) If for some reason you cannot use 4 bytes indices or don't want to, a workaround is to call BeginChild()/EndChild() before reaching the 64K limit to split your draw commands in multiple draw lists. + if (sizeof(ImDrawIdx) == 2) + IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + + out_render_list.push_back(draw_list); + GImGui->IO.MetricsRenderVertices += draw_list->VtxBuffer.Size; + GImGui->IO.MetricsRenderIndices += draw_list->IdxBuffer.Size; +} + +static void AddWindowToRenderList(ImVector& out_render_list, ImGuiWindow* window) +{ + AddDrawListToRenderList(out_render_list, window->DrawList); + for (int i = 0; i < window->DC.ChildWindows.Size; i++) + { + ImGuiWindow* child = window->DC.ChildWindows[i]; + if (!child->Active) // clipped children may have been marked not active + continue; + if ((child->Flags & ImGuiWindowFlags_Popup) && child->HiddenFrames > 0) + continue; + AddWindowToRenderList(out_render_list, child); + } +} + +static void AddWindowToRenderListSelectLayer(ImGuiWindow* window) +{ + // FIXME: Generalize this with a proper layering system so e.g. user can draw in specific layers, below text, .. + ImGuiContext& g = *GImGui; + g.IO.MetricsActiveWindows++; + if (window->Flags & ImGuiWindowFlags_Popup) + AddWindowToRenderList(g.RenderDrawLists[1], window); + else if (window->Flags & ImGuiWindowFlags_Tooltip) + AddWindowToRenderList(g.RenderDrawLists[2], window); + else + AddWindowToRenderList(g.RenderDrawLists[0], window); +} + +// When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result. +void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +void ImGui::PopClipRect() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DrawList->PopClipRect(); + window->ClipRect = window->DrawList->_ClipRectStack.back(); +} + +// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. +void ImGui::EndFrame() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times. + return; + + // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) + if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.OsImePosRequest - g.OsImePosSet) > 0.0001f) + { + g.IO.ImeSetInputScreenPosFn((int)g.OsImePosRequest.x, (int)g.OsImePosRequest.y); + g.OsImePosSet = g.OsImePosRequest; + } + + // Hide implicit "Debug" window if it hasn't been used + IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls + if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) + g.CurrentWindow->Active = false; + ImGui::End(); + + if (g.ActiveId == 0 && g.HoveredId == 0) + { + if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear + { + // Click to focus window and start moving (after we're done with all our widgets) + if (g.IO.MouseClicked[0]) + { + if (g.HoveredRootWindow != NULL) + { + FocusWindow(g.HoveredWindow); + if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove) && !(g.HoveredRootWindow->Flags & ImGuiWindowFlags_NoMove)) + { + g.MovingWindow = g.HoveredWindow; + g.MovingWindowMoveId = g.MovingWindow->MoveId; + SetActiveID(g.MovingWindowMoveId, g.HoveredRootWindow); + g.ActiveIdClickOffset = g.IO.MousePos - g.MovingWindow->RootWindow->Pos; + } + } + else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL) + { + // Clicking on void disable focus + FocusWindow(NULL); + } + } + + // With right mouse button we close popups without changing focus + // (The left mouse button path calls FocusWindow which will lead NewFrame->CloseInactivePopups to trigger) + if (g.IO.MouseClicked[1]) + { + // Find the top-most window between HoveredWindow and the front most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow* modal = GetFrontMostModalRootWindow(); + bool hovered_window_above_modal = false; + if (modal == NULL) + hovered_window_above_modal = true; + for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (window == modal) + break; + if (window == g.HoveredWindow) + hovered_window_above_modal = true; + } + CloseInactivePopups(hovered_window_above_modal ? g.HoveredWindow : modal); + } + } + } + + // Sort the window list so that all child windows are after their parent + // We cannot do that on FocusWindow() because childs may not exist yet + g.WindowsSortBuffer.resize(0); + g.WindowsSortBuffer.reserve(g.Windows.Size); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it + continue; + AddWindowToSortedBuffer(g.WindowsSortBuffer, window); + } + + IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong + g.Windows.swap(g.WindowsSortBuffer); + + // Clear Input data for next frame + g.IO.MouseWheel = 0.0f; + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + + g.FrameCountEnded = g.FrameCount; +} + +void ImGui::Render() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + + if (g.FrameCountEnded != g.FrameCount) + ImGui::EndFrame(); + g.FrameCountRendered = g.FrameCount; + + // Skip render altogether if alpha is 0.0 + // Note that vertex buffers have been created and are wasted, so it is best practice that you don't create windows in the first place, or consistently respond to Begin() returning false. + if (g.Style.Alpha > 0.0f) + { + // Gather windows to render + g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0; + for (int i = 0; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) + g.RenderDrawLists[i].resize(0); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* window = g.Windows[i]; + if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0) + AddWindowToRenderListSelectLayer(window); + } + + // Flatten layers + int n = g.RenderDrawLists[0].Size; + int flattened_size = n; + for (int i = 1; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) + flattened_size += g.RenderDrawLists[i].Size; + g.RenderDrawLists[0].resize(flattened_size); + for (int i = 1; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) + { + ImVector& layer = g.RenderDrawLists[i]; + if (layer.empty()) + continue; + memcpy(&g.RenderDrawLists[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); + n += layer.Size; + } + + // Draw software mouse cursor if requested + if (g.IO.MouseDrawCursor) + { + const ImGuiMouseCursorData& cursor_data = g.MouseCursorData[g.MouseCursor]; + const ImVec2 pos = g.IO.MousePos - cursor_data.HotOffset; + const ImVec2 size = cursor_data.Size; + const ImTextureID tex_id = g.IO.Fonts->TexID; + g.OverlayDrawList.PushTextureID(tex_id); + g.OverlayDrawList.AddImage(tex_id, pos+ImVec2(1,0), pos+ImVec2(1,0) + size, cursor_data.TexUvMin[1], cursor_data.TexUvMax[1], IM_COL32(0,0,0,48)); // Shadow + g.OverlayDrawList.AddImage(tex_id, pos+ImVec2(2,0), pos+ImVec2(2,0) + size, cursor_data.TexUvMin[1], cursor_data.TexUvMax[1], IM_COL32(0,0,0,48)); // Shadow + g.OverlayDrawList.AddImage(tex_id, pos, pos + size, cursor_data.TexUvMin[1], cursor_data.TexUvMax[1], IM_COL32(0,0,0,255)); // Black border + g.OverlayDrawList.AddImage(tex_id, pos, pos + size, cursor_data.TexUvMin[0], cursor_data.TexUvMax[0], IM_COL32(255,255,255,255)); // White fill + g.OverlayDrawList.PopTextureID(); + } + if (!g.OverlayDrawList.VtxBuffer.empty()) + AddDrawListToRenderList(g.RenderDrawLists[0], &g.OverlayDrawList); + + // Setup draw data + g.RenderDrawData.Valid = true; + g.RenderDrawData.CmdLists = (g.RenderDrawLists[0].Size > 0) ? &g.RenderDrawLists[0][0] : NULL; + g.RenderDrawData.CmdListsCount = g.RenderDrawLists[0].Size; + g.RenderDrawData.TotalVtxCount = g.IO.MetricsRenderVertices; + g.RenderDrawData.TotalIdxCount = g.IO.MetricsRenderIndices; + + // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData() + if (g.RenderDrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) + g.IO.RenderDrawListsFn(&g.RenderDrawData); + } +} + +const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) +{ + const char* text_display_end = text; + if (!text_end) + text_end = (const char*)-1; + + while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) + text_display_end++; + return text_display_end; +} + +// Pass text data straight to log (without being displayed) +void ImGui::LogText(const char* fmt, ...) +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + va_list args; + va_start(args, fmt); + if (g.LogFile) + { + vfprintf(g.LogFile, fmt, args); + } + else + { + g.LogClipboard->appendfv(fmt, args); + } + va_end(args); +} + +// Internal version that takes a position to decide on newline placement and pad items according to their depth. +// We split text into individual lines to add current tree level padding +static void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = ImGui::FindRenderedTextEnd(text, text_end); + + const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1); + if (ref_pos) + window->DC.LogLinePosY = ref_pos->y; + + const char* text_remaining = text; + if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth + g.LogStartDepth = window->DC.TreeDepth; + const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth); + for (;;) + { + // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry. + const char* line_end = text_remaining; + while (line_end < text_end) + if (*line_end == '\n') + break; + else + line_end++; + if (line_end >= text_end) + line_end = NULL; + + const bool is_first_line = (text == text_remaining); + bool is_last_line = false; + if (line_end == NULL) + { + is_last_line = true; + line_end = text_end; + } + if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0)) + { + const int char_count = (int)(line_end - text_remaining); + if (log_new_line || !is_first_line) + ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining); + else + ImGui::LogText(" %.*s", char_count, text_remaining); + } + + if (is_last_line) + break; + text_remaining = line_end + 1; + } +} + +// Internal ImGui functions to render text +// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() +void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Hide anything after a '##' string + const char* text_display_end; + if (hide_text_after_hash) + { + text_display_end = FindRenderedTextEnd(text, text_end); + } + else + { + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + text_display_end = text_end; + } + + const int text_len = (int)(text_display_end - text); + if (text_len > 0) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); + } +} + +void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!text_end) + text_end = text + strlen(text); // FIXME-OPT + + const int text_len = (int)(text_end - text); + if (text_len > 0) + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); + if (g.LogEnabled) + LogRenderedText(&pos, text, text_end); + } +} + +// Default clip_rect uses (pos_min,pos_max) +// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) +void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +{ + // Hide anything after a '##' string + const char* text_display_end = FindRenderedTextEnd(text, text_end); + const int text_len = (int)(text_display_end - text); + if (text_len == 0) + return; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Perform CPU side clipping for single clipped element to avoid using scissor state + ImVec2 pos = pos_min; + const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); + + const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; + const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; + bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); + if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min + need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); + + // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. + if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); + if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); + + // Render + if (need_clipping) + { + ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + } + else + { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + } + if (g.LogEnabled) + LogRenderedText(&pos, text, text_display_end); +} + +// Render a rectangle shaped with optional rounding and borders +void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); + const float border_size = g.Style.FrameBorderSize; + if (border && border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const float border_size = g.Style.FrameBorderSize; + if (border_size > 0.0f) + { + window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); + } +} + +// Render a triangle to denote expanded/collapsed state +void ImGui::RenderTriangle(ImVec2 p_min, ImGuiDir dir, float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const float h = g.FontSize * 1.00f; + float r = h * 0.40f * scale; + ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale); + + ImVec2 a, b, c; + switch (dir) + { + case ImGuiDir_Up: + case ImGuiDir_Down: + if (dir == ImGuiDir_Up) r = -r; + center.y -= r * 0.25f; + a = ImVec2(0,1) * r; + b = ImVec2(-0.866f,-0.5f) * r; + c = ImVec2(+0.866f,-0.5f) * r; + break; + case ImGuiDir_Left: + case ImGuiDir_Right: + if (dir == ImGuiDir_Left) r = -r; + center.x -= r * 0.25f; + a = ImVec2(1,0) * r; + b = ImVec2(-0.500f,+0.866f) * r; + c = ImVec2(-0.500f,-0.866f) * r; + break; + case ImGuiDir_None: + case ImGuiDir_Count_: + IM_ASSERT(0); + break; + } + + window->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text)); +} + +void ImGui::RenderBullet(ImVec2 pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddCircleFilled(pos, GImGui->FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8); +} + +void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + float thickness = ImMax(sz / 5.0f, 1.0f); + sz -= thickness*0.5f; + pos += ImVec2(thickness*0.25f, thickness*0.25f); + + float third = sz / 3.0f; + float bx = pos.x + third; + float by = pos.y + sz - third*0.5f; + window->DrawList->PathLineTo(ImVec2(bx - third, by - third)); + window->DrawList->PathLineTo(ImVec2(bx, by)); + window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2)); + window->DrawList->PathStroke(col, false, thickness); +} + +// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. +// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize) +ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) +{ + ImGuiContext& g = *GImGui; + + const char* text_display_end; + if (hide_text_after_double_hash) + text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string + else + text_display_end = text_end; + + ImFont* font = g.Font; + const float font_size = g.FontSize; + if (text == text_display_end) + return ImVec2(0.0f, font_size); + ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); + + // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field) + const float font_scale = font_size / font->FontSize; + const float character_spacing_x = 1.0f * font_scale; + if (text_size.x > 0.0f) + text_size.x -= character_spacing_x; + text_size.x = (float)(int)(text_size.x + 0.95f); + + return text_size; +} + +// Helper to calculate coarse clipping of large list of evenly sized items. +// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. +// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX +void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.LogEnabled) + { + // If logging is active, do not perform any clipping + *out_items_display_start = 0; + *out_items_display_end = items_count; + return; + } + if (window->SkipItems) + { + *out_items_display_start = *out_items_display_end = 0; + return; + } + + const ImVec2 pos = window->DC.CursorPos; + int start = (int)((window->ClipRect.Min.y - pos.y) / items_height); + int end = (int)((window->ClipRect.Max.y - pos.y) / items_height); + start = ImClamp(start, 0, items_count); + end = ImClamp(end + 1, start, items_count); + *out_items_display_start = start; + *out_items_display_end = end; +} + +// Find window given position, search front-to-back +// FIXME: Note that we have a lag here because WindowRectClipped is updated in Begin() so windows moved by user via SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is called, aka before the next Begin(). Moving window thankfully isn't affected. +static ImGuiWindow* FindHoveredWindow(ImVec2 pos) +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (!window->Active) + continue; + if (window->Flags & ImGuiWindowFlags_NoInputs) + continue; + + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) + ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding); + if (bb.Contains(pos)) + return window; + } + return NULL; +} + +// Test if mouse cursor is hovering given rectangle +// NB- Rectangle is clipped by our current clip setting +// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) +bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Clip + ImRect rect_clipped(r_min, r_max); + if (clip) + rect_clipped.ClipWith(window->ClipRect); + + // Expand for touch input + const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); + return rect_for_touch.Contains(g.IO.MousePos); +} + +bool ImGui::IsAnyWindowHovered() +{ + ImGuiContext& g = *GImGui; + return g.HoveredWindow != NULL; +} + +bool ImGui::IsAnyWindowFocused() +{ + ImGuiContext& g = *GImGui; + return g.NavWindow != NULL; +} + +static bool IsKeyPressedMap(ImGuiKey key, bool repeat) +{ + const int key_index = GImGui->IO.KeyMap[key]; + return (key_index >= 0) ? ImGui::IsKeyPressed(key_index, repeat) : false; +} + +int ImGui::GetKeyIndex(ImGuiKey imgui_key) +{ + IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT); + return GImGui->IO.KeyMap[imgui_key]; +} + +// Note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your backend/engine stored them into KeyDown[]! +bool ImGui::IsKeyDown(int user_key_index) +{ + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown)); + return GImGui->IO.KeysDown[user_key_index]; +} + +int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate) +{ + if (t == 0.0f) + return 1; + if (t <= repeat_delay || repeat_rate <= 0.0f) + return 0; + const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate); + return (count > 0) ? count : 0; +} + +int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate) +{ + ImGuiContext& g = *GImGui; + if (key_index < 0) return false; + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[key_index]; + return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate); +} + +bool ImGui::IsKeyPressed(int user_key_index, bool repeat) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + const float t = g.IO.KeysDownDuration[user_key_index]; + if (t == 0.0f) + return true; + if (repeat && t > g.IO.KeyRepeatDelay) + return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + return false; +} + +bool ImGui::IsKeyReleased(int user_key_index) +{ + ImGuiContext& g = *GImGui; + if (user_key_index < 0) return false; + IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + if (g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index]) + return true; + return false; +} + +bool ImGui::IsMouseDown(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDown[button]; +} + +bool ImGui::IsMouseClicked(int button, bool repeat) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float t = g.IO.MouseDownDuration[button]; + if (t == 0.0f) + return true; + + if (repeat && t > g.IO.KeyRepeatDelay) + { + float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; + if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) + return true; + } + + return false; +} + +bool ImGui::IsMouseReleased(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button]; +} + +bool ImGui::IsMouseDoubleClicked(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + +bool ImGui::IsMouseDragging(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (!g.IO.MouseDown[button]) + return false; + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; +} + +ImVec2 ImGui::GetMousePos() +{ + return GImGui->IO.MousePos; +} + +// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! +ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() +{ + ImGuiContext& g = *GImGui; + if (g.CurrentPopupStack.Size > 0) + return g.OpenPopupStack[g.CurrentPopupStack.Size-1].MousePosOnOpen; + return g.IO.MousePos; +} + +// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position +bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) +{ + if (mouse_pos == NULL) + mouse_pos = &GImGui->IO.MousePos; + const float MOUSE_INVALID = -256000.0f; + return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID; +} + +ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + if (g.IO.MouseDown[button]) + if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) + return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment). + return ImVec2(0.0f, 0.0f); +} + +void ImGui::ResetMouseDragDelta(int button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr + g.IO.MouseClickedPos[button] = g.IO.MousePos; +} + +ImGuiMouseCursor ImGui::GetMouseCursor() +{ + return GImGui->MouseCursor; +} + +void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) +{ + GImGui->MouseCursor = cursor_type; +} + +void ImGui::CaptureKeyboardFromApp(bool capture) +{ + GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0; +} + +void ImGui::CaptureMouseFromApp(bool capture) +{ + GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0; +} + +bool ImGui::IsItemActive() +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId) + { + ImGuiWindow* window = g.CurrentWindow; + return g.ActiveId == window->DC.LastItemId; + } + return false; +} + +bool ImGui::IsItemClicked(int mouse_button) +{ + return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_Default); +} + +bool ImGui::IsAnyItemHovered() +{ + ImGuiContext& g = *GImGui; + return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0; +} + +bool ImGui::IsAnyItemActive() +{ + return GImGui->ActiveId != 0; +} + +bool ImGui::IsItemVisible() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(window->DC.LastItemRect); +} + +// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. +void ImGui::SetItemAllowOverlap() +{ + ImGuiContext& g = *GImGui; + if (g.HoveredId == g.CurrentWindow->DC.LastItemId) + g.HoveredIdAllowOverlap = true; + if (g.ActiveId == g.CurrentWindow->DC.LastItemId) + g.ActiveIdAllowOverlap = true; +} + +ImVec2 ImGui::GetItemRectMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Min; +} + +ImVec2 ImGui::GetItemRectMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.Max; +} + +ImVec2 ImGui::GetItemRectSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.LastItemRect.GetSize(); +} + +ImVec2 ImGui::CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge, float outward) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImRect rect = window->DC.LastItemRect; + rect.Expand(outward); + return rect.GetClosestPoint(pos, on_edge); +} + +static ImRect GetVisibleRect() +{ + ImGuiContext& g = *GImGui; + if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y) + return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax); + return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); +} + +// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first. +void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip) +{ + ImGuiContext& g = *GImGui; + char window_name[16]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); + if (override_previous_tooltip) + if (ImGuiWindow* window = FindWindowByName(window_name)) + if (window->Active) + { + // Hide previous tooltips. We can't easily "reset" the content of a window so we create a new one. + window->HiddenFrames = 1; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); + } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; + Begin(window_name, NULL, flags | extra_flags); +} + +void ImGui::SetTooltipV(const char* fmt, va_list args) +{ + BeginTooltipEx(0, true); + TextV(fmt, args); + EndTooltip(); +} + +void ImGui::SetTooltip(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + SetTooltipV(fmt, args); + va_end(args); +} + +void ImGui::BeginTooltip() +{ + BeginTooltipEx(0, false); +} + +void ImGui::EndTooltip() +{ + IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls + ImGui::End(); +} + +// Mark popup as open (toggle toward open state). +// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. +// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). +// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) +void ImGui::OpenPopupEx(ImGuiID id, bool reopen_existing) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = g.CurrentWindow; + int current_stack_size = g.CurrentPopupStack.Size; + ImGuiPopupRef popup_ref = ImGuiPopupRef(id, parent_window, parent_window->GetID("##Menus"), g.IO.MousePos); // Tagged as new ref because constructor sets Window to NULL. + if (g.OpenPopupStack.Size < current_stack_size + 1) + g.OpenPopupStack.push_back(popup_ref); + else if (reopen_existing || g.OpenPopupStack[current_stack_size].PopupId != id) + { + g.OpenPopupStack.resize(current_stack_size+1); + g.OpenPopupStack[current_stack_size] = popup_ref; + + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by CloseInactivePopups(). + // This is equivalent to what ClosePopupToLevel() does. + if (g.OpenPopupStack[current_stack_size].PopupId == id) + FocusWindow(parent_window); + } +} + +void ImGui::OpenPopup(const char* str_id) +{ + ImGuiContext& g = *GImGui; + OpenPopupEx(g.CurrentWindow->GetID(str_id), false); +} + +static void CloseInactivePopups(ImGuiWindow* ref_window) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.empty()) + return; + + // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. + // Don't close our own child popup windows. + int n = 0; + if (ref_window) + { + for (n = 0; n < g.OpenPopupStack.Size; n++) + { + ImGuiPopupRef& popup = g.OpenPopupStack[n]; + if (!popup.Window) + continue; + IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) + continue; + + // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow) + bool has_focus = false; + for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) + has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow); + if (!has_focus) + break; + } + } + if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below + ClosePopupToLevel(n); +} + +static ImGuiWindow* GetFrontMostModalRootWindow() +{ + ImGuiContext& g = *GImGui; + for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) + if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; + return NULL; +} + +static void ClosePopupToLevel(int remaining) +{ + ImGuiContext& g = *GImGui; + if (remaining > 0) + ImGui::FocusWindow(g.OpenPopupStack[remaining-1].Window); + else + ImGui::FocusWindow(g.OpenPopupStack[0].ParentWindow); + g.OpenPopupStack.resize(remaining); +} + +void ImGui::ClosePopup(ImGuiID id) +{ + if (!IsPopupOpen(id)) + return; + ImGuiContext& g = *GImGui; + ClosePopupToLevel(g.OpenPopupStack.Size - 1); +} + +// Close the popup we have begin-ed into. +void ImGui::CloseCurrentPopup() +{ + ImGuiContext& g = *GImGui; + int popup_idx = g.CurrentPopupStack.Size - 1; + if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) + return; + while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu)) + popup_idx--; + ClosePopupToLevel(popup_idx); +} + +static inline void ClearSetNextWindowData() +{ + // FIXME-OPT + ImGuiContext& g = *GImGui; + g.SetNextWindowPosCond = g.SetNextWindowSizeCond = g.SetNextWindowContentSizeCond = g.SetNextWindowCollapsedCond = 0; + g.SetNextWindowSizeConstraint = g.SetNextWindowFocus = false; +} + +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(id)) + { + ClearSetNextWindowData(); // We behave like Begin() and need to consume those values + return false; + } + + char name[20]; + if (extra_flags & ImGuiWindowFlags_ChildMenu) + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + else + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + + bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + + return is_open; +} + +bool ImGui::BeginPopup(const char* str_id) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance + { + ClearSetNextWindowData(); // We behave like Begin() and need to consume those values + return false; + } + return BeginPopupEx(g.CurrentWindow->GetID(str_id), ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::IsPopupOpen(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id; +} + +bool ImGui::IsPopupOpen(const char* str_id) +{ + ImGuiContext& g = *GImGui; + return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id); +} + +bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = window->GetID(name); + if (!IsPopupOpen(id)) + { + ClearSetNextWindowData(); // We behave like Begin() and need to consume those values + return false; + } + + // Center modal windows by default + if ((window->SetWindowPosAllowFlags & g.SetNextWindowPosCond) == 0) + SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + ImGuiWindowFlags flags = extra_flags|ImGuiWindowFlags_Popup|ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoSavedSettings; + bool is_open = Begin(name, p_open, flags); + if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + { + EndPopup(); + if (is_open) + ClosePopup(id); + return false; + } + + return is_open; +} + +void ImGui::EndPopup() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls + IM_ASSERT(g.CurrentPopupStack.Size > 0); + End(); +} + +bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + OpenPopupEx(id, true); + return true; + } + return false; +} + +// This is a helper to handle the simplest case of associating one named popup to one given widget. +// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// You can pass a NULL str_id to use the identifier of the last item. +bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + if (IsMouseClicked(mouse_button)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + OpenPopupEx(id, true); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items) +{ + if (!str_id) + str_id = "window_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (IsMouseClicked(mouse_button)) + if (IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (also_over_items || !IsAnyItemHovered()) + OpenPopupEx(id, true); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) +{ + if (!str_id) + str_id = "void_context"; + ImGuiID id = GImGui->CurrentWindow->GetID(str_id); + if (!IsAnyWindowHovered() && IsMouseClicked(mouse_button)) + OpenPopupEx(id, true); + return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); +} + +static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; + flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag + + const ImVec2 content_avail = ImGui::GetContentRegionAvail(); + ImVec2 size = ImFloor(size_arg); + const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); + if (size.x <= 0.0f) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) + if (size.y <= 0.0f) + size.y = ImMax(content_avail.y + size.y, 4.0f); + + const float backup_border_size = g.Style.ChildBorderSize; + if (!border) + g.Style.ChildBorderSize = 0.0f; + flags |= extra_flags; + + char title[256]; + if (name) + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id); + else + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id); + + ImGui::SetNextWindowSize(size); + bool ret = ImGui::Begin(title, NULL, flags); + ImGuiWindow* child_window = ImGui::GetCurrentWindow(); + child_window->AutoFitChildAxises = auto_fit_axises; + g.Style.ChildBorderSize = backup_border_size; + + return ret; +} + +bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); +} + +bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + return BeginChildEx(NULL, id, size_arg, border, extra_flags); +} + +void ImGui::EndChild() +{ + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss + if (window->BeginCount > 1) + { + ImGui::End(); + } + else + { + // When using auto-filling child window, we don't provide full width/height to ItemSize so that it doesn't feed back into automatic size-fitting. + ImVec2 sz = GetWindowSize(); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f + sz.x = ImMax(4.0f, sz.x); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) + sz.y = ImMax(4.0f, sz.y); + ImGui::End(); + + ImGuiWindow* parent_window = GetCurrentWindow(); + ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); + ItemSize(sz); + ItemAdd(bb, 0); + } +} + +// Helper to create a child window / scrolling region that looks like a normal widget frame. +bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + return BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); +} + +void ImGui::EndChildFrame() +{ + EndChild(); + PopStyleVar(3); + PopStyleColor(); +} + +// Save and compare stack sizes on Begin()/End() to detect usage errors +static void CheckStacksSize(ImGuiWindow* window, bool write) +{ + // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + ImGuiContext& g = *GImGui; + int* p_backup = &window->DC.StackSizesBackup[0]; + { int current = window->IDStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop() + { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup() + { int current = g.CurrentPopupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup() + { int current = g.ColorModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor() + { int current = g.StyleModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar() + { int current = g.FontStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont() + IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); +} + +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox +}; + +static ImVec2 FindBestWindowPosForPopup(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default) +{ + const ImGuiStyle& style = GImGui->Style; + + // r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) + // r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. + ImVec2 safe_padding = style.DisplaySafeAreaPadding; + ImRect r_outer(GetVisibleRect()); + r_outer.Expand(ImVec2((size.x - r_outer.GetWidth() > safe_padding.x*2) ? -safe_padding.x : 0.0f, (size.y - r_outer.GetHeight() > safe_padding.y*2) ? -safe_padding.y : 0.0f)); + ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); + //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); + //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); + + // Combo Box policy (we want a connecting edge) + if (policy == ImGuiPopupPositionPolicy_ComboBox) + { + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + ImVec2 pos; + if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) + if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right + if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left + if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left + if (!r_outer.Contains(ImRect(pos, pos + size))) + continue; + *last_dir = dir; + return pos; + } + } + + // Default popup policy + const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); + float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + if (avail_w < size.x || avail_h < size.y) + continue; + ImVec2 pos; + pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; + pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + *last_dir = dir; + return pos; + } + + // Fallback, try to keep within display + *last_dir = ImGuiDir_None; + ImVec2 pos = ref_pos; + pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); + pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); + return pos; +} + +static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) +{ + window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); + window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); + window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); +} + +ImGuiWindow* ImGui::FindWindowByName(const char* name) +{ + ImGuiContext& g = *GImGui; + ImGuiID id = ImHash(name, 0); + return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); +} + +static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + + // Create window the first time + ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); + window->Flags = flags; + g.WindowsById.SetVoidPtr(window->ID, window); + + // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) + { + // Retrieve settings from .ini file + // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + window->PosFloat = ImVec2(60, 60); + window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); + + if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) + { + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + window->PosFloat = settings->Pos; + window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); + window->Collapsed = settings->Collapsed; + if (ImLengthSqr(settings->Size) > 0.00001f) + size = settings->Size; + } + } + window->Size = window->SizeFull = window->SizeFullAtLastBegin = size; + + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + { + window->AutoFitFramesX = window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } + else + { + if (window->Size.x <= 0.0f) + window->AutoFitFramesX = 2; + if (window->Size.y <= 0.0f) + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } + + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) + g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once + else + g.Windows.push_back(window); + return window; +} + +static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) +{ + ImGuiContext& g = *GImGui; + if (g.SetNextWindowSizeConstraint) + { + // Using -1,-1 on either X/Y axis to preserve the current size. + ImRect cr = g.SetNextWindowSizeConstraintRect; + new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; + new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; + if (g.SetNextWindowSizeConstraintCallback) + { + ImGuiSizeConstraintCallbackData data; + data.UserData = g.SetNextWindowSizeConstraintCallbackUserData; + data.Pos = window->Pos; + data.CurrentSize = window->SizeFull; + data.DesiredSize = new_size; + g.SetNextWindowSizeConstraintCallback(&data); + new_size = data.DesiredSize; + } + } + + // Minimum size + if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) + { + new_size = ImMax(new_size, g.Style.WindowMinSize); + new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + } + return new_size; +} + +static ImVec2 CalcSizeContents(ImGuiWindow* window) +{ + ImVec2 sz; + sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x)); + sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y)); + return sz + window->WindowPadding; +} + +static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImGuiWindowFlags flags = window->Flags; + ImVec2 size_auto_fit; + if ((flags & ImGuiWindowFlags_Tooltip) != 0) + { + // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. + size_auto_fit = size_contents; + } + else + { + // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. + size_auto_fit = ImClamp(size_contents, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding)); + ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit); + if (size_auto_fit_after_constraint.x < size_contents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)) + size_auto_fit.y += style.ScrollbarSize; + if (size_auto_fit_after_constraint.y < size_contents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) + size_auto_fit.x += style.ScrollbarSize; + } + return size_auto_fit; +} + +static float GetScrollMaxX(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x)); +} + +static float GetScrollMaxY(ImGuiWindow* window) +{ + return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y)); +} + +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) +{ + ImVec2 scroll = window->Scroll; + float cr_x = window->ScrollTargetCenterRatio.x; + float cr_y = window->ScrollTargetCenterRatio.y; + if (window->ScrollTarget.x < FLT_MAX) + scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x); + if (window->ScrollTarget.y < FLT_MAX) + scroll.y = window->ScrollTarget.y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y); + scroll = ImMax(scroll, ImVec2(0.0f, 0.0f)); + if (!window->Collapsed && !window->SkipItems) + { + scroll.x = ImMin(scroll.x, GetScrollMaxX(window)); + scroll.y = ImMin(scroll.y, GetScrollMaxY(window)); + } + return scroll; +} + +static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) +{ + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + return ImGuiCol_PopupBg; + if (flags & ImGuiWindowFlags_ChildWindow) + return ImGuiCol_ChildBg; + return ImGuiCol_WindowBg; +} + +static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) +{ + ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left + ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right + ImVec2 size_expected = pos_max - pos_min; + ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected); + *out_pos = pos_min; + if (corner_norm.x == 0.0f) + out_pos->x -= (size_constrained.x - size_expected.x); + if (corner_norm.y == 0.0f) + out_pos->y -= (size_constrained.y - size_expected.y); + *out_size = size_constrained; +} + +struct ImGuiResizeGripDef +{ + ImVec2 CornerPos; + ImVec2 InnerDir; + int AngleMin12, AngleMax12; +}; + +const ImGuiResizeGripDef resize_grip_def[4] = +{ + { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right + { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left + { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left + { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right +}; + +static ImRect GetBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) +{ + ImRect rect = window->Rect(); + if (thickness == 0.0f) rect.Max -= ImVec2(1,1); + if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness); + if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding); + if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y); + if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); + IM_ASSERT(0); + return ImRect(); +} + +// Push a new ImGui window to add widgets to. +// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. +// - Begin/End can be called multiple times during the frame with the same window name to append content. +// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). +// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. +// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. +// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. +bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + IM_ASSERT(name != NULL); // Window name required + IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() + IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet + + if (flags & ImGuiWindowFlags_NoInputs) + flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + + // Find or create + ImGuiWindow* window = FindWindowByName(name); + if (!window) + { + ImVec2 size_on_first_use = (g.SetNextWindowSizeCond != 0) ? g.SetNextWindowSizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. + window = CreateNewWindow(name, size_on_first_use, flags); + } + + const int current_frame = g.FrameCount; + const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); + if (first_begin_of_the_frame) + window->Flags = (ImGuiWindowFlags)flags; + else + flags = window->Flags; + + // Update the Appearing flag + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed + window_just_activated_by_user |= (window != popup_ref.Window); + } + window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); + window->CloseButton = (p_open != NULL); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack + ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; + IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); + + // Add to stack + g.CurrentWindowStack.push_back(window); + SetCurrentWindow(window); + CheckStacksSize(window, true); + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; + popup_ref.Window = window; + g.CurrentPopupStack.push_back(popup_ref); + window->PopupId = popup_ref.PopupId; + } + + // Process SetNextWindow***() calls + bool window_pos_set_by_api = false; + bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; + if (g.SetNextWindowPosCond) + { + window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.SetNextWindowPosCond) != 0; + if (window_pos_set_by_api && ImLengthSqr(g.SetNextWindowPosPivot) > 0.00001f) + { + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. + window->SetWindowPosVal = g.SetNextWindowPosVal; + window->SetWindowPosPivot = g.SetNextWindowPosPivot; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + } + else + { + SetWindowPos(window, g.SetNextWindowPosVal, g.SetNextWindowPosCond); + } + g.SetNextWindowPosCond = 0; + } + if (g.SetNextWindowSizeCond) + { + window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.SetNextWindowSizeCond) != 0 && (g.SetNextWindowSizeVal.x > 0.0f); + window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.SetNextWindowSizeCond) != 0 && (g.SetNextWindowSizeVal.y > 0.0f); + SetWindowSize(window, g.SetNextWindowSizeVal, g.SetNextWindowSizeCond); + g.SetNextWindowSizeCond = 0; + } + if (g.SetNextWindowContentSizeCond) + { + // Adjust passed "client size" to become a "window size" + window->SizeContentsExplicit = g.SetNextWindowContentSizeVal; + window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight(); + g.SetNextWindowContentSizeCond = 0; + } + else if (first_begin_of_the_frame) + { + window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); + } + if (g.SetNextWindowCollapsedCond) + { + SetWindowCollapsed(window, g.SetNextWindowCollapsedVal, g.SetNextWindowCollapsedCond); + g.SetNextWindowCollapsedCond = 0; + } + if (g.SetNextWindowFocus) + { + SetWindowFocus(); + g.SetNextWindowFocus = false; + } + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + + // When reusing window again multiple times a frame, just append content (don't need to setup again) + if (first_begin_of_the_frame) + { + // Initialize + window->ParentWindow = parent_window; + window->RootWindow = window->RootNonPopupWindow = window; + if (parent_window && (flags & ImGuiWindowFlags_ChildWindow)) + window->RootWindow = parent_window->RootWindow; + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + window->RootNonPopupWindow = parent_window->RootNonPopupWindow; + //window->RootNavWindow = window; + //while (window->RootNavWindow->Flags & ImGuiWindowFlags_NavFlattened) + // window->RootNavWindow = window->RootNavWindow->ParentWindow; + + window->Active = true; + window->BeginOrderWithinParent = 0; + window->BeginOrderWithinContext = g.WindowsActiveCount++; + window->BeginCount = 0; + window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); + window->LastFrameActive = current_frame; + window->IDStack.resize(1); + + // Setup draw list and outer clipping rectangle + window->DrawList->Clear(); + window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + ImRect fullscreen_rect(GetVisibleRect()); + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) + PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); + else + PushClipRect(fullscreen_rect.Min, fullscreen_rect.Max, true); + + if (window_just_activated_by_user) + { + // Popup first latch mouse position, will position itself when it appears next frame + window->AutoPosLastDirection = ImGuiDir_None; + if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) + window->PosFloat = g.IO.MousePos; + } + + // Collapse window by double-clicking on title bar + // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) + { + ImRect title_bar_rect = window->TitleBarRect(); + if (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]) + { + window->Collapsed = !window->Collapsed; + MarkIniSettingsDirty(window); + FocusWindow(window); + } + } + else + { + window->Collapsed = false; + } + + // SIZE + + // Update contents size from last frame for auto-fitting (unless explicitly specified) + window->SizeContents = CalcSizeContents(window); + + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) + if (window->HiddenFrames > 0) + window->HiddenFrames--; + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && window_just_activated_by_user) + { + window->HiddenFrames = 1; + if (flags & ImGuiWindowFlags_AlwaysAutoResize) + { + if (!window_size_x_set_by_api) + window->Size.x = window->SizeFull.x = 0.f; + if (!window_size_y_set_by_api) + window->Size.y = window->SizeFull.y = 0.f; + window->SizeContents = ImVec2(0.f, 0.f); + } + } + + // Lock window rounding, border size and rounding so that altering the border sizes for children doesn't have side-effects. + window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; + window->WindowPadding = style.WindowPadding; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) + window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); + const float window_rounding = window->WindowRounding; + const float window_border_size = window->WindowBorderSize; + + // Calculate auto-fit size, handle automatic resize + const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents); + ImVec2 size_full_modified(FLT_MAX, FLT_MAX); + if (flags & ImGuiWindowFlags_AlwaysAutoResize && !window->Collapsed) + { + // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + if (!window_size_x_set_by_api) + window->SizeFull.x = size_full_modified.x = size_auto_fit.x; + if (!window_size_y_set_by_api) + window->SizeFull.y = size_full_modified.y = size_auto_fit.y; + } + else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + { + // Auto-fit only grows during the first few frames + // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. + if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) + window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) + window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + if (!window->Collapsed) + MarkIniSettingsDirty(window); + } + + // Apply minimum/maximum window size constraints and final size + window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); + window->Size = window->Collapsed ? window->TitleBarRect().GetSize() : window->SizeFull; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) + { + IM_ASSERT(window_size_x_set_by_api && window_size_y_set_by_api); // Submitted by BeginChild() + window->Size = window->SizeFull; + } + + // SCROLLBAR STATUS + + // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). + if (!window->Collapsed) + { + // When reading the current size we need to read it after size constraints have been applied + float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x; + float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y; + window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); + window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f) - window->WindowPadding.x) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + if (window->ScrollbarX && !window->ScrollbarY) + window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars + style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); + } + + // POSITION + + // Position child window + if (flags & ImGuiWindowFlags_ChildWindow) + { + window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size; + parent_window->DC.ChildWindows.push_back(window); + } + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api) + window->Pos = window->PosFloat = parent_window->DC.CursorPos; + + const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFrames == 0); + if (window_pos_with_pivot) + { + // Position given a pivot (e.g. for centering) + SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); + } + else if (flags & ImGuiWindowFlags_ChildMenu) + { + // Child menus typically request _any_ position within the parent menu item, and then our FindBestPopupWindowPos() function will move the new menu outside the parent bounds. + // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. + IM_ASSERT(window_pos_set_by_api); + float horizontal_overlap = style.ItemSpacing.x; // We want some overlap to convey the relative depth of each popup (currently the amount of overlap it is hard-coded to style.ItemSpacing.x, may need to introduce another style value). + ImGuiWindow* parent_menu = parent_window_in_stack; + ImRect rect_to_avoid; + if (parent_menu->DC.MenuBarAppending) + rect_to_avoid = ImRect(-FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight(), FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight() + parent_menu->MenuBarHeight()); + else + rect_to_avoid = ImRect(parent_menu->Pos.x + horizontal_overlap, -FLT_MAX, parent_menu->Pos.x + parent_menu->Size.x - horizontal_overlap - parent_menu->ScrollbarSizes.x, FLT_MAX); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + } + else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) + { + ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1); + window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + } + + // Position tooltip (always follows mouse) + if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api) + { + ImVec2 ref_pos = g.IO.MousePos; + ImRect rect_to_avoid(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24, ref_pos.y + 24); // FIXME: Completely hard-coded. Store boxes in mouse cursor data? Scale? Center on cursor hit-point? + window->PosFloat = FindBestWindowPosForPopup(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + if (window->AutoPosLastDirection == ImGuiDir_None) + window->PosFloat = ref_pos + ImVec2(2,2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + } + + // Clamp position so it stays visible + if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + { + if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + { + ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); + window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size; + window->PosFloat = ImMin(window->PosFloat, g.IO.DisplaySize - padding); + } + } + window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); + + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); + else + window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); + + // Prepare for focus requests + window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); + window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); + window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; + window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; + + // Apply scrolling + window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window); + window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + + // Apply focus, new windows appears in front + bool want_focus = false; + if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) + if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) + want_focus = true; + + // Draw modal window background (darkens what is behind them) + if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow()) + window->DrawList->AddRectFilled(fullscreen_rect.Min, fullscreen_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio)); + + // Draw window + handle manual resize + ImRect title_bar_rect = window->TitleBarRect(); + if (window->Collapsed) + { + // Title bar only + float backup_border_size = style.FrameBorderSize; + g.Style.FrameBorderSize = window->WindowBorderSize; + RenderFrame(title_bar_rect.Min, title_bar_rect.Max, GetColorU32(ImGuiCol_TitleBgCollapsed), true, window_rounding); + g.Style.FrameBorderSize = backup_border_size; + } + else + { + // Handle resize for: Resize Grips, Borders, Gamepad + int border_held = -1; + ImU32 resize_grip_col[4] = { 0 }; + const int resize_grip_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 2 : 1; // 4 + const int resize_border_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 4 : 0; + + const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window_rounding + 1.0f + g.FontSize * 0.2f); + const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f); + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && !(flags & ImGuiWindowFlags_NoResize)) + { + ImVec2 pos_target(FLT_MAX, FLT_MAX); + ImVec2 size_target(FLT_MAX, FLT_MAX); + + // Manual resize grips + PushID("#RESIZE"); + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + + // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window + ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size); + resize_rect.FixInverted(); + bool hovered, held; + ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren); + if (hovered || held) + g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + + if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) + { + // Manual auto-fit when double-clicking + size_target = CalcSizeAfterConstraint(window, size_auto_fit); + ClearActiveID(); + } + else if (held) + { + // Resize from any of the four corners + // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip + CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target); + } + if (resize_grip_n == 0 || held || hovered) + resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); + } + for (int border_n = 0; border_n < resize_border_count; border_n++) + { + const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check. + const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise + bool hovered, held; + ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE); + ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n+4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren); + if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held) + { + g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + if (held) border_held = border_n; + } + if (held) + { + ImVec2 border_target = window->Pos; + ImVec2 border_posn; + if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); } + if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); } + if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); } + if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); } + CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target); + } + } + PopID(); + + // Apply back modified position/size to window + if (size_target.x != FLT_MAX) + { + window->SizeFull = size_target; + MarkIniSettingsDirty(window); + } + if (pos_target.x != FLT_MAX) + { + window->Pos = window->PosFloat = ImVec2((float)(int)pos_target.x, (float)(int)pos_target.y); + MarkIniSettingsDirty(window); + } + + window->Size = window->SizeFull; + title_bar_rect = window->TitleBarRect(); + } + + // Window background, Default Alpha + ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); + window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); + + // Title bar + const bool window_is_focused = want_focus || (g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow); + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg), window_rounding, ImDrawCornerFlags_Top); + + // Menu bar + if (flags & ImGuiWindowFlags_MenuBar) + { + ImRect menu_bar_rect = window->MenuBarRect(); + menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top); + if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) + window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Scrollbars + if (window->ScrollbarX) + Scrollbar(ImGuiLayoutType_Horizontal); + if (window->ScrollbarY) + Scrollbar(ImGuiLayoutType_Vertical); + + // Render resize grips (after their input handling so we don't have a frame of latency) + if (!(flags & ImGuiWindowFlags_NoResize)) + { + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) + { + const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); + } + } + + // Borders + if (window_border_size > 0.0f) + window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size); + if (border_held != -1) + { + ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f); + window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); + } + if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar)) + window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize,-1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. + window->SizeFullAtLastBegin = window->SizeFull; + + // Update ContentsRegionMax. All the variable it depends on are set above in this function. + window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x; + window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); + window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x)); + window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y)); + + // Setup drawing context + // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) + window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x; + window->DC.GroupOffsetX = 0.0f; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); + window->DC.CursorPos = window->DC.CursorStartPos; + window->DC.CursorPosPrevLine = window->DC.CursorPos; + window->DC.CursorMaxPos = window->DC.CursorStartPos; + window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; + window->DC.MenuBarAppending = false; + window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x); + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + window->DC.ChildWindows.resize(0); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.ItemFlags = ImGuiItemFlags_Default_; + window->DC.ItemWidth = window->ItemWidthDefault; + window->DC.TextWrapPos = -1.0f; // disabled + window->DC.ItemFlagsStack.resize(0); + window->DC.ItemWidthStack.resize(0); + window->DC.TextWrapPosStack.resize(0); + window->DC.ColumnsSet = NULL; + window->DC.TreeDepth = 0; + window->DC.StateStorage = &window->StateStorage; + window->DC.GroupStack.resize(0); + window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); + + if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags)) + { + window->DC.ItemFlags = parent_window->DC.ItemFlags; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); + } + + if (window->AutoFitFramesX > 0) + window->AutoFitFramesX--; + if (window->AutoFitFramesY > 0) + window->AutoFitFramesY--; + + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) + if (want_focus) + FocusWindow(window); + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + { + // Collapse button + if (!(flags & ImGuiWindowFlags_NoCollapse)) + { + RenderTriangle(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + } + + // Close button + if (p_open != NULL) + { + const float PAD = 2.0f; + const float rad = (window->TitleBarHeight() - PAD*2.0f) * 0.5f; + if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-PAD - rad, PAD + rad), rad)) + *p_open = false; + } + + // Title text (FIXME: refactor text alignment facilities along with RenderText helpers) + const ImVec2 text_size = CalcTextSize(name, NULL, true); + ImVec2 text_min = window->Pos; + ImVec2 text_max = window->Pos + ImVec2(window->Size.x, style.FramePadding.y*2 + text_size.y); + ImRect clip_rect; + clip_rect.Max = ImVec2(window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x), text_max.y); // Match the size of CloseWindowButton() + float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; + float pad_right = (p_open != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; + if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x); + text_min.x += pad_left; + text_max.x -= pad_right; + clip_rect.Min = ImVec2(text_min.x, window->Pos.y); + RenderTextClipped(text_min, text_max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); + } + + // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() + window->WindowRectClipped = window->Rect(); + window->WindowRectClipped.ClipWith(window->ClipRect); + + // Pressing CTRL+C while holding on a window copy its content to the clipboard + // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. + // Maybe we can support CTRL+C on every element? + /* + if (g.ActiveId == move_id) + if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) + ImGui::LogToClipboard(); + */ + + // Inner rectangle + // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame + // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. + window->InnerRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize; + window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); + window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize; + window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize; + //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); + + // After Begin() we fill the last item / hovered data using the title bar data. Make that a standard behavior (to allow usage of context menus on title bar only, etc.). + window->DC.LastItemId = window->MoveId; + window->DC.LastItemRect = title_bar_rect; + window->DC.LastItemRectHoveredRect = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false); + } + + // Inner clipping rectangle + // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + const float border_size = window->WindowBorderSize; + ImRect clip_rect; + clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y); + clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); + clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y); + PushClipRect(clip_rect.Min, clip_rect.Max, true); + + // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) + if (first_begin_of_the_frame) + window->WriteAccessed = false; + + window->BeginCount++; + g.SetNextWindowSizeConstraint = false; + + // Child window can be out of sight and have "negative" clip windows. + // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar). + if (flags & ImGuiWindowFlags_ChildWindow) + { + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); + window->Collapsed = parent_window && parent_window->Collapsed; + + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + window->Collapsed |= (window->WindowRectClipped.Min.x >= window->WindowRectClipped.Max.x || window->WindowRectClipped.Min.y >= window->WindowRectClipped.Max.y); + + // We also hide the window from rendering because we've already added its border to the command list. + // (we could perform the check earlier in the function but it is simpler at this point) + if (window->Collapsed) + window->Active = false; + } + if (style.Alpha <= 0.0f) + window->Active = false; + + // Return false if we don't intend to display anything to allow user to perform an early out optimization + window->SkipItems = (window->Collapsed || !window->Active) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0; + return !window->SkipItems; +} + +// Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()+Begin() instead. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override, ImGuiWindowFlags flags) +{ + // Old API feature: we could pass the initial window size as a parameter, however this was very misleading because in most cases it would only affect the window when it didn't have storage in the .ini file. + if (size_on_first_use.x != 0.0f || size_on_first_use.y != 0.0f) + SetNextWindowSize(size_on_first_use, ImGuiCond_FirstUseEver); + + // Old API feature: we could override the window background alpha with a parameter. This is actually tricky to reproduce manually because: + // (1) there are multiple variants of WindowBg (popup, tooltip, etc.) and (2) you can't call PushStyleColor before Begin and PopStyleColor just after Begin() because of how CheckStackSizes() behave. + // The user-side solution is to do backup = GetStyleColorVec4(ImGuiCol_xxxBG), PushStyleColor(ImGuiCol_xxxBg), Begin, PushStyleColor(ImGuiCol_xxxBg, backup), [...], PopStyleColor(), End(); PopStyleColor() - which is super awkward. + // The alpha override was rarely used but for now we'll leave the Begin() variant around for a bit. We may either lift the constraint on CheckStackSizes() either add a SetNextWindowBgAlpha() helper that does it magically. + ImGuiContext& g = *GImGui; + const ImGuiCol bg_color_idx = GetWindowBgColorIdxFromFlags(flags); + const ImVec4 bg_color_backup = g.Style.Colors[bg_color_idx]; + if (bg_alpha_override >= 0.0f) + g.Style.Colors[bg_color_idx].w = bg_alpha_override; + + bool ret = Begin(name, p_open, flags); + + if (bg_alpha_override >= 0.0f) + g.Style.Colors[bg_color_idx] = bg_color_backup; + return ret; +} +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +void ImGui::End() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (window->DC.ColumnsSet != NULL) + EndColumns(); + PopClipRect(); // inner window clip rectangle + + // Stop logging + if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + LogFinish(); + + // Pop + // NB: we don't clear 'window->RootWindow'. The pointer is allowed to live until the next call to Begin(). + g.CurrentWindowStack.pop_back(); + if (window->Flags & ImGuiWindowFlags_Popup) + g.CurrentPopupStack.pop_back(); + CheckStacksSize(window, false); + SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); +} + +// Vertical scrollbar +// The entire piece of code below is rather confusing because: +// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) +// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar +// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. +void ImGui::Scrollbar(ImGuiLayoutType direction) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const bool horizontal = (direction == ImGuiLayoutType_Horizontal); + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); + + // Render background + bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); + float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; + const ImRect window_rect = window->Rect(); + const float border_size = window->WindowBorderSize; + ImRect bb = horizontal + ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size) + : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size); + if (!horizontal) + bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f); + if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) + return; + + int window_rounding_corners; + if (horizontal) + window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + else + window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners); + bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f))); + + // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) + float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight(); + float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y; + float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w; + float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y; + + // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) + // But we maintain a minimum size in pixel to allow for the user to still aim inside. + IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f); + const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v); + const float grab_h_norm = grab_h_pixels / scrollbar_size_v; + + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). + bool held = false; + bool hovered = false; + const bool previously_held = (g.ActiveId == id); + ButtonBehavior(bb, id, &hovered, &held); + + float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); + float scroll_ratio = ImSaturate(scroll_v / scroll_max); + float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + if (held && grab_h_norm < 1.0f) + { + float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y; + float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y; + + // Click position in scrollbar normalized space (0.0f->1.0f) + const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); + SetHoveredID(id); + + bool seek_absolute = false; + if (!previously_held) + { + // On initial click calculate the distance between mouse and the center of the grab + if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) + { + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + else + { + seek_absolute = true; + *click_delta_to_grab_center_v = 0.0f; + } + } + + // Apply scroll + // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position + const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm)); + scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); + if (horizontal) + window->Scroll.x = scroll_v; + else + window->Scroll.y = scroll_v; + + // Update values for rendering + scroll_ratio = ImSaturate(scroll_v / scroll_max); + grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + + // Update distance to grab now that we have seeked and saturated + if (seek_absolute) + *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; + } + + // Render + const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); + ImRect grab_rect; + if (horizontal) + grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y); + else + grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y)); + window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); +} + +void ImGui::BringWindowToFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows.back() == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + g.Windows.erase(g.Windows.begin() + i); + g.Windows.push_back(window); + break; + } +} + +void ImGui::BringWindowToBack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); + g.Windows[0] = window; + break; + } +} + +// Moving window to front of display and set focus (which happens to be back of our sorted list) +void ImGui::FocusWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + // Always mark the window we passed as focused. This is used for keyboard interactions such as tabbing. + g.NavWindow = window; + + // Passing NULL allow to disable keyboard focus + if (!window) + return; + + // Move the root window to the top of the pile + if (window->RootWindow) + window = window->RootWindow; + + // Steal focus on active widgets + if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it.. + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window) + ClearActiveID(); + + // Bring to front + if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) + BringWindowToFront(window); +} + +void ImGui::FocusPreviousWindow() +{ + ImGuiContext& g = *GImGui; + for (int i = g.Windows.Size - 1; i >= 0; i--) + if (g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) + { + FocusWindow(g.Windows[i]); + return; + } +} + +void ImGui::PushItemWidth(float item_width) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); +} + +void ImGui::PushMultiItemsWidths(int components, float w_full) +{ + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = GImGui->Style; + if (w_full <= 0.0f) + w_full = CalcItemWidth(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + window->DC.ItemWidthStack.push_back(w_item_last); + for (int i = 0; i < components-1; i++) + window->DC.ItemWidthStack.push_back(w_item_one); + window->DC.ItemWidth = window->DC.ItemWidthStack.back(); +} + +void ImGui::PopItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemWidthStack.pop_back(); + window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back(); +} + +float ImGui::CalcItemWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + float w = window->DC.ItemWidth; + if (w < 0.0f) + { + // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well. + float width_to_right_edge = GetContentRegionAvail().x; + w = ImMax(1.0f, width_to_right_edge + w); + } + w = (float)(int)w; + return w; +} + +static ImFont* GetDefaultFont() +{ + ImGuiContext& g = *GImGui; + return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; +} + +static void SetCurrentFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(font->Scale > 0.0f); + g.Font = font; + g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; + g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + + ImFontAtlas* atlas = g.Font->ContainerAtlas; + g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; + g.DrawListSharedData.Font = g.Font; + g.DrawListSharedData.FontSize = g.FontSize; +} + +void ImGui::PushFont(ImFont* font) +{ + ImGuiContext& g = *GImGui; + if (!font) + font = GetDefaultFont(); + SetCurrentFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DrawList->PopTextureID(); + g.FontStack.pop_back(); + SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); +} + +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (enabled) + window->DC.ItemFlags |= option; + else + window->DC.ItemFlags &= ~option; + window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); +} + +void ImGui::PopItemFlag() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ItemFlagsStack.pop_back(); + window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back(); +} + +void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) +{ + PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus); +} + +void ImGui::PopAllowKeyboardFocus() +{ + PopItemFlag(); +} + +void ImGui::PushButtonRepeat(bool repeat) +{ + PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); +} + +void ImGui::PopButtonRepeat() +{ + PopItemFlag(); +} + +void ImGui::PushTextWrapPos(float wrap_pos_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPos = wrap_pos_x; + window->DC.TextWrapPosStack.push_back(wrap_pos_x); +} + +void ImGui::PopTextWrapPos() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPosStack.pop_back(); + window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back(); +} + +// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32 +void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) +{ + ImGuiContext& g = *GImGui; + ImGuiColMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); +} + +void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) +{ + ImGuiContext& g = *GImGui; + ImGuiColMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorModifiers.push_back(backup); + g.Style.Colors[idx] = col; +} + +void ImGui::PopStyleColor(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiColMod& backup = g.ColorModifiers.back(); + g.Style.Colors[backup.Col] = backup.BackupValue; + g.ColorModifiers.pop_back(); + count--; + } +} + +struct ImGuiStyleVarInfo +{ + ImGuiDataType Type; + ImU32 Offset; + void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); } +}; + +static const ImGuiStyleVarInfo GStyleVarInfo[ImGuiStyleVar_Count_] = +{ + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign +}; + +static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) +{ + IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_Count_); + return &GStyleVarInfo[idx]; +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float) + { + ImGuiContext& g = *GImGui; + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a float. +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) +{ + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float2) + { + ImGuiContext& g = *GImGui; + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2. +} + +void ImGui::PopStyleVar(int count) +{ + ImGuiContext& g = *GImGui; + while (count > 0) + { + ImGuiStyleMod& backup = g.StyleModifiers.back(); + const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); + if (info->Type == ImGuiDataType_Float) (*(float*)info->GetVarPtr(&g.Style)) = backup.BackupFloat[0]; + else if (info->Type == ImGuiDataType_Float2) (*(ImVec2*)info->GetVarPtr(&g.Style)) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]); + else if (info->Type == ImGuiDataType_Int) (*(int*)info->GetVarPtr(&g.Style)) = backup.BackupInt[0]; + g.StyleModifiers.pop_back(); + count--; + } +} + +const char* ImGui::GetStyleColorName(ImGuiCol idx) +{ + // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; + switch (idx) + { + case ImGuiCol_Text: return "Text"; + case ImGuiCol_TextDisabled: return "TextDisabled"; + case ImGuiCol_WindowBg: return "WindowBg"; + case ImGuiCol_ChildBg: return "ChildBg"; + case ImGuiCol_PopupBg: return "PopupBg"; + case ImGuiCol_Border: return "Border"; + case ImGuiCol_BorderShadow: return "BorderShadow"; + case ImGuiCol_FrameBg: return "FrameBg"; + case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; + case ImGuiCol_FrameBgActive: return "FrameBgActive"; + case ImGuiCol_TitleBg: return "TitleBg"; + case ImGuiCol_TitleBgActive: return "TitleBgActive"; + case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; + case ImGuiCol_MenuBarBg: return "MenuBarBg"; + case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; + case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; + case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; + case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; + case ImGuiCol_CheckMark: return "CheckMark"; + case ImGuiCol_SliderGrab: return "SliderGrab"; + case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; + case ImGuiCol_Button: return "Button"; + case ImGuiCol_ButtonHovered: return "ButtonHovered"; + case ImGuiCol_ButtonActive: return "ButtonActive"; + case ImGuiCol_Header: return "Header"; + case ImGuiCol_HeaderHovered: return "HeaderHovered"; + case ImGuiCol_HeaderActive: return "HeaderActive"; + case ImGuiCol_Separator: return "Separator"; + case ImGuiCol_SeparatorHovered: return "SeparatorHovered"; + case ImGuiCol_SeparatorActive: return "SeparatorActive"; + case ImGuiCol_ResizeGrip: return "ResizeGrip"; + case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; + case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_CloseButton: return "CloseButton"; + case ImGuiCol_CloseButtonHovered: return "CloseButtonHovered"; + case ImGuiCol_CloseButtonActive: return "CloseButtonActive"; + case ImGuiCol_PlotLines: return "PlotLines"; + case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; + case ImGuiCol_PlotHistogram: return "PlotHistogram"; + case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; + case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; + case ImGuiCol_ModalWindowDarkening: return "ModalWindowDarkening"; + case ImGuiCol_DragDropTarget: return "DragDropTarget"; + } + IM_ASSERT(0); + return "Unknown"; +} + +bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) +{ + if (window->RootWindow == potential_parent) + return true; + while (window != NULL) + { + if (window == potential_parent) + return true; + window = window->ParentWindow; + } + return false; +} + +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) +{ + IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function + ImGuiContext& g = *GImGui; + switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) + { + case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: + if (g.HoveredRootWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_RootWindow: + if (g.HoveredWindow != g.CurrentWindow->RootWindow) + return false; + break; + case ImGuiHoveredFlags_ChildWindows: + if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow)) + return false; + break; + default: + if (g.HoveredWindow != g.CurrentWindow) + return false; + break; + } + + if (!IsWindowContentHoverable(g.HoveredRootWindow, flags)) + return false; + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId) + return false; + return true; +} + +bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End() + + switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) + { + case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && g.CurrentWindow->RootWindow == g.NavWindow->RootWindow; + case ImGuiFocusedFlags_RootWindow: + return g.CurrentWindow->RootWindow == g.NavWindow; + case ImGuiFocusedFlags_ChildWindows: + return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); + default: + return g.CurrentWindow == g.NavWindow; + } +} + +float ImGui::GetWindowWidth() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.x; +} + +float ImGui::GetWindowHeight() +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->Size.y; +} + +ImVec2 ImGui::GetWindowPos() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + return window->Pos; +} + +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) +{ + window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. + window->Scroll.y = new_scroll_y; + window->DC.CursorMaxPos.y -= window->Scroll.y; +} + +static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowPosAllowFlags & cond) == 0) + return; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); + + // Set + const ImVec2 old_pos = window->Pos; + window->PosFloat = pos; + window->Pos = ImVec2((float)(int)window->PosFloat.x, (float)(int)window->PosFloat.y); + window->DC.CursorPos += (window->Pos - old_pos); // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor + window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected. +} + +void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + SetWindowPos(window, pos, cond); +} + +void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowPos(window, pos, cond); +} + +ImVec2 ImGui::GetWindowSize() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Size; +} + +static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) + return; + window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + if (size.x > 0.0f) + { + window->AutoFitFramesX = 0; + window->SizeFull.x = size.x; + } + else + { + window->AutoFitFramesX = 2; + window->AutoFitOnlyGrows = false; + } + if (size.y > 0.0f) + { + window->AutoFitFramesY = 0; + window->SizeFull.y = size.y; + } + else + { + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } +} + +void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) +{ + SetWindowSize(GImGui->CurrentWindow, size, cond); +} + +void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowSize(window, size, cond); +} + +static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) + return; + window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + // Set + window->Collapsed = collapsed; +} + +void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); +} + +bool ImGui::IsWindowCollapsed() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Collapsed; +} + +bool ImGui::IsWindowAppearing() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->Appearing; +} + +void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) +{ + if (ImGuiWindow* window = FindWindowByName(name)) + SetWindowCollapsed(window, collapsed, cond); +} + +void ImGui::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char* name) +{ + if (name) + { + if (ImGuiWindow* window = FindWindowByName(name)) + FocusWindow(window); + } + else + { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) +{ + ImGuiContext& g = *GImGui; + g.SetNextWindowPosVal = pos; + g.SetNextWindowPosPivot = pivot; + g.SetNextWindowPosCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.SetNextWindowSizeVal = size; + g.SetNextWindowSizeCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeConstraintCallback custom_callback, void* custom_callback_user_data) +{ + ImGuiContext& g = *GImGui; + g.SetNextWindowSizeConstraint = true; + g.SetNextWindowSizeConstraintRect = ImRect(size_min, size_max); + g.SetNextWindowSizeConstraintCallback = custom_callback; + g.SetNextWindowSizeConstraintCallbackUserData = custom_callback_user_data; +} + +void ImGui::SetNextWindowContentSize(const ImVec2& size) +{ + ImGuiContext& g = *GImGui; + g.SetNextWindowContentSizeVal = size; // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value. + g.SetNextWindowContentSizeCond = ImGuiCond_Always; +} + +void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.SetNextWindowCollapsedVal = collapsed; + g.SetNextWindowCollapsedCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext& g = *GImGui; + g.SetNextWindowFocus = true; +} + +// In window space (not screen space!) +ImVec2 ImGui::GetContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImVec2 mx = window->ContentsRegionRect.Max; + if (window->DC.ColumnsSet) + mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x; + return mx; +} + +ImVec2 ImGui::GetContentRegionAvail() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return GetContentRegionMax() - (window->DC.CursorPos - window->Pos); +} + +float ImGui::GetContentRegionAvailWidth() +{ + return GetContentRegionAvail().x; +} + +// In window space (not screen space!) +ImVec2 ImGui::GetWindowContentRegionMin() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Min; +} + +ImVec2 ImGui::GetWindowContentRegionMax() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max; +} + +float ImGui::GetWindowContentRegionWidth() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ContentsRegionRect.Max.x - window->ContentsRegionRect.Min.x; +} + +float ImGui::GetTextLineHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize; +} + +float ImGui::GetTextLineHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.ItemSpacing.y; +} + +float ImGui::GetFrameHeight() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f; +} + +float ImGui::GetFrameHeightWithSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; +} + +ImDrawList* ImGui::GetWindowDrawList() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DrawList; +} + +ImFont* ImGui::GetFont() +{ + return GImGui->Font; +} + +float ImGui::GetFontSize() +{ + return GImGui->FontSize; +} + +ImVec2 ImGui::GetFontTexUvWhitePixel() +{ + return GImGui->DrawListSharedData.TexUvWhitePixel; +} + +void ImGui::SetWindowFontScale(float scale) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->FontWindowScale = scale; + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); +} + +// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. +// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'. +ImVec2 ImGui::GetCursorPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos - window->Pos + window->Scroll; +} + +float ImGui::GetCursorPosX() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; +} + +float ImGui::GetCursorPosY() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; +} + +void ImGui::SetCursorPos(const ImVec2& local_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = window->Pos - window->Scroll + local_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +void ImGui::SetCursorPosX(float x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); +} + +void ImGui::SetCursorPosY(float y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); +} + +ImVec2 ImGui::GetCursorStartPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorStartPos - window->Pos; +} + +ImVec2 ImGui::GetCursorScreenPos() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CursorPos; +} + +void ImGui::SetCursorScreenPos(const ImVec2& screen_pos) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.CursorPos = screen_pos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); +} + +float ImGui::GetScrollX() +{ + return GImGui->CurrentWindow->Scroll.x; +} + +float ImGui::GetScrollY() +{ + return GImGui->CurrentWindow->Scroll.y; +} + +float ImGui::GetScrollMaxX() +{ + return GetScrollMaxX(GImGui->CurrentWindow); +} + +float ImGui::GetScrollMaxY() +{ + return GetScrollMaxY(GImGui->CurrentWindow); +} + +void ImGui::SetScrollX(float scroll_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.x = scroll_x; + window->ScrollTargetCenterRatio.x = 0.0f; +} + +void ImGui::SetScrollY(float scroll_y) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY + window->ScrollTargetCenterRatio.y = 0.0f; +} + +void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) +{ + // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); + window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y); + window->ScrollTargetCenterRatio.y = center_y_ratio; + + // Minor hack to to make scrolling to top/bottom of window take account of WindowPadding, it looks more right to the user this way + if (center_y_ratio <= 0.0f && window->ScrollTarget.y <= window->WindowPadding.y) + window->ScrollTarget.y = 0.0f; + else if (center_y_ratio >= 1.0f && window->ScrollTarget.y >= window->SizeContents.y - window->WindowPadding.y + GImGui->Style.ItemSpacing.y) + window->ScrollTarget.y = window->SizeContents.y; +} + +// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. +void ImGui::SetScrollHere(float center_y_ratio) +{ + ImGuiWindow* window = GetCurrentWindow(); + float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space + target_y += (window->DC.PrevLineHeight * center_y_ratio) + (GImGui->Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line. + SetScrollFromPosY(target_y, center_y_ratio); +} + +// FIXME-NAV: This function is a placeholder for the upcoming Navigation branch + Focusing features. +// In the current branch this function will only set the scrolling, in the navigation branch it will also set your navigation cursor. +// Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable. +void ImGui::SetItemDefaultFocus() +{ + if (IsWindowAppearing()) + SetScrollHere(); +} + +void ImGui::SetKeyboardFocusHere(int offset) +{ + IM_ASSERT(offset >= -1); // -1 is allowed but not below + ImGuiWindow* window = GetCurrentWindow(); + window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset; + window->FocusIdxTabRequestNext = INT_MAX; +} + +void ImGui::SetStateStorage(ImGuiStorage* tree) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.StateStorage = tree ? tree : &window->StateStorage; +} + +ImGuiStorage* ImGui::GetStateStorage() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.StateStorage; +} + +void ImGui::TextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + TextUnformatted(g.TempBuffer, text_end); +} + +void ImGui::Text(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextV(fmt, args); + va_end(args); +} + +void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, col); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextColoredV(col, fmt, args); + va_end(args); +} + +void ImGui::TextDisabledV(const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextDisabled(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + +void ImGui::TextWrappedV(const char* fmt, va_list args) +{ + bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set + if (need_wrap) PushTextWrapPos(0.0f); + TextV(fmt, args); + if (need_wrap) PopTextWrapPos(); +} + +void ImGui::TextWrapped(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextWrappedV(fmt, args); + va_end(args); +} + +void ImGui::TextUnformatted(const char* text, const char* text_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + IM_ASSERT(text != NULL); + const char* text_begin = text; + if (text_end == NULL) + text_end = text + strlen(text); // FIXME-OPT + + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); + const float wrap_pos_x = window->DC.TextWrapPos; + const bool wrap_enabled = wrap_pos_x >= 0.0f; + if (text_end - text > 2000 && !wrap_enabled) + { + // Long text! + // Perform manual coarse clipping to optimize for long multi-line text + // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. + // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. + const char* line = text; + const float line_height = GetTextLineHeight(); + const ImRect clip_rect = window->ClipRect; + ImVec2 text_size(0,0); + + if (text_pos.y <= clip_rect.Max.y) + { + ImVec2 pos = text_pos; + + // Lines to skip (can't skip when logging text) + if (!g.LogEnabled) + { + int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height); + if (lines_skippable > 0) + { + int lines_skipped = 0; + while (line < text_end && lines_skipped < lines_skippable) + { + const char* line_end = strchr(line, '\n'); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + } + + // Lines to render + if (line < text_end) + { + ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); + while (line < text_end) + { + const char* line_end = strchr(line, '\n'); + if (IsClippedEx(line_rect, 0, false)) + break; + + const ImVec2 line_size = CalcTextSize(line, line_end, false); + text_size.x = ImMax(text_size.x, line_size.x); + RenderText(pos, line, line_end, false); + if (!line_end) + line_end = text_end; + line = line_end + 1; + line_rect.Min.y += line_height; + line_rect.Max.y += line_height; + pos.y += line_height; + } + + // Count remaining lines + int lines_skipped = 0; + while (line < text_end) + { + const char* line_end = strchr(line, '\n'); + if (!line_end) + line_end = text_end; + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + + text_size.y += (pos - text_pos).y; + } + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(bb); + ItemAdd(bb, 0); + } + else + { + const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; + const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + + // Account of baseline offset + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size); + if (!ItemAdd(bb, 0)) + return; + + // Render (we don't hide text after ## in this end-user function) + RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); + } +} + +void ImGui::AlignTextToFramePadding() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + window->DC.CurrentLineHeight = ImMax(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2); + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); +} + +// Add a label+text combo aligned to other label+value widgets +void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); + const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0)) + return; + + // Render + const char* value_text_begin = &g.TempBuffer[0]; + const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); +} + +void ImGui::LabelText(const char* label, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + LabelTextV(label, fmt, args); + va_end(args); +} + +bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + if (flags & ImGuiButtonFlags_Disabled) + { + if (out_hovered) *out_hovered = false; + if (out_held) *out_held = false; + if (g.ActiveId == id) ClearActiveID(); + return false; + } + + // Default behavior requires click+release on same spot + if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) + flags |= ImGuiButtonFlags_PressedOnClickRelease; + + ImGuiWindow* backup_hovered_window = g.HoveredWindow; + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = window; + + bool pressed = false; + bool hovered = ItemHoverable(bb, id); + + // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && g.DragDropActive && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + { + hovered = true; + SetHoveredID(id); + if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy + { + pressed = true; + FocusWindow(window); + } + } + + if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) + g.HoveredWindow = backup_hovered_window; + + // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one. + if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) + hovered = false; + + if (hovered) + { + if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) + { + // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat + // PressedOnClickRelease | * | .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds + // PressedOnClick | | .. + // PressedOnRelease | | .. (NOT on release) + // PressedOnDoubleClick | | .. + if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) + { + SetActiveID(id, window); // Hold on ID + FocusWindow(window); + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + } + if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) + { + pressed = true; + if (flags & ImGuiButtonFlags_NoHoldingActiveID) + { + ClearActiveID(); + } + else + { + SetActiveID(id, window); // Hold on ID + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + } + FocusWindow(window); + } + if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) + { + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + pressed = true; + ClearActiveID(); + } + + // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). + // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. + if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true)) + pressed = true; + } + } + + bool held = false; + if (g.ActiveId == id) + { + if (g.IO.MouseDown[0]) + { + held = true; + } + else + { + if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) + if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps + if (!g.DragDropActive) + pressed = true; + ClearActiveID(); + } + } + + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + + return pressed; +} + +bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImVec2 pos = window->DC.CursorPos; + if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + + const ImRect bb(pos, pos + size); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + return pressed; +} + +bool ImGui::Button(const char* label, const ImVec2& size_arg) +{ + return ButtonEx(label, size_arg, 0); +} + +// Small buttons fits within text without additional vertical spacing. +bool ImGui::SmallButton(const char* label) +{ + ImGuiContext& g = *GImGui; + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label, ImVec2(0,0), ImGuiButtonFlags_AlignTextBaseLine); + g.Style.FramePadding.y = backup_padding_y; + return pressed; +} + +// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. +// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) +bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiID id = window->GetID(str_id); + ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + return pressed; +} + +// Upper-right button to close a window. +bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) +{ + ImGuiWindow* window = GetCurrentWindow(); + + const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius)); + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton); + const ImVec2 center = bb.GetCenter(); + window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), col, 12); + + const float cross_extent = (radius * 0.7071f) - 1.0f; + if (hovered) + { + window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), GetColorU32(ImGuiCol_Text)); + window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), GetColorU32(ImGuiCol_Text)); + } + + return pressed; +} + +// [Internal] +bool ImGui::ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + padding.x * 2.0f, g.FontSize + padding.y * 2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); +#ifdef IMGUI_HAS_NAV + RenderNavHighlight(bb, id); +#endif + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTriangle(bb.Min + padding, dir, 1.0f); + + return pressed; +} + +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + if (border_col.w > 0.0f) + bb.Max += ImVec2(2,2); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + if (border_col.w > 0.0f) + { + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); + window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, GetColorU32(tint_col)); + } + else + { + window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); + } +} + +// frame_padding < 0: uses FramePadding from style (default) +// frame_padding = 0: no framing +// frame_padding > 0: set framing size +// The color used are the button colors. +bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. + PushID((void *)user_texture_id); + const ImGuiID id = window->GetID("#image"); + PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2); + const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + // Render + const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col)); + window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col)); + + return pressed; +} + +// Start logging ImGui output to TTY +void ImGui::LogToTTY(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + g.LogEnabled = true; + g.LogFile = stdout; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to given file +void ImGui::LogToFile(int max_depth, const char* filename) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + if (!filename) + { + filename = g.IO.LogFilename; + if (!filename) + return; + } + + g.LogFile = ImFileOpen(filename, "ab"); + if (!g.LogFile) + { + IM_ASSERT(g.LogFile != NULL); // Consider this an error + return; + } + g.LogEnabled = true; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +// Start logging ImGui output to clipboard +void ImGui::LogToClipboard(int max_depth) +{ + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + return; + ImGuiWindow* window = g.CurrentWindow; + + g.LogEnabled = true; + g.LogFile = NULL; + g.LogStartDepth = window->DC.TreeDepth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; +} + +void ImGui::LogFinish() +{ + ImGuiContext& g = *GImGui; + if (!g.LogEnabled) + return; + + LogText(IM_NEWLINE); + g.LogEnabled = false; + if (g.LogFile != NULL) + { + if (g.LogFile == stdout) + fflush(g.LogFile); + else + fclose(g.LogFile); + g.LogFile = NULL; + } + if (g.LogClipboard->size() > 1) + { + SetClipboardText(g.LogClipboard->begin()); + g.LogClipboard->clear(); + } +} + +// Helper to display logging buttons +void ImGui::LogButtons() +{ + ImGuiContext& g = *GImGui; + + PushID("LogButtons"); + const bool log_to_tty = Button("Log To TTY"); SameLine(); + const bool log_to_file = Button("Log To File"); SameLine(); + const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); + PushItemWidth(80.0f); + PushAllowKeyboardFocus(false); + SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL); + PopAllowKeyboardFocus(); + PopItemWidth(); + PopID(); + + // Start logging at the end of the function so that the buttons don't appear in the log + if (log_to_tty) + LogToTTY(g.LogAutoExpandMaxDepth); + if (log_to_file) + LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename); + if (log_to_clipboard) + LogToClipboard(g.LogAutoExpandMaxDepth); +} + +bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) +{ + if (flags & ImGuiTreeNodeFlags_Leaf) + return true; + + // We only write to the tree storage if the user clicks (or explicitely use SetNextTreeNode*** functions) + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStorage* storage = window->DC.StateStorage; + + bool is_open; + if (g.SetNextTreeNodeOpenCond != 0) + { + if (g.SetNextTreeNodeOpenCond & ImGuiCond_Always) + { + is_open = g.SetNextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. + const int stored_value = storage->GetInt(id, -1); + if (stored_value == -1) + { + is_open = g.SetNextTreeNodeOpenVal; + storage->SetInt(id, is_open); + } + else + { + is_open = stored_value != 0; + } + } + g.SetNextTreeNodeOpenCond = 0; + } + else + { + is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + } + + // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). + // NB- If we are above max depth we still allow manually opened nodes to be logged. + if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth) + is_open = true; + + return is_open; +} + +bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; + const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); + + if (!label_end) + label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + + // We vertically grow up to current line height up the typical widget height. + const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2); + ImRect bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height)); + if (display_frame) + { + // Framed header expand a little outside the default padding + bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1; + bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1; + } + + const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing + const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser + ItemSize(ImVec2(text_width, frame_height), text_base_offset_y); + + // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing + // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not) + const ImRect interact_bb = display_frame ? bb : ImRect(bb.Min.x, bb.Min.y, bb.Min.x + text_width + style.ItemSpacing.x*2, bb.Max.y); + bool is_open = TreeNodeBehaviorIsOpen(id, flags); + if (!ItemAdd(interact_bb, id)) + { + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; + } + + // Flags that affects opening behavior: + // - 0(default) ..................... single-click anywhere to open + // - OpenOnDoubleClick .............. double-click anywhere to open + // - OpenOnArrow .................... single-click on arrow to open + // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open + ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0); + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0); + + bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + if (pressed && !(flags & ImGuiTreeNodeFlags_Leaf)) + { + bool toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)); + if (flags & ImGuiTreeNodeFlags_OpenOnArrow) + toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)); + if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + toggled |= g.IO.MouseDoubleClicked[0]; + if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. + toggled = false; + if (toggled) + { + is_open = !is_open; + window->DC.StateStorage->SetInt(id, is_open); + } + } + if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) + SetItemAllowOverlap(); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + const ImVec2 text_pos = bb.Min + ImVec2(text_offset_x, text_base_offset_y); + if (display_frame) + { + // Framed type + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTriangle(bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); + if (g.LogEnabled) + { + // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here. + const char log_prefix[] = "\n##"; + const char log_suffix[] = "##"; + LogRenderedText(&text_pos, log_prefix, log_prefix+3); + RenderTextClipped(text_pos, bb.Max, label, label_end, &label_size); + LogRenderedText(&text_pos, log_suffix+1, log_suffix+3); + } + else + { + RenderTextClipped(text_pos, bb.Max, label, label_end, &label_size); + } + } + else + { + // Unframed typed for tree nodes + if (hovered || (flags & ImGuiTreeNodeFlags_Selected)) + RenderFrame(bb.Min, bb.Max, col, false); + + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y)); + else if (!(flags & ImGuiTreeNodeFlags_Leaf)) + RenderTriangle(bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogRenderedText(&text_pos, ">"); + RenderText(text_pos, label, label_end, false); + } + + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushRawID(id); + return is_open; +} + +// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). +// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). +bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen, label); +} + +bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (p_open && !*p_open) + return false; + + ImGuiID id = window->GetID(label); + bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label); + if (p_open) + { + // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. + ImGuiContext& g = *GImGui; + float button_sz = g.FontSize * 0.5f; + ImGuiItemHoveredDataBackup last_item_backup; + if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_sz, window->DC.LastItemRect.Min.y + g.Style.FramePadding.y + button_sz), button_sz)) + *p_open = false; + last_item_backup.Restore(); + } + + return is_open; +} + +bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags, label, NULL); +} + +bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); +} + +bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) +{ + return TreeNodeExV(str_id, 0, fmt, args); +} + +bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) +{ + return TreeNodeExV(ptr_id, 0, fmt, args); +} + +bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + return TreeNodeBehavior(window->GetID(label), 0, label, NULL); +} + +void ImGui::TreeAdvanceToLabelPos() +{ + ImGuiContext& g = *GImGui; + g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing(); +} + +// Horizontal distance preceding label when using TreeNode() or Bullet() +float ImGui::GetTreeNodeToLabelSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + (g.Style.FramePadding.x * 2.0f); +} + +void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.SetNextTreeNodeOpenVal = is_open; + g.SetNextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::PushID(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(str_id)); +} + +void ImGui::PushID(const char* str_id_begin, const char* str_id_end) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(str_id_begin, str_id_end)); +} + +void ImGui::PushID(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(ptr_id)); +} + +void ImGui::PushID(int int_id) +{ + const void* ptr_id = (void*)(intptr_t)int_id; + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.push_back(window->GetID(ptr_id)); +} + +void ImGui::PopID() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + window->IDStack.pop_back(); +} + +ImGuiID ImGui::GetID(const char* str_id) +{ + return GImGui->CurrentWindow->GetID(str_id); +} + +ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) +{ + return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end); +} + +ImGuiID ImGui::GetID(const void* ptr_id) +{ + return GImGui->CurrentWindow->GetID(ptr_id); +} + +void ImGui::Bullet() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + { + SameLine(0, style.FramePadding.x*2); + return; + } + + // Render and stay on same line + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + SameLine(0, style.FramePadding.x*2); +} + +// Text with a little bullet aligned to the typical tree node. +void ImGui::BulletTextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const char* text_begin = g.TempBuffer; + const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); + const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it + const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + // Render + RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); + RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false); +} + +void ImGui::BulletText(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + BulletTextV(fmt, args); + va_end(args); +} + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size) +{ + if (data_type == ImGuiDataType_Int) + ImFormatString(buf, buf_size, display_format, *(int*)data_ptr); + else if (data_type == ImGuiDataType_Float) + ImFormatString(buf, buf_size, display_format, *(float*)data_ptr); +} + +static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size) +{ + if (data_type == ImGuiDataType_Int) + { + if (decimal_precision < 0) + ImFormatString(buf, buf_size, "%d", *(int*)data_ptr); + else + ImFormatString(buf, buf_size, "%.*d", decimal_precision, *(int*)data_ptr); + } + else if (data_type == ImGuiDataType_Float) + { + if (decimal_precision < 0) + ImFormatString(buf, buf_size, "%f", *(float*)data_ptr); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? + else + ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(float*)data_ptr); + } +} + +static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2)// Store into value1 +{ + if (data_type == ImGuiDataType_Int) + { + if (op == '+') + *(int*)value1 = *(int*)value1 + *(const int*)value2; + else if (op == '-') + *(int*)value1 = *(int*)value1 - *(const int*)value2; + } + else if (data_type == ImGuiDataType_Float) + { + if (op == '+') + *(float*)value1 = *(float*)value1 + *(const float*)value2; + else if (op == '-') + *(float*)value1 = *(float*)value1 - *(const float*)value2; + } +} + +// User can input math operators (e.g. +100) to edit a numerical values. +static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format) +{ + while (ImCharIsSpace(*buf)) + buf++; + + // We don't support '-' op because it would conflict with inputing negative value. + // Instead you can use +-100 to subtract from an existing value + char op = buf[0]; + if (op == '+' || op == '*' || op == '/') + { + buf++; + while (ImCharIsSpace(*buf)) + buf++; + } + else + { + op = 0; + } + if (!buf[0]) + return false; + + if (data_type == ImGuiDataType_Int) + { + if (!scalar_format) + scalar_format = "%d"; + int* v = (int*)data_ptr; + const int old_v = *v; + int arg0i = *v; + if (op && sscanf(initial_value_buf, scalar_format, &arg0i) < 1) + return false; + + // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision + float arg1f = 0.0f; + if (op == '+') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i + arg1f); } // Add (use "+-" to subtract) + else if (op == '*') { if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i * arg1f); } // Multiply + else if (op == '/') { if (sscanf(buf, "%f", &arg1f) == 1 && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }// Divide + else { if (sscanf(buf, scalar_format, &arg0i) == 1) *v = arg0i; } // Assign constant (read as integer so big values are not lossy) + return (old_v != *v); + } + else if (data_type == ImGuiDataType_Float) + { + // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in + scalar_format = "%f"; + float* v = (float*)data_ptr; + const float old_v = *v; + float arg0f = *v; + if (op && sscanf(initial_value_buf, scalar_format, &arg0f) < 1) + return false; + + float arg1f = 0.0f; + if (sscanf(buf, scalar_format, &arg1f) < 1) + return false; + if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) + else if (op == '*') { *v = arg0f * arg1f; } // Multiply + else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide + else { *v = arg1f; } // Assign constant + return (old_v != *v); + } + + return false; +} + +// Create text input in place of a slider (when CTRL+Clicking on slider) +// FIXME: Logic is messy and confusing. +bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen) + // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id + SetActiveID(g.ScalarAsInputTextId, window); + SetHoveredID(0); + FocusableItemUnregister(window); + + char buf[32]; + DataTypeFormatString(data_type, data_ptr, decimal_precision, buf, IM_ARRAYSIZE(buf)); + bool text_value_changed = InputTextEx(label, buf, IM_ARRAYSIZE(buf), aabb.GetSize(), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll); + if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget + { + IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID (else we'd need to store them both, which is also possible) + g.ScalarAsInputTextId = g.ActiveId; + SetHoveredID(id); + } + if (text_value_changed) + return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL); + return false; +} + +// Parse display precision back from the display format string +int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) +{ + int precision = default_precision; + while ((fmt = strchr(fmt, '%')) != NULL) + { + fmt++; + if (fmt[0] == '%') { fmt++; continue; } // Ignore "%%" + while (*fmt >= '0' && *fmt <= '9') + fmt++; + if (*fmt == '.') + { + fmt = ImAtoi(fmt + 1, &precision); + if (precision < 0 || precision > 10) + precision = default_precision; + } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; + break; + } + return precision; +} + +static float GetMinimumStepAtDecimalPrecision(int decimal_precision) +{ + static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; + return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision); +} + +float ImGui::RoundScalar(float value, int decimal_precision) +{ + // Round past decimal precision + // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0 + // FIXME: Investigate better rounding methods + if (decimal_precision < 0) + return value; + const float min_step = GetMinimumStepAtDecimalPrecision(decimal_precision); + bool negative = value < 0.0f; + value = fabsf(value); + float remainder = fmodf(value, min_step); + if (remainder <= min_step*0.5f) + value -= remainder; + else + value += (min_step - remainder); + return negative ? -value : value; +} + +static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos) +{ + if (v_min == v_max) + return 0.0f; + + const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); + const float v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); + if (is_non_linear) + { + if (v_clamped < 0.0f) + { + const float f = 1.0f - (v_clamped - v_min) / (ImMin(0.0f,v_max) - v_min); + return (1.0f - powf(f, 1.0f/power)) * linear_zero_pos; + } + else + { + const float f = (v_clamped - ImMax(0.0f,v_min)) / (v_max - ImMax(0.0f,v_min)); + return linear_zero_pos + powf(f, 1.0f/power) * (1.0f - linear_zero_pos); + } + } + + // Linear slider + return (v_clamped - v_min) / (v_max - v_min); +} + +bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiStyle& style = g.Style; + + // Draw frame + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); + const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; + + const float grab_padding = 2.0f; + const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f); + float grab_sz; + if (decimal_precision != 0) + grab_sz = ImMin(style.GrabMinSize, slider_sz); + else + grab_sz = ImMin(ImMax(1.0f * (slider_sz / ((v_min < v_max ? v_max - v_min : v_min - v_max) + 1.0f)), style.GrabMinSize), slider_sz); // Integer sliders, if possible have the grab size represent 1 unit + const float slider_usable_sz = slider_sz - grab_sz; + const float slider_usable_pos_min = (is_horizontal ? frame_bb.Min.x : frame_bb.Min.y) + grab_padding + grab_sz*0.5f; + const float slider_usable_pos_max = (is_horizontal ? frame_bb.Max.x : frame_bb.Max.y) - grab_padding - grab_sz*0.5f; + + // For logarithmic sliders that cross over sign boundary we want the exponential increase to be symmetric around 0.0f + float linear_zero_pos = 0.0f; // 0.0->1.0f + if (v_min * v_max < 0.0f) + { + // Different sign + const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power); + const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power); + linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0); + } + else + { + // Same sign + linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; + } + + // Process clicking on the slider + bool value_changed = false; + if (g.ActiveId == id) + { + bool set_new_value = false; + float clicked_t = 0.0f; + if (g.IO.MouseDown[0]) + { + const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; + clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; + if (!is_horizontal) + clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + else + { + ClearActiveID(); + } + + if (set_new_value) + { + float new_value; + if (is_non_linear) + { + // Account for logarithmic scale on both sides of the zero + if (clicked_t < linear_zero_pos) + { + // Negative: rescale to the negative range before powering + float a = 1.0f - (clicked_t / linear_zero_pos); + a = powf(a, power); + new_value = ImLerp(ImMin(v_max,0.0f), v_min, a); + } + else + { + // Positive: rescale to the positive range before powering + float a; + if (fabsf(linear_zero_pos - 1.0f) > 1.e-6f) + a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = clicked_t; + a = powf(a, power); + new_value = ImLerp(ImMax(v_min,0.0f), v_max, a); + } + } + else + { + // Linear slider + new_value = ImLerp(v_min, v_max, clicked_t); + } + + // Round past decimal precision + new_value = RoundScalar(new_value, decimal_precision); + if (*v != new_value) + { + *v = new_value; + value_changed = true; + } + } + } + + // Draw + float grab_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos); + if (!is_horizontal) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + ImRect grab_bb; + if (is_horizontal) + grab_bb = ImRect(ImVec2(grab_pos - grab_sz*0.5f, frame_bb.Min.y + grab_padding), ImVec2(grab_pos + grab_sz*0.5f, frame_bb.Max.y - grab_padding)); + else + grab_bb = ImRect(ImVec2(frame_bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f), ImVec2(frame_bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f)); + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + return value_changed; +} + +// Use power!=1.0 for logarithmic sliders. +// Adjust display_format to decorate the value with a prefix or a suffix. +// "%.3f" 1.234 +// "%5.2f secs" 01.23 secs +// "Gold: %.0f" Gold: 1 +bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + // Tabbing or CTRL-clicking on Slider turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && g.IO.MouseClicked[0])) + { + SetActiveID(id, window); + FocusWindow(window); + if (tab_focus_requested || g.IO.KeyCtrl) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); + + // Actual slider behavior + render grab + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(frame_bb, id)) + return false; + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + if (hovered && g.IO.MouseClicked[0]) + { + SetActiveID(id, window); + FocusWindow(window); + } + + // Actual slider behavior + render grab + bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision, ImGuiSliderFlags_Vertical); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + // For the vertical slider we allow centered text to overlap the frame padding + char value_buf[64]; + char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max) +{ + float v_deg = (*v_rad) * 360.0f / (2*IM_PI); + bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f); + *v_rad = v_deg * (2*IM_PI) / 360.0f; + return value_changed; +} + +bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = SliderFloat(label, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); + *v = (int)v_f; + return value_changed; +} + +bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = VSliderFloat(label, size, &v_f, (float)v_min, (float)v_max, display_format, 1.0f); + *v = (int)v_f; + return value_changed; +} + +// Add multiple sliders on 1 line for compact edition of multiple components +bool ImGui::SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderFloat("##v", &v[i], v_min, v_max, display_format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 2, v_min, v_max, display_format, power); +} + +bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 3, v_min, v_max, display_format, power); +} + +bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format, float power) +{ + return SliderFloatN(label, v, 4, v_min, v_max, display_format, power); +} + +bool ImGui::SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= SliderInt("##v", &v[i], v_min, v_max, display_format); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 2, v_min, v_max, display_format); +} + +bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 3, v_min, v_max, display_format); +} + +bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format) +{ + return SliderIntN(label, v, 4, v_min, v_max, display_format); +} + +bool ImGui::DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + bool value_changed = false; + + // Process clicking on the drag + if (g.ActiveId == id) + { + if (g.IO.MouseDown[0]) + { + if (g.ActiveIdIsJustActivated) + { + // Lock current value on click + g.DragCurrentValue = *v; + g.DragLastMouseDelta = ImVec2(0.f, 0.f); + } + + if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX) + v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio; + + float v_cur = g.DragCurrentValue; + const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f); + float adjust_delta = 0.0f; + //if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + adjust_delta = mouse_drag_delta.x - g.DragLastMouseDelta.x; + if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f) + adjust_delta *= g.DragSpeedScaleFast; + if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f) + adjust_delta *= g.DragSpeedScaleSlow; + } + adjust_delta *= v_speed; + g.DragLastMouseDelta.x = mouse_drag_delta.x; + + if (fabsf(adjust_delta) > 0.0f) + { + if (fabsf(power - 1.0f) > 0.001f) + { + // Logarithmic curve on both side of 0.0 + float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur; + float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f; + float v1 = powf(v0_abs, 1.0f / power) + (adjust_delta * v0_sign); + float v1_abs = v1 >= 0.0f ? v1 : -v1; + float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f; // Crossed sign line + v_cur = powf(v1_abs, power) * v0_sign * v1_sign; // Reapply sign + } + else + { + v_cur += adjust_delta; + } + + // Clamp + if (v_min < v_max) + v_cur = ImClamp(v_cur, v_min, v_max); + g.DragCurrentValue = v_cur; + } + + // Round to user desired precision, then apply + v_cur = RoundScalar(v_cur, decimal_precision); + if (*v != v_cur) + { + *v = v_cur; + value_changed = true; + } + } + else + { + ClearActiveID(); + } + } + + return value_changed; +} + +bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + // NB- we don't call ItemSize() yet because we may turn into a text edit box below + if (!ItemAdd(total_bb, id)) + { + ItemSize(total_bb, style.FramePadding.y); + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + + if (!display_format) + display_format = "%.3f"; + int decimal_precision = ParseFormatPrecision(display_format, 3); + + // Tabbing or CTRL-clicking on Drag turns it into an input box + bool start_text_input = false; + const bool tab_focus_requested = FocusableItemRegister(window, id); + if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0]))) + { + SetActiveID(id, window); + FocusWindow(window); + if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0]) + { + start_text_input = true; + g.ScalarAsInputTextId = 0; + } + } + if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) + return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision); + + // Actual drag behavior + ItemSize(total_bb, style.FramePadding.y); + const bool value_changed = DragBehavior(frame_bb, id, v, v_speed, v_min, v_max, decimal_precision, power); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + + return value_changed; +} + +bool ImGui::DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 2, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 3, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* display_format, float power) +{ + return DragFloatN(label, v, 4, v_speed, v_min, v_max, display_format, power); +} + +bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* display_format, const char* display_format_max, float power) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), display_format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, display_format_max ? display_format_max : display_format, power); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +// NB: v_speed is float to allow adjusting the drag speed with more precision +bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format) +{ + if (!display_format) + display_format = "%.0f"; + float v_f = (float)*v; + bool value_changed = DragFloat(label, &v_f, v_speed, (float)v_min, (float)v_max, display_format); + *v = (int)v_f; + return value_changed; +} + +bool ImGui::DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= DragInt("##v", &v[i], v_speed, v_min, v_max, display_format); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 2, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 3, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* display_format) +{ + return DragIntN(label, v, 4, v_speed, v_min, v_max, display_format); +} + +bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* display_format, const char* display_format_max) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2); + + bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), display_format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, display_format_max ? display_format_max : display_format); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + if (graph_size.x == 0.0f) + graph_size.x = CalcItemWidth(); + if (graph_size.y == 0.0f) + graph_size.y = label_size.y + (style.FramePadding.y * 2); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y)); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0)) + return; + const bool hovered = ItemHoverable(inner_bb, 0); + + // Determine scale from values if not specified + if (scale_min == FLT_MAX || scale_max == FLT_MAX) + { + float v_min = FLT_MAX; + float v_max = -FLT_MAX; + for (int i = 0; i < values_count; i++) + { + const float v = values_getter(data, i); + v_min = ImMin(v_min, v); + v_max = ImMax(v_max, v); + } + if (scale_min == FLT_MAX) + scale_min = v_min; + if (scale_max == FLT_MAX) + scale_max = v_max; + } + + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + if (values_count > 0) + { + int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + + // Tooltip on hover + int v_hovered = -1; + if (hovered) + { + const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); + const int v_idx = (int)(t * item_count); + IM_ASSERT(v_idx >= 0 && v_idx < values_count); + + const float v0 = values_getter(data, (v_idx + values_offset) % values_count); + const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); + if (plot_type == ImGuiPlotType_Lines) + SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1); + else if (plot_type == ImGuiPlotType_Histogram) + SetTooltip("%d: %8.4g", v_idx, v0); + v_hovered = v_idx; + } + + const float t_step = 1.0f / (float)res_w; + + float v0 = values_getter(data, (0 + values_offset) % values_count); + float t0 = 0.0f; + ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) / (scale_max - scale_min)) ); // Point in the normalized space of our target rectangle + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min / (scale_max - scale_min)) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + + const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); + const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); + + for (int n = 0; n < res_w; n++) + { + const float t1 = t0 + t_step; + const int v1_idx = (int)(t0 * item_count + 0.5f); + IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); + const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); + const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) / (scale_max - scale_min)) ); + + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); + ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); + if (plot_type == ImGuiPlotType_Lines) + { + window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + else if (plot_type == ImGuiPlotType_Histogram) + { + if (pos1.x >= pos0.x + 2.0f) + pos1.x -= 1.0f; + window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); + } + + t0 = t1; + tp0 = tp1; + } + } + + // Text overlay + if (overlay_text) + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); +} + +struct ImGuiPlotArrayGetterData +{ + const float* Values; + int Stride; + + ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } +}; + +static float Plot_ArrayGetter(void* data, int idx) +{ + ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; + const float v = *(float*)(void*)((unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); + return v; +} + +void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size +void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + ImVec2 pos = window->DC.CursorPos; + ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, 0)) + return; + + // Render + fraction = ImSaturate(fraction); + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + char overlay_buf[32]; + if (!overlay) + { + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f); + overlay = overlay_buf; + } + + ImVec2 overlay_size = CalcTextSize(overlay, NULL); + if (overlay_size.x > 0.0f) + RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb); +} + +bool ImGui::Checkbox(const char* label, bool* v) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); + } + + if (!ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + *v = !(*v); + + RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + if (*v) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +{ + bool v = ((*flags & flags_value) == flags_value); + bool pressed = Checkbox(label, &v); + if (pressed) + { + if (v) + *flags |= flags_value; + else + *flags &= ~flags_value; + } + + return pressed; +} + +bool ImGui::RadioButton(const char* label, bool active) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1)); + ItemSize(check_bb, style.FramePadding.y); + + ImRect total_bb = check_bb; + if (label_size.x > 0) + SameLine(0, style.ItemInnerSpacing.x); + const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size); + if (label_size.x > 0) + { + ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); + total_bb.Add(text_bb); + } + + if (!ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = (float)(int)center.x + 0.5f; + center.y = (float)(int)center.y + 0.5f; + const float radius = check_bb.GetHeight() * 0.5f; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + + window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); + if (active) + { + const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); + const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); + } + + if (g.LogEnabled) + LogRenderedText(&text_bb.Min, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(text_bb.Min, label); + + return pressed; +} + +bool ImGui::RadioButton(const char* label, int* v, int v_button) +{ + const bool pressed = RadioButton(label, *v == v_button); + if (pressed) + { + *v = v_button; + } + return pressed; +} + +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding + if (c == '\n') + line_count++; + s--; + if (s[0] != '\n' && s[0] != '\r') + line_count++; + *out_text_end = s; + return line_count; +} + +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) +{ + ImFont* font = GImGui->Font; + const float line_height = GImGui->FontSize; + const float scale = line_height / font->FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const ImWchar* s = text_begin; + while (s < text_end) + { + unsigned int c = (unsigned int)(*s++); + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = font->GetCharAdvance((unsigned short)c) * scale; + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) +namespace ImGuiStb +{ + +static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } +static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); } +static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } +static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) +{ + const ImWchar* text = obj->Text.Data; + const ImWchar* text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +static bool is_separator(unsigned int c) { return ImCharIsSpace(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } +static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->Text[idx-1] ) && !is_separator( obj->Text[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } +#ifdef __APPLE__ // FIXME: Move setting to IO structure +static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->Text[idx-1] ) && is_separator( obj->Text[idx] ) ) : 1; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } +#else +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +#endif +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + +static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) +{ + ImWchar* dst = obj->Text.Data + pos; + + // We maintain our buffer length in both UTF-8 and wchar formats + obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); + obj->CurLenW -= n; + + // Offset remaining text + const ImWchar* src = obj->Text.Data + pos + n; + while (ImWchar c = *src++) + *dst++ = c; + *dst = '\0'; +} + +static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) +{ + const int text_len = obj->CurLenW; + IM_ASSERT(pos <= text_len); + if (new_text_len + text_len + 1 > obj->Text.Size) + return false; + + const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); + if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA) + return false; + + ImWchar* text = obj->Text.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + + obj->CurLenW += new_text_len; + obj->CurLenA += new_text_len_utf8; + obj->Text[obj->CurLenW] = '\0'; + + return true; +} + +// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) +#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left +#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right +#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_SHIFT 0x20000 + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "stb_textedit.h" + +} + +void ImGuiTextEditState::OnKeyPressed(int key) +{ + stb_textedit_key(this, &StbState, key); + CursorFollow = true; + CursorAnimReset(); +} + +// Public API to manipulate UTF-8 text +// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) +{ + IM_ASSERT(pos + bytes_count <= BufTextLen); + char* dst = Buf + pos; + const char* src = Buf + pos + bytes_count; + while (char c = *src++) + *dst++ = c; + *dst = '\0'; + + if (CursorPos + bytes_count >= pos) + CursorPos -= bytes_count; + else if (CursorPos >= pos) + CursorPos = pos; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen -= bytes_count; +} + +void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) +{ + const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); + if (new_text_len + BufTextLen + 1 >= BufSize) + return; + + if (BufTextLen != pos) + memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); + memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); + Buf[BufTextLen + new_text_len] = '\0'; + + if (CursorPos >= pos) + CursorPos += new_text_len; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen += new_text_len; +} + +// Return false to discard a character. +static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + unsigned int c = *p_char; + + if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) + { + bool pass = false; + pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); + pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); + if (!pass) + return false; + } + + if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys. + return false; + + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank)) + { + if (flags & ImGuiInputTextFlags_CharsDecimal) + if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/')) + return false; + + if (flags & ImGuiInputTextFlags_CharsHexadecimal) + if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) + return false; + + if (flags & ImGuiInputTextFlags_CharsUppercase) + if (c >= 'a' && c <= 'z') + *p_char = (c += (unsigned int)('A'-'a')); + + if (flags & ImGuiInputTextFlags_CharsNoBlank) + if (ImCharIsSpace(c)) + return false; + } + + if (flags & ImGuiInputTextFlags_CallbackCharFilter) + { + ImGuiTextEditCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); + callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + callback_data.EventChar = (ImWchar)c; + callback_data.Flags = flags; + callback_data.UserData = user_data; + if (callback(&callback_data) != 0) + return false; + *p_char = callback_data.EventChar; + if (!callback_data.EventChar) + return false; + } + + return true; +} + +// Edit a string of text +// NB: when active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while active has no effect. +// FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188 +bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + + ImGuiContext& g = *GImGui; + const ImGuiIO& io = g.IO; + const ImGuiStyle& style = g.Style; + + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; + const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; + const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + + if (is_multiline) // Open group before calling GetID() because groups tracks id created during their spawn + BeginGroup(); + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); + + ImGuiWindow* draw_window = window; + if (is_multiline) + { + if (!BeginChildFrame(id, frame_bb.GetSize())) + { + EndChildFrame(); + EndGroup(); + return false; + } + draw_window = GetCurrentWindow(); + size.x -= draw_window->ScrollbarSizes.x; + } + else + { + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id)) + return false; + } + const bool hovered = ItemHoverable(frame_bb, id); + if (hovered) + g.MouseCursor = ImGuiMouseCursor_TextInput; + + // Password pushes a temporary font with only a fallback glyph + if (is_password) + { + const ImFontGlyph* glyph = g.Font->FindGlyph('*'); + ImFont* password_font = &g.InputTextPasswordFont; + password_font->FontSize = g.Font->FontSize; + password_font->Scale = g.Font->Scale; + password_font->DisplayOffset = g.Font->DisplayOffset; + password_font->Ascent = g.Font->Ascent; + password_font->Descent = g.Font->Descent; + password_font->ContainerAtlas = g.Font->ContainerAtlas; + password_font->FallbackGlyph = glyph; + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + PushFont(password_font); + } + + // NB: we are only allowed to access 'edit_state' if we are the active widget. + ImGuiTextEditState& edit_state = g.InputTextState; + + const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing + const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); + const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; + + const bool user_clicked = hovered && io.MouseClicked[0]; + const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); + + bool clear_active_id = false; + + bool select_all = (g.ActiveId != id) && (flags & ImGuiInputTextFlags_AutoSelectAll) != 0; + if (focus_requested || user_clicked || user_scrolled) + { + if (g.ActiveId != id) + { + // Start edition + // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int prev_len_w = edit_state.CurLenW; + edit_state.Text.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + edit_state.InitialText.resize(buf_size+1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + edit_state.CursorAnimReset(); + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). + const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW); + if (recycle_state) + { + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + edit_state.CursorClamp(); + } + else + { + edit_state.Id = id; + edit_state.ScrollX = 0.0f; + stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); + if (!is_multiline && focus_requested_by_code) + select_all = true; + } + if (flags & ImGuiInputTextFlags_AlwaysInsertMode) + edit_state.StbState.insert_mode = true; + if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) + select_all = true; + } + SetActiveID(id, window); + FocusWindow(window); + } + else if (io.MouseClicked[0]) + { + // Release focus when we click outside + clear_active_id = true; + } + + bool value_changed = false; + bool enter_pressed = false; + + if (g.ActiveId == id) + { + if (!is_editable && !g.ActiveIdIsJustActivated) + { + // When read-only we always use the live data passed to the function + edit_state.Text.resize(buf_size+1); + const char* buf_end = NULL; + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end); + edit_state.CurLenA = (int)(buf_end - buf); + edit_state.CursorClamp(); + } + + edit_state.BufSizeA = buf_size; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner library-wide concept of Selected vs Active. + g.ActiveIdAllowOverlap = !io.MouseDown[0]; + g.WantTextInputNextFrame = 1; + + // Edit in progress + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; + const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); + + const bool osx_double_click_selects_words = io.OptMacOSXBehaviors; // OS X style: Double click selects by word instead of selecting whole text + if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0])) + { + edit_state.SelectAll(); + edit_state.SelectedAllMouseLock = true; + } + else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0]) + { + // Select a word only, OS X style (by simulating keystrokes) + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) + { + stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + } + else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) + { + stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + edit_state.CursorAnimReset(); + edit_state.CursorFollow = true; + } + if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) + edit_state.SelectedAllMouseLock = false; + + if (io.InputCharacters[0]) + { + // Process text input (before we check for Return because using some IME will effectively send a Return?) + // We ignore CTRL inputs, but need to allow CTRL+ALT as some keyboards (e.g. German) use AltGR - which is Alt+Ctrl - to input certain characters. + if (!(io.KeyCtrl && !io.KeyAlt) && is_editable) + { + for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++) + if (unsigned int c = (unsigned int)io.InputCharacters[n]) + { + // Insert character if they pass filtering + if (!InputTextFilterCharacter(&c, flags, callback, user_data)) + continue; + edit_state.OnKeyPressed((int)c); + } + } + + // Consume characters + memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); + } + } + + bool cancel_edit = false; + if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) + { + // Handle key-presses + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); + const bool is_shortcut_key_only = (io.OptMacOSXBehaviors ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl + const bool is_wordmove_key_down = io.OptMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl + const bool is_startend_key_down = io.OptMacOSXBehaviors && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) + { + if (!edit_state.HasSelection()) + { + if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); + else if (io.OptMacOSXBehaviors && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); + } + edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + } + else if (IsKeyPressedMap(ImGuiKey_Enter)) + { + bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + { + enter_pressed = clear_active_id = true; + } + else if (is_editable) + { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + } + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + else if (IsKeyPressedMap(ImGuiKey_Escape)) { clear_active_id = cancel_edit = true; } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable && is_undoable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } + else if (is_shortcut_key_only && !is_password && ((IsKeyPressedMap(ImGuiKey_X) && is_editable) || IsKeyPressedMap(ImGuiKey_C)) && (!is_multiline || edit_state.HasSelection())) + { + // Cut, Copy + const bool cut = IsKeyPressedMap(ImGuiKey_X); + if (cut && !edit_state.HasSelection()) + edit_state.SelectAll(); + + if (io.SetClipboardTextFn) + { + const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; + const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW; + edit_state.TempTextBuffer.resize((ie-ib) * 4 + 1); + ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data+ib, edit_state.Text.Data+ie); + SetClipboardText(edit_state.TempTextBuffer.Data); + } + + if (cut) + { + edit_state.CursorFollow = true; + stb_textedit_cut(&edit_state, &edit_state.StbState); + } + } + else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_V) && is_editable) + { + // Paste + if (const char* clipboard = GetClipboardText()) + { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar)); + int clipboard_filtered_len = 0; + for (const char* s = clipboard; *s; ) + { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, NULL); + if (c == 0) + break; + if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data)) + continue; + clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; + } + clipboard_filtered[clipboard_filtered_len] = 0; + if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { + stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); + edit_state.CursorFollow = true; + } + ImGui::MemFree(clipboard_filtered); + } + } + } + + if (g.ActiveId == id) + { + if (cancel_edit) + { + // Restore initial value + if (is_editable) + { + ImStrncpy(buf, edit_state.InitialText.Data, buf_size); + value_changed = true; + } + } + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage. + bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) + { + // Apply new value immediately - copy modified buffer back + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. + // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. + if (is_editable) + { + edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4); + ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL); + } + + // User callback + if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) + { + IM_ASSERT(callback != NULL); + + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + ImGuiInputTextFlags event_flag = 0; + ImGuiKey event_key = ImGuiKey_COUNT; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) + { + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + event_flag = ImGuiInputTextFlags_CallbackAlways; + + if (event_flag) + { + ImGuiTextEditCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData)); + callback_data.EventFlag = event_flag; + callback_data.Flags = flags; + callback_data.UserData = user_data; + callback_data.ReadOnly = !is_editable; + + callback_data.EventKey = event_key; + callback_data.Buf = edit_state.TempTextBuffer.Data; + callback_data.BufTextLen = edit_state.CurLenA; + callback_data.BufSize = edit_state.BufSizeA; + callback_data.BufDirty = false; + + // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) + ImWchar* text = edit_state.Text.Data; + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); + + // Call user code + callback(&callback_data); + + // Read back what user may have modified + IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA); + IM_ASSERT(callback_data.Flags == flags); + if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); + if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); + if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); + if (callback_data.BufDirty) + { + IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL); + edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + edit_state.CursorAnimReset(); + } + } + } + + // Copy back to user buffer + if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0) + { + ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size); + value_changed = true; + } + } + } + + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + if (clear_active_id && g.ActiveId == id) + ClearActiveID(); + + // Render + // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on. + const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL; + + if (!is_multiline) + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size + ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 text_size(0.f, 0.f); + const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY")); + if (g.ActiveId == id || is_currently_scrolling) + { + edit_state.CursorAnim += io.DeltaTime; + + // This is going to be messy. We need to: + // - Display the text (this alone can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + const ImWchar* text_begin = edit_state.Text.Data; + ImVec2 cursor_offset, select_start_offset; + + { + // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. + const ImWchar* searches_input_ptr[2]; + searches_input_ptr[0] = text_begin + edit_state.StbState.cursor; + searches_input_ptr[1] = NULL; + int searches_remaining = 1; + int searches_result_line_number[2] = { -1, -999 }; + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + searches_result_line_number[1] = -1; + searches_remaining++; + } + + // Iterate all lines to find our line numbers + // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. + searches_remaining += is_multiline ? 1 : 0; + int line_count = 0; + for (const ImWchar* s = text_begin; *s != 0; s++) + if (*s == '\n') + { + line_count++; + if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; } + } + line_count++; + if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count; + if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; + cursor_offset.y = searches_result_line_number[0] * g.FontSize; + if (searches_result_line_number[1] >= 0) + { + select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; + select_start_offset.y = searches_result_line_number[1] * g.FontSize; + } + + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) + if (is_multiline) + text_size = ImVec2(size.x, line_count * g.FontSize); + } + + // Scroll + if (edit_state.CursorFollow) + { + // Horizontal scroll in chunks of quarter width + if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) + { + const float scroll_increment_x = size.x * 0.25f; + if (cursor_offset.x < edit_state.ScrollX) + edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); + else if (cursor_offset.x - size.x >= edit_state.ScrollX) + edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); + } + else + { + edit_state.ScrollX = 0.0f; + } + + // Vertical scroll + if (is_multiline) + { + float scroll_y = draw_window->Scroll.y; + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - size.y >= scroll_y) + scroll_y = cursor_offset.y - size.y; + draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag + draw_window->Scroll.y = scroll_y; + render_pos.y = draw_window->DC.CursorPos.y; + } + } + edit_state.CursorFollow = false; + const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); + + // Draw selection + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); + + float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); + ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; + for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) + { + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) + { + while (p < text_selected_end) + if (*p++ == '\n') + break; + } + else + { + ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines + ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); + rect.ClipWith(clip_rect); + if (rect.Overlaps(clip_rect)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + } + rect_pos.x = render_pos.x - render_scroll.x; + rect_pos.y += g.FontSize; + } + } + + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + edit_state.CurLenA, 0.0f, is_multiline ? NULL : &clip_rect); + + // Draw blinking cursor + bool cursor_is_visible = (!g.IO.OptCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || fmodf(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + if (is_editable) + g.OsImePosRequest = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); + } + else + { + // Render text only + const char* buf_end = NULL; + if (is_multiline) + text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + + if (is_multiline) + { + Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line + EndChildFrame(); + EndGroup(); + } + + if (is_password) + PopFont(); + + // Log as text + if (g.LogEnabled && !is_password) + LogRenderedText(&render_pos, buf_display, NULL); + + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) + return enter_pressed; + else + return value_changed; +} + +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data); +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); +} + +// NB: scalar_format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "display_format" argument) +bool ImGui::InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + BeginGroup(); + PushID(label); + const ImVec2 button_sz = ImVec2(GetFrameHeight(), GetFrameHeight()); + if (step_ptr) + PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x)*2)); + + char buf[64]; + DataTypeFormatString(data_type, data_ptr, scalar_format, buf, IM_ARRAYSIZE(buf)); + + bool value_changed = false; + if (!(extra_flags & ImGuiInputTextFlags_CharsHexadecimal)) + extra_flags |= ImGuiInputTextFlags_CharsDecimal; + extra_flags |= ImGuiInputTextFlags_AutoSelectAll; + if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view + value_changed = DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, scalar_format); + + // Step buttons + if (step_ptr) + { + PopItemWidth(); + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '-', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + DataTypeApplyOp(data_type, '+', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr); + value_changed = true; + } + } + PopID(); + + if (label_size.x > 0) + { + SameLine(0, style.ItemInnerSpacing.x); + RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label); + ItemSize(label_size, style.FramePadding.y); + } + EndGroup(); + + return value_changed; +} + +bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + char display_format[16]; + if (decimal_precision < 0) + strcpy(display_format, "%f"); // Ideally we'd have a minimum decimal precision of 1 to visually denote that this is a float, while hiding non-significant digits? %f doesn't have a minimum of 1 + else + ImFormatString(display_format, IM_ARRAYSIZE(display_format), "%%.%df", decimal_precision); + return InputScalarEx(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), display_format, extra_flags); +} + +bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags) +{ + // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. + const char* scalar_format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; + return InputScalarEx(label, ImGuiDataType_Int, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), scalar_format, extra_flags); +} + +bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputFloat("##v", &v[i], 0, 0, decimal_precision, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 2, decimal_precision, extra_flags); +} + +bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 3, decimal_precision, extra_flags); +} + +bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + return InputFloatN(label, v, 4, decimal_precision, extra_flags); +} + +bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components); + for (int i = 0; i < components; i++) + { + PushID(i); + value_changed |= InputInt("##v", &v[i], 0, 0, extra_flags); + SameLine(0, g.Style.ItemInnerSpacing.x); + PopID(); + PopItemWidth(); + } + PopID(); + + TextUnformatted(label, FindRenderedTextEnd(label)); + EndGroup(); + + return value_changed; +} + +bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 2, extra_flags); +} + +bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 3, extra_flags); +} + +bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags) +{ + return InputIntN(label, v, 4, extra_flags); +} + +static float CalcMaxPopupHeightFromItemCount(int items_count) +{ + ImGuiContext& g = *GImGui; + if (items_count <= 0) + return FLT_MAX; + return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); +} + +bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) +{ + // Always consume the SetNextWindowSizeConstraint() call in our early return paths + ImGuiContext& g = *GImGui; + bool backup_has_next_window_size_constraint = g.SetNextWindowSizeConstraint; + g.SetNextWindowSizeConstraint = false; + + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); + bool popup_open = IsPopupOpen(id); + + const float arrow_size = GetFrameHeight(); + const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + RenderFrame(ImVec2(frame_bb.Max.x-arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING + RenderTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); + if (preview_value != NULL) + RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f)); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if (pressed && !popup_open) + { + OpenPopupEx(id, false); + popup_open = true; + } + + if (!popup_open) + return false; + + if (backup_has_next_window_size_constraint) + { + g.SetNextWindowSizeConstraint = true; + g.SetNextWindowSizeConstraintRect.Min.x = ImMax(g.SetNextWindowSizeConstraintRect.Min.x, w); + } + else + { + if ((flags & ImGuiComboFlags_HeightMask_) == 0) + flags |= ImGuiComboFlags_HeightRegular; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + int popup_max_height_in_items = -1; + if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; + else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; + else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; + SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + } + + char name[16]; + ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth + + // Peak into expected window size so we can position it + if (ImGuiWindow* popup_window = FindWindowByName(name)) + { + ImVec2 size_contents = CalcSizeContents(popup_window); + ImVec2 size_expected = CalcSizeAfterConstraint(popup_window, CalcSizeAutoFit(popup_window, size_contents)); + if (flags & ImGuiComboFlags_PopupAlignLeft) + popup_window->AutoPosLastDirection = ImGuiDir_Left; + ImVec2 pos = FindBestWindowPosForPopup(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, frame_bb, ImGuiPopupPositionPolicy_ComboBox); + SetNextWindowPos(pos); + } + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + if (!Begin(name, NULL, window_flags)) + { + EndPopup(); + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + + // Horizontally align ourselves with the framed text + if (style.FramePadding.x != style.WindowPadding.x) + Indent(style.FramePadding.x - style.WindowPadding.x); + + return true; +} + +void ImGui::EndCombo() +{ + const ImGuiStyle& style = GImGui->Style; + if (style.FramePadding.x != style.WindowPadding.x) + Unindent(style.FramePadding.x - style.WindowPadding.x); + EndPopup(); +} + +// Old API, prefer using BeginCombo() nowadays if you can. +bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items) +{ + ImGuiContext& g = *GImGui; + + const char* preview_text = NULL; + if (*current_item >= 0 && *current_item < items_count) + items_getter(data, *current_item, &preview_text); + + // The old Combo() API exposed "popup_max_height_in_items", however the new more general BeginCombo() API doesn't, so we emulate it here. + if (popup_max_height_in_items != -1 && !g.SetNextWindowSizeConstraint) + { + float popup_max_height = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); + SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, popup_max_height)); + } + + if (!BeginCombo(label, preview_text, 0)) + return false; + + // Display items + // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) + bool value_changed = false; + for (int i = 0; i < items_count; i++) + { + PushID((void*)(intptr_t)i); + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + if (Selectable(item_text, item_selected)) + { + value_changed = true; + *current_item = i; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + + EndCombo(); + return value_changed; +} + +static bool Items_ArrayGetter(void* data, int idx, const char** out_text) +{ + const char* const* items = (const char* const*)data; + if (out_text) + *out_text = items[idx]; + return true; +} + +static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) +{ + // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. + const char* items_separated_by_zeros = (const char*)data; + int items_count = 0; + const char* p = items_separated_by_zeros; + while (*p) + { + if (idx == items_count) + break; + p += strlen(p) + 1; + items_count++; + } + if (!*p) + return false; + if (out_text) + *out_text = p; + return true; +} + +// Combo box helper allowing to pass an array of strings. +bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) +{ + const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); + return value_changed; +} + +// Combo box helper allowing to pass all items in a single string. +bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) +{ + int items_count = 0; + const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open + while (*p) + { + p += strlen(p) + 1; + items_count++; + } + bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); + return value_changed; +} + +// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. +// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID. +bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped. + PopClipRect(); + + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrentLineTextBaseOffset; + ImRect bb(pos, pos + size); + ItemSize(bb); + + // Fill horizontal space. + ImVec2 window_padding = window->WindowPadding; + float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x; + float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x); + ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y); + ImRect bb_with_spacing(pos, pos + size_draw); + if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth)) + bb_with_spacing.Max.x += window_padding.x; + + // Selectables are tightly packed together, we extend the box to cover spacing between selectable. + float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); + float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); + float spacing_R = style.ItemSpacing.x - spacing_L; + float spacing_D = style.ItemSpacing.y - spacing_U; + bb_with_spacing.Min.x -= spacing_L; + bb_with_spacing.Min.y -= spacing_U; + bb_with_spacing.Max.x += spacing_R; + bb_with_spacing.Max.y += spacing_D; + if (!ItemAdd(bb_with_spacing, id)) + { + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + PushColumnClipRect(); + return false; + } + + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoHoldingActiveID; + if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnRelease; + if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled; + if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + bool hovered, held; + bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags); + if (flags & ImGuiSelectableFlags_Disabled) + selected = false; + + // Render + if (hovered || selected) + { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f); + } + + if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) + { + PushColumnClipRect(); + bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x); + } + + if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f)); + if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); + return pressed; +} + +bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + if (Selectable(label, *p_selected, flags, size_arg)) + { + *p_selected = !*p_selected; + return true; + } + return false; +} + +// Helper to calculate the size of a listbox and display a label on the right. +// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty" +bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = GetStyle(); + const ImGuiID id = GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + window->DC.LastItemRect = bb; + + BeginGroup(); + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + BeginChildFrame(id, frame_bb.GetSize()); + return true; +} + +bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) +{ + // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + // However we don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size. + // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. + if (height_in_items < 0) + height_in_items = ImMin(items_count, 7); + float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f); + + // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild(). + ImVec2 size; + size.x = 0.0f; + size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y; + return ListBoxHeader(label, size); +} + +void ImGui::ListBoxFooter() +{ + ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow; + const ImRect bb = parent_window->DC.LastItemRect; + const ImGuiStyle& style = GetStyle(); + + EndChildFrame(); + + // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect) + // We call SameLine() to restore DC.CurrentLine* data + SameLine(); + parent_window->DC.CursorPos = bb.Min; + ItemSize(bb, style.FramePadding.y); + EndGroup(); +} + +bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) +{ + const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); + return value_changed; +} + +bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) +{ + if (!ListBoxHeader(label, items_count, height_in_items)) + return false; + + // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. + bool value_changed = false; + ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const bool item_selected = (i == *current_item); + const char* item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + PushID(i); + if (Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + PopID(); + } + ListBoxFooter(); + return value_changed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImGuiSelectableFlags flags = ImGuiSelectableFlags_MenuItem | (enabled ? 0 : ImGuiSelectableFlags_Disabled); + bool pressed; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation we render neither the shortcut neither the selected tick mark + float w = label_size.x; + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); + float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f)); + if (shortcut_size.x > 0.0f) + { + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); + } + return pressed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) +{ + if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) + { + if (p_selected) + *p_selected = !*p_selected; + return true; + } + return false; +} + +bool ImGui::BeginMainMenuBar() +{ + ImGuiContext& g = *GImGui; + SetNextWindowPos(ImVec2(0.0f, 0.0f)); + SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f)); + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); + if (!Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_MenuBar) + || !BeginMenuBar()) + { + End(); + PopStyleVar(2); + return false; + } + g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x; + return true; +} + +void ImGui::EndMainMenuBar() +{ + EndMenuBar(); + End(); + PopStyleVar(2); +} + +bool ImGui::BeginMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + if (!(window->Flags & ImGuiWindowFlags_MenuBar)) + return false; + + IM_ASSERT(!window->DC.MenuBarAppending); + BeginGroup(); // Save position + PushID("##menubar"); + + // We don't clip with regular window clipping rectangle as it is already set to the area below. However we clip with window full rect. + // We remove 1 worth of rounding to Max.x to that text in long menus don't tend to display over the lower-right rounded area, which looks particularly glitchy. + ImRect bar_rect = window->MenuBarRect(); + ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f)); + clip_rect.ClipWith(window->Rect()); + PushClipRect(clip_rect.Min, clip_rect.Max, false); + + window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffsetX, bar_rect.Min.y);// + g.Style.FramePadding.y); + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.MenuBarAppending = true; + AlignTextToFramePadding(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + PopClipRect(); + PopID(); + window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x; + window->DC.GroupStack.back().AdvanceCursor = false; + EndGroup(); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.MenuBarAppending = false; +} + +bool ImGui::BeginMenu(const char* label, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + ImVec2 label_size = CalcTextSize(label, NULL, true); + + bool pressed; + bool menu_is_open = IsPopupOpen(id); + bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentMenuSet == window->GetID("##Menus")); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) + g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestPopupWindowPos). + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestPopupWindowPos() in Begin() + popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + float w = label_size.x; + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + PopStyleVar(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu inside a menu + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); + pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); + if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right); + if (!enabled) PopStyleColor(); + } + + const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); + if (menuset_is_open) + g.NavWindow = backed_nav_window; + + bool want_open = false, want_close = false; + if (window->DC.LayoutType != ImGuiLayoutType_Horizontal) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_within_opened_triangle = false; + if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window) + { + if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) + { + ImRect next_window_rect = next_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug + } + } + + want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); + want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); + } + else + { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && IsPopupOpen(id)) + ClosePopupToLevel(GImGui->CurrentPopupStack.Size); + + if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) + { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) + OpenPopup(label); + + if (menu_is_open) + { + SetNextWindowPos(popup_pos, ImGuiCond_Always); + ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu); + menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + } + + return menu_is_open; +} + +void ImGui::EndMenu() +{ + EndPopup(); +} + +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + BeginTooltipEx(0, true); + + const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; + if (text_end > text) + { + TextUnformatted(text, text_end); + Separator(); + } + + ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); + ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + SameLine(); + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); + else + Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); + EndTooltip(); +} + +static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b) +{ + float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; + int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); + return IM_COL32(r, g, b, 0xFF); +} + +// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. +// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether. +void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) + { + ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col)); + ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col)); + window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags); + + int yi = 0; + for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) + { + float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + if (y2 <= y1) + continue; + for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + { + float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + if (x2 <= x1) + continue; + int rounding_corners_flags_cell = 0; + if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; } + rounding_corners_flags_cell &= rounding_corners_flags; + window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell); + } + } + } + else + { + window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags); + } +} + +void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiColorEditFlags__InputsMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask; + if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; + if ((flags & ImGuiColorEditFlags__PickerMask) == 0) + flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected + g.ColorEditOptions = flags; +} + +// A little colored square. Return true when clicked. +// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. +// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. +bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(desc_id); + float default_size = GetFrameHeight(); + if (size.x == 0.0f) + size.x = default_size; + if (size.y == 0.0f) + size.y = default_size; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + if (flags & ImGuiColorEditFlags_NoAlpha) + flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + + ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f); + float grid_step = ImMin(size.x, size.y) / 2.99f; + float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + ImRect bb_inner = bb; + float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. + bb_inner.Expand(off); + if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f) + { + float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f); + RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft); + } + else + { + // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All); + } + if (g.Style.FrameBorderSize > 0.0f) + RenderFrameBorder(bb.Min, bb.Max, rounding); + else + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border + + // Drag and Drop Source + if (g.ActiveId == id && BeginDragDropSource()) // NB: The ActiveId test is merely an optional micro-optimization + { + if (flags & ImGuiColorEditFlags_NoAlpha) + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once); + else + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once); + ColorButton(desc_id, col, flags); + SameLine(); + TextUnformatted("Color"); + EndDragDropSource(); + hovered = false; + } + + // Tooltip + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + + return pressed; +} + +bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); +} + +void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) +{ + bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask); + bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); + if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + ImGuiColorEditFlags opts = g.ColorEditOptions; + if (allow_opt_inputs) + { + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV; + if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX; + } + if (allow_opt_datatype) + { + if (allow_opt_inputs) Separator(); + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; + } + + if (allow_opt_inputs || allow_opt_datatype) + Separator(); + if (Button("Copy as..", ImVec2(-1,0))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) + { + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + if (flags & ImGuiColorEditFlags_NoAlpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + EndPopup(); + } + + g.ColorEditOptions = opts; + EndPopup(); +} + +static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, const float* ref_col) +{ + bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); + bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); + if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context")) + return; + ImGuiContext& g = *GImGui; + if (allow_opt_picker) + { + ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + ImGui::PushItemWidth(picker_size.x); + for (int picker_type = 0; picker_type < 2; picker_type++) + { + // Draw small/thumbnail version of each picker type (over an invisible button for selection) + if (picker_type > 0) ImGui::Separator(); + ImGui::PushID(picker_type); + ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha); + if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; + ImVec2 backup_pos = ImGui::GetCursorScreenPos(); + if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); + ImGui::SetCursorScreenPos(backup_pos); + ImVec4 dummy_ref_col; + memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4)); + ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + } + if (allow_opt_alpha_bar) + { + if (allow_opt_picker) ImGui::Separator(); + ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + } + ImGui::EndPopup(); +} + +// Edit colors components (each component in 0.0f..1.0f range). +// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. +bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float square_sz = GetFrameHeight(); + const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); + const float w_items_all = CalcItemWidth() - w_extra; + const char* label_display_end = FindRenderedTextEnd(label); + + const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; + const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; + const int components = alpha ? 4 : 3; + const ImGuiColorEditFlags flags_untouched = flags; + + BeginGroup(); + PushID(label); + + // If we're not showing any slider there's no point in doing any HSV conversions + if (flags & ImGuiColorEditFlags_NoInputs) + flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions; + + // Context menu: display and modify options (before defaults are applied) + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorEditOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__InputsMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask); + if (!(flags & ImGuiColorEditFlags__DataTypeMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); + flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask)); + + // Convert to the formats we need + float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; + + bool value_changed = false; + bool value_changed_as_float = false; + + if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB/HSV 0..255 Sliders + const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); + + const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; + const char* fmt_table_int[3][4] = + { + { "%3.0f", "%3.0f", "%3.0f", "%3.0f" }, // Short display + { "R:%3.0f", "G:%3.0f", "B:%3.0f", "A:%3.0f" }, // Long display for RGBA + { "H:%3.0f", "S:%3.0f", "V:%3.0f", "A:%3.0f" } // Long display for HSVA + }; + const char* fmt_table_float[3][4] = + { + { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display + { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA + { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA + }; + const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1; + + PushItemWidth(w_item_one); + for (int n = 0; n < components; n++) + { + if (n > 0) + SameLine(0, style.ItemInnerSpacing.x); + if (n + 1 == components) + PushItemWidth(w_item_last); + if (flags & ImGuiColorEditFlags_Float) + value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + else + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + PopItemWidth(); + PopItemWidth(); + } + else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB Hexadecimal Input + char buf[64]; + if (alpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255)); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255)); + PushItemWidth(w_items_all); + if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) + { + value_changed = true; + char* p = buf; + while (*p == '#' || ImCharIsSpace(*p)) + p++; + i[0] = i[1] = i[2] = i[3] = 0; + if (alpha) + sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) + else + sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + PopItemWidth(); + } + + ImGuiWindow* picker_active_window = NULL; + if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) + { + if (!(flags & ImGuiColorEditFlags_NoInputs)) + SameLine(0, style.ItemInnerSpacing.x); + + const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); + if (ColorButton("##ColorButton", col_v4, flags)) + { + if (!(flags & ImGuiColorEditFlags_NoPicker)) + { + // Store current color and open a picker + g.ColorPickerRef = col_v4; + OpenPopup("picker"); + SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y)); + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + if (BeginPopup("picker")) + { + picker_active_window = g.CurrentWindow; + if (label != label_display_end) + { + TextUnformatted(label, label_display_end); + Separator(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + PopItemWidth(); + EndPopup(); + } + } + + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) + { + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + + // Convert back + if (picker_active_window == NULL) + { + if (!value_changed_as_float) + for (int n = 0; n < 4; n++) + f[n] = i[n] / 255.0f; + if (flags & ImGuiColorEditFlags_HSV) + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + if (value_changed) + { + col[0] = f[0]; + col[1] = f[1]; + col[2] = f[2]; + if (alpha) + col[3] = f[3]; + } + } + + PopID(); + EndGroup(); + + // Drag and Drop Target + if (window->DC.LastItemRectHoveredRect && BeginDragDropTarget()) // NB: The LastItemRectHoveredRect test is merely an optional micro-optimization + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * 3); + value_changed = true; + } + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * components); + value_changed = true; + } + EndDragDropTarget(); + } + + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) + window->DC.LastItemId = g.ActiveId; + + return value_changed; +} + +bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + float col4[4] = { col[0], col[1], col[2], 1.0f }; + if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) + return false; + col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; + return true; +} + +// 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. +static void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) +{ + switch (direction) + { + case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return; + case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return; + case ImGuiDir_None: case ImGuiDir_Count_: break; // Fix warnings + } +} + +static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w) +{ + RenderArrow(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK); + RenderArrow(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE); + RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK); + RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE); +} + +// ColorPicker +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) +bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImDrawList* draw_list = window->DrawList; + + ImGuiStyle& style = g.Style; + ImGuiIO& io = g.IO; + + PushID(label); + BeginGroup(); + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + flags |= ImGuiColorEditFlags_NoSmallPreview; + + // Context menu: display and store options. + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorPickerOptionsPopup(flags, col); + + // Read stored options + if (!(flags & ImGuiColorEditFlags__PickerMask)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_NoOptions)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + + // Setup + int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; + bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); + ImVec2 picker_pos = window->DC.CursorPos; + float square_sz = GetFrameHeight(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars + float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box + float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; + float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; + float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f); + + float backup_initial_col[4]; + memcpy(backup_initial_col, col, components * sizeof(float)); + + float wheel_thickness = sv_picker_size * 0.08f; + float wheel_r_outer = sv_picker_size * 0.50f; + float wheel_r_inner = wheel_r_outer - wheel_thickness; + ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f); + + // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. + float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); + ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. + ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. + ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + + float H,S,V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V); + + bool value_changed = false, value_changed_h = false, value_changed_sv = false; + + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Hue wheel + SV triangle logic + InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); + if (IsItemActive()) + { + ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; + ImVec2 current_off = g.IO.MousePos - wheel_center; + float initial_dist2 = ImLengthSqr(initial_off); + if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1)) + { + // Interactive with Hue wheel + H = atan2f(current_off.y, current_off.x) / IM_PI*0.5f; + if (H < 0.0f) + H += 1.0f; + value_changed = value_changed_h = true; + } + float cos_hue_angle = cosf(-H * 2.0f * IM_PI); + float sin_hue_angle = sinf(-H * 2.0f * IM_PI); + if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) + { + // Interacting with SV triangle + ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); + if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) + current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); + float uu, vv, ww; + ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); + V = ImClamp(1.0f - vv, 0.0001f, 1.0f); + S = ImClamp(uu / V, 0.0001f, 1.0f); + value_changed = value_changed_sv = true; + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // SV rectangle logic + InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); + if (IsItemActive()) + { + S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1)); + V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_sv = true; + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + + // Hue bar logic + SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); + InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = value_changed_h = true; + } + } + + // Alpha bar logic + if (alpha_bar) + { + SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); + InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); + value_changed = true; + } + } + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + SameLine(0, style.ItemInnerSpacing.x); + BeginGroup(); + } + + if (!(flags & ImGuiColorEditFlags_NoLabel)) + { + const char* label_display_end = FindRenderedTextEnd(label); + if (label != label_display_end) + { + if ((flags & ImGuiColorEditFlags_NoSidePreview)) + SameLine(0, style.ItemInnerSpacing.x); + TextUnformatted(label, label_display_end); + } + } + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if ((flags & ImGuiColorEditFlags_NoLabel)) + Text("Current"); + ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)); + if (ref_col != NULL) + { + Text("Original"); + ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); + if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2))) + { + memcpy(col, ref_col, components * sizeof(float)); + value_changed = true; + } + } + EndGroup(); + } + + // Convert back color to RGB + if (value_changed_h || value_changed_sv) + ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]); + + // R,G,B and H,S,V slider color editor + if ((flags & ImGuiColorEditFlags_NoInputs) == 0) + { + PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; + if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB); + if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV); + if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0) + value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX); + PopItemWidth(); + } + + // Try to cancel hue wrap (after ColorEdit), if any + if (value_changed) + { + float new_H, new_S, new_V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); + if (new_H <= 0 && H > 0) + { + if (new_V <= 0 && V != new_V) + ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); + else if (new_S <= 0) + ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); + } + } + + ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); + ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); + ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f)); + + const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) }; + ImVec2 sv_cursor_pos; + + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Render Hue Wheel + const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). + const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); + for (int n = 0; n < 6; n++) + { + const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; + const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; + const int vert_start_idx = draw_list->VtxBuffer.Size; + draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); + draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness); + const int vert_end_idx = draw_list->VtxBuffer.Size; + + // Paint colors over existing vertices + ImVec2 gradient_p0(wheel_center.x + cosf(a0) * wheel_r_inner, wheel_center.y + sinf(a0) * wheel_r_inner); + ImVec2 gradient_p1(wheel_center.x + cosf(a1) * wheel_r_inner, wheel_center.y + sinf(a1) * wheel_r_inner); + ShadeVertsLinearColorGradientKeepAlpha(draw_list->VtxBuffer.Data + vert_start_idx, draw_list->VtxBuffer.Data + vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]); + } + + // Render Cursor + preview on Hue Wheel + float cos_hue_angle = cosf(H * 2.0f * IM_PI); + float sin_hue_angle = sinf(H * 2.0f * IM_PI); + ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f); + float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; + int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); + draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments); + + // Render SV triangle (rotated according to hue) + ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); + ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); + ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); + ImVec2 uv_white = GetFontTexUvWhitePixel(); + draw_list->PrimReserve(6, 6); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, hue_color32); + draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE); + draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS); + draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK); + draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS); + draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f); + sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // Render SV Square + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE); + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK); + RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f); + sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much + sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); + + // Render Hue Bar + for (int i = 0; i < 6; ++i) + draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]); + float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f); + RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) + float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f; + draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12); + + // Render alpha bar + if (alpha_bar) + { + float alpha = ImSaturate(col[3]); + ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); + RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK); + float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f); + RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); + } + + EndGroup(); + PopID(); + + return value_changed && memcmp(backup_initial_col, col, components * sizeof(float)); +} + +// Horizontal separating line. +void ImGui::Separator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + ImGuiWindowFlags flags = 0; + if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0) + flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected + if (flags & ImGuiSeparatorFlags_Vertical) + { + VerticalSeparator(); + return; + } + + // Horizontal Separator + if (window->DC.ColumnsSet) + PopClipRect(); + + float x1 = window->Pos.x; + float x2 = window->Pos.x + window->Size.x; + if (!window->DC.GroupStack.empty()) + x1 += window->DC.IndentX; + + const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f)); + ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout. + if (!ItemAdd(bb, 0)) + { + if (window->DC.ColumnsSet) + PushColumnClipRect(); + return; + } + + window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator)); + + if (g.LogEnabled) + LogRenderedText(NULL, IM_NEWLINE "--------------------------------"); + + if (window->DC.ColumnsSet) + { + PushColumnClipRect(); + window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; + } +} + +void ImGui::VerticalSeparator() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrentLineHeight; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2)); + ItemSize(ImVec2(bb.GetWidth(), 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); +} + +bool ImGui::SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; +#ifdef IMGUI_HAS_NAV + window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; +#endif + bool add = ItemAdd(bb, id); + window->DC.ItemFlags = item_flags_backup; + if (!add) + return false; + + bool hovered, held; + ImRect bb_interact = bb; + bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); + ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (g.ActiveId != id) + SetItemAllowOverlap(); + + if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id)) + SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + + ImRect bb_render = bb; + if (held) + { + ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; + float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; + + // Minimum pane size + if (mouse_delta < min_size1 - *size1) + mouse_delta = min_size1 - *size1; + if (mouse_delta > *size2 - min_size2) + mouse_delta = *size2 - min_size2; + + // Apply resize + *size1 += mouse_delta; + *size2 -= mouse_delta; + bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + } + + // Render + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding); + + return held; +} + +void ImGui::Spacing() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ItemSize(ImVec2(0,0)); +} + +void ImGui::Dummy(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb); + ItemAdd(bb, 0); +} + +bool ImGui::IsRectVisible(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); +} + +bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); +} + +// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) +void ImGui::BeginGroup() +{ + ImGuiWindow* window = GetCurrentWindow(); + + window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1); + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + group_data.BackupCursorPos = window->DC.CursorPos; + group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; + group_data.BackupIndentX = window->DC.IndentX; + group_data.BackupGroupOffsetX = window->DC.GroupOffsetX; + group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight; + group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; + group_data.BackupLogLinePosY = window->DC.LogLinePosY; + group_data.BackupActiveIdIsAlive = GImGui->ActiveIdIsAlive; + group_data.AdvanceCursor = true; + + window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX; + window->DC.IndentX = window->DC.GroupOffsetX; + window->DC.CursorMaxPos = window->DC.CursorPos; + window->DC.CurrentLineHeight = 0.0f; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; +} + +void ImGui::EndGroup() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls + + ImGuiGroupData& group_data = window->DC.GroupStack.back(); + + ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos); + group_bb.Max = ImMax(group_bb.Min, group_bb.Max); + + window->DC.CursorPos = group_data.BackupCursorPos; + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight; + window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; + window->DC.IndentX = group_data.BackupIndentX; + window->DC.GroupOffsetX = group_data.BackupGroupOffsetX; + window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; + + if (group_data.AdvanceCursor) + { + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. + ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); + ItemAdd(group_bb, 0); + } + + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will be functional on the entire group. + // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but if you search for LastItemId you'll notice it is only used in that context. + const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow); + if (active_id_within_group) + window->DC.LastItemId = g.ActiveId; + window->DC.LastItemRect = group_bb; + + window->DC.GroupStack.pop_back(); + + //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] +} + +// Gets back to previous line and continue with horizontal layout +// pos_x == 0 : follow right after previous item +// pos_x != 0 : align to specified x position (relative to window/group left) +// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 +// spacing_w >= 0 : enforce spacing amount +void ImGui::SameLine(float pos_x, float spacing_w) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + if (pos_x != 0.0f) + { + if (spacing_w < 0.0f) spacing_w = 0.0f; + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + else + { + if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x; + window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + window->DC.CurrentLineHeight = window->DC.PrevLineHeight; + window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; +} + +void ImGui::NewLine() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; + window->DC.LayoutType = ImGuiLayoutType_Vertical; + if (window->DC.CurrentLineHeight > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. + ItemSize(ImVec2(0,0)); + else + ItemSize(ImVec2(0.0f, g.FontSize)); + window->DC.LayoutType = backup_layout_type; +} + +void ImGui::NextColumn() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems || window->DC.ColumnsSet == NULL) + return; + + ImGuiContext& g = *GImGui; + PopItemWidth(); + PopClipRect(); + + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y); + if (++columns->Current < columns->Count) + { + // Columns 1+ cancel out IndentX + window->DC.ColumnsOffsetX = GetColumnOffset(columns->Current) - window->DC.IndentX + g.Style.ItemSpacing.x; + window->DrawList->ChannelsSetCurrent(columns->Current); + } + else + { + window->DC.ColumnsOffsetX = 0.0f; + window->DrawList->ChannelsSetCurrent(0); + columns->Current = 0; + columns->CellMinY = columns->CellMaxY; + } + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); + window->DC.CursorPos.y = columns->CellMinY; + window->DC.CurrentLineHeight = 0.0f; + window->DC.CurrentLineTextBaseOffset = 0.0f; + + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup +} + +int ImGui::GetColumnIndex() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0; +} + +int ImGui::GetColumnsCount() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1; +} + +static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm) +{ + return offset_norm * (columns->MaxX - columns->MinX); +} + +static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset) +{ + return offset / (columns->MaxX - columns->MinX); +} + +static inline float GetColumnsRectHalfWidth() { return 4.0f; } + +static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index) +{ + // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(column_index > 0); // We cannot drag column 0. If you get this assert you may have a conflict between the ID of your columns and another widgets. + IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x; + x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); + if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths)) + x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + + return x; +} + +float ImGui::GetColumnOffset(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + /* + if (g.ActiveId) + { + ImGuiContext& g = *GImGui; + const ImGuiID column_id = columns->ColumnsSetId + ImGuiID(column_index); + if (g.ActiveId == column_id) + return GetDraggedColumnOffset(columns, column_index); + } + */ + + const float t = columns->Columns[column_index].OffsetNorm; + const float x_offset = ImLerp(columns->MinX, columns->MaxX, t); + return x_offset; +} + +static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false) +{ + if (column_index < 0) + column_index = columns->Current; + + float offset_norm; + if (before_resize) + offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; + else + offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; + return OffsetNormToPixels(columns, offset_norm); +} + +float ImGui::GetColumnWidth(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); +} + +void ImGui::SetColumnOffset(int column_index, float offset) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1); + const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); + columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX); + + if (preserve_width) + SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); +} + +void ImGui::SetColumnWidth(int column_index, float width) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); +} + +void ImGui::PushColumnClipRect(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + if (column_index < 0) + column_index = columns->Current; + + PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false); +} + +static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id) +{ + for (int n = 0; n < window->ColumnsStorage.Size; n++) + if (window->ColumnsStorage[n].ID == id) + return &window->ColumnsStorage[n]; + + window->ColumnsStorage.push_back(ImGuiColumnsSet()); + ImGuiColumnsSet* columns = &window->ColumnsStorage.back(); + columns->ID = id; + return columns; +} + +void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(columns_count > 1); + IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported + + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. + // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. + PushID(0x11223347 + (str_id ? 0 : columns_count)); + ImGuiID id = window->GetID(str_id ? str_id : "columns"); + PopID(); + + // Acquire storage for the columns set + ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + columns->Flags = flags; + window->DC.ColumnsSet = columns; + + // Set state for first column + const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->Size.x -window->ScrollbarSizes.x); + columns->MinX = window->DC.IndentX - g.Style.ItemSpacing.x; // Lock our horizontal range + //column->MaxX = content_region_width - window->Scroll.x - ((window->Flags & ImGuiWindowFlags_NoScrollbar) ? 0 : g.Style.ScrollbarSize);// - window->WindowPadding().x; + columns->MaxX = content_region_width - window->Scroll.x; + columns->StartPosY = window->DC.CursorPos.y; + columns->StartMaxPosX = window->DC.CursorMaxPos.x; + columns->CellMinY = columns->CellMaxY = window->DC.CursorPos.y; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); + + // Initialize defaults + columns->IsFirstFrame = (columns->Columns.Size == 0); + if (columns->Columns.Size == 0) + { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) + { + ImGuiColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + IM_ASSERT(columns->Columns.Size == columns_count + 1); + + for (int n = 0; n < columns_count + 1; n++) + { + // Clamp position + ImGuiColumnData* column = &columns->Columns[n]; + float t = column->OffsetNorm; + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + t = ImMin(t, PixelsToOffsetNorm(columns, (columns->MaxX - columns->MinX) - g.Style.ColumnsMinSpacing * (columns->Count - n))); + column->OffsetNorm = t; + + if (n == columns_count) + continue; + + // Compute clipping rectangle + float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f); + float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f); + column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); + column->ClipRect.ClipWith(window->ClipRect); + } + + window->DrawList->ChannelsSplit(columns->Count); + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); +} + +void ImGui::EndColumns() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + PopItemWidth(); + PopClipRect(); + window->DrawList->ChannelsMerge(); + + columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y); + window->DC.CursorPos.y = columns->CellMaxY; + if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize)) + window->DC.CursorMaxPos.x = ImMax(columns->StartMaxPosX, columns->MaxX); // Restore cursor max pos, as columns don't grow parent + + // Draw columns borders and handle resize + bool is_being_resized = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) + { + const float y1 = columns->StartPosY; + const float y2 = window->DC.CursorPos.y; + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) + { + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction + const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2)); + KeepAliveID(column_id); + if (IsClippedEx(column_rect, column_id, false)) + continue; + + bool hovered = false, held = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoResize)) + { + ButtonBehavior(column_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize)) + dragging_column = n; + } + + // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.) + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col); + } + + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. + if (dragging_column != -1) + { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->DC.ColumnsSet = NULL; + window->DC.ColumnsOffsetX = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX); +} + +// [2017/12: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing] +void ImGui::Columns(int columns_count, const char* id, bool border) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count != columns_count) + EndColumns(); + + ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder); + //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior + if (columns_count != 1) + BeginColumns(id, columns_count, flags); +} + +void ImGui::Indent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.IndentX += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; +} + +void ImGui::Unindent(float indent_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + window->DC.IndentX -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; + window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX; +} + +void ImGui::TreePush(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(str_id ? str_id : "#TreePush"); +} + +void ImGui::TreePush(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); +} + +void ImGui::TreePushRawID(ImGuiID id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + window->IDStack.push_back(id); +} + +void ImGui::TreePop() +{ + ImGuiWindow* window = GetCurrentWindow(); + Unindent(); + window->DC.TreeDepth--; + PopID(); +} + +void ImGui::Value(const char* prefix, bool b) +{ + Text("%s: %s", prefix, (b ? "true" : "false")); +} + +void ImGui::Value(const char* prefix, int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, unsigned int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, float v, const char* float_format) +{ + if (float_format) + { + char fmt[64]; + ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + Text(fmt, prefix, v); + } + else + { + Text("%s: %.3f", prefix, v); + } +} + +//----------------------------------------------------------------------------- +// DRAG AND DROP +//----------------------------------------------------------------------------- + +void ImGui::ClearDragDrop() +{ + ImGuiContext& g = *GImGui; + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropAcceptFrameCount = -1; +} + +// Call when current ID is active. +// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags, int mouse_button) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool source_drag_active = false; + ImGuiID source_id = 0; + ImGuiID source_parent_id = 0; + if (!(flags & ImGuiDragDropFlags_SourceExtern)) + { + source_id = window->DC.LastItemId; + if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case + return false; + if (g.IO.MouseDown[mouse_button] == false) + return false; + + if (source_id == 0) + { + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: + // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. + if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) + { + IM_ASSERT(0); + return false; + } + + // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() + // We build a throwaway ID based on current ID stack + relative AABB of items in window. + // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. + // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. + bool is_hovered = window->DC.LastItemRectHoveredRect; + if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + return false; + source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); + if (is_hovered) + SetHoveredID(source_id); + if (is_hovered && g.IO.MouseClicked[mouse_button]) + { + SetActiveID(source_id, window); + FocusWindow(window); + } + if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. + g.ActiveIdAllowOverlap = is_hovered; + } + if (g.ActiveId != source_id) + return false; + source_parent_id = window->IDStack.back(); + source_drag_active = IsMouseDragging(mouse_button); + } + else + { + window = NULL; + source_id = ImHash("#SourceExtern", 0); + source_drag_active = true; + } + + if (source_drag_active) + { + if (!g.DragDropActive) + { + IM_ASSERT(source_id != 0); + ClearDragDrop(); + ImGuiPayload& payload = g.DragDropPayload; + payload.SourceId = source_id; + payload.SourceParentId = source_parent_id; + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + } + + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + // FIXME-DRAG + //SetNextWindowPos(g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding); + //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This is better but e.g ColorButton with checkboard has issue with transparent colors :( + SetNextWindowPos(g.IO.MousePos); + PushStyleColor(ImGuiCol_PopupBg, GetStyleColorVec4(ImGuiCol_PopupBg) * ImVec4(1.0f, 1.0f, 1.0f, 0.6f)); + BeginTooltipEx(ImGuiWindowFlags_NoInputs); + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + window->DC.LastItemRectHoveredRect = false; + + return true; + } + return false; +} + +void ImGui::EndDragDropSource() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + EndTooltip(); + PopStyleColor(); + //PopStyleVar(); + } + + // Discard the drag if have not called SetDragDropPayload() + if (g.DragDropPayload.DataFrameCount == -1) + ClearDragDrop(); +} + +// Use 'cond' to choose to submit payload on drag start or every frame +bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + ImGuiPayload& payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType)); // Payload type can be at most 8 characters longs + IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); + IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + { + // Copy payload + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) + { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy((void*)payload.Data, data, data_size); + } + else if (data_size > 0) + { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy((void*)payload.Data, data, data_size); + } + else + { + payload.Data = NULL; + } + payload.DataSize = (int)data_size; + } + payload.DataFrameCount = g.FrameCount; + + return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); +} + +bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + IM_ASSERT(id != 0); + if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) + return false; + + g.DragDropTargetRect = bb; + g.DragDropTargetId = id; + return true; +} + +// We don't use BeginDragDropTargetCustom() and duplicate its code because: +// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. +// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) +bool ImGui::BeginDragDropTarget() +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (!window->DC.LastItemRectHoveredRect) + return false; + if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) + return false; + + ImGuiID id = window->DC.LastItemId; + if (id == 0) + id = window->GetIDFromRectangle(window->DC.LastItemRect); + if (g.DragDropPayload.SourceId == id) + return false; + + g.DragDropTargetRect = window->DC.LastItemRect; + g.DragDropTargetId = id; + return true; +} + +bool ImGui::IsDragDropPayloadBeingAccepted() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive && g.DragDropAcceptIdPrev != 0; +} + +const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiPayload& payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? + if (type != NULL && !payload.IsDataType(type)) + return NULL; + + // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. + // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! + const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); + ImRect r = g.DragDropTargetRect; + float r_surface = r.GetWidth() * r.GetHeight(); + if (r_surface < g.DragDropAcceptIdCurrRectSurface) + { + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + } + + // Render default drop visuals + payload.Preview = was_accepted_previously; + flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) + if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) + { + // FIXME-DRAG: Settle on a proper default visuals for drop target. + r.Expand(3.5f); + bool push_clip_rect = !window->ClipRect.Contains(r); + if (push_clip_rect) window->DrawList->PushClipRectFullScreen(); + window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); + if (push_clip_rect) window->DrawList->PopClipRect(); + } + + g.DragDropAcceptFrameCount = g.FrameCount; + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) + return NULL; + + return &payload; +} + +// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. +void ImGui::EndDragDropTarget() +{ + ImGuiContext& g = *GImGui; (void)g; + IM_ASSERT(g.DragDropActive); +} + +//----------------------------------------------------------------------------- +// PLATFORM DEPENDENT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)) +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include +#endif + +// Win32 API clipboard implementation +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) + +#ifdef _MSC_VER +#pragma comment(lib, "user32") +#endif + +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + static ImVector buf_local; + buf_local.clear(); + if (!OpenClipboard(NULL)) + return NULL; + HANDLE wbuf_handle = GetClipboardData(CF_UNICODETEXT); + if (wbuf_handle == NULL) + { + CloseClipboard(); + return NULL; + } + if (ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle)) + { + int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1; + buf_local.resize(buf_len); + ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL); + } + GlobalUnlock(wbuf_handle); + CloseClipboard(); + return buf_local.Data; +} + +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + if (!OpenClipboard(NULL)) + return; + const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1; + HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar)); + if (wbuf_handle == NULL) + { + CloseClipboard(); + return; + } + ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle); + ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL); + GlobalUnlock(wbuf_handle); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, wbuf_handle); + CloseClipboard(); +} + +#else + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static const char* GetClipboardTextFn_DefaultImpl(void*) +{ + ImGuiContext& g = *GImGui; + return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin(); +} + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +{ + ImGuiContext& g = *GImGui; + g.PrivateClipboard.clear(); + const char* text_end = text + strlen(text); + g.PrivateClipboard.resize((int)(text_end - text) + 1); + memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text)); + g.PrivateClipboard[(int)(text_end - text)] = 0; +} + +#endif + +// Win32 API IME support (for Asian languages, etc.) +#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) + +#include +#ifdef _MSC_VER +#pragma comment(lib, "imm32") +#endif + +static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) +{ + // Notify OS Input Method Editor of text input position + if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle) + if (HIMC himc = ImmGetContext(hwnd)) + { + COMPOSITIONFORM cf; + cf.ptCurrentPos.x = x; + cf.ptCurrentPos.y = y; + cf.dwStyle = CFS_FORCE_POSITION; + ImmSetCompositionWindow(himc, &cf); + } +} + +#else + +static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} + +#endif + +//----------------------------------------------------------------------------- +// HELP +//----------------------------------------------------------------------------- + +void ImGui::ShowMetricsWindow(bool* p_open) +{ + if (ImGui::Begin("ImGui Metrics", p_open)) + { + ImGui::Text("ImGui %s", ImGui::GetVersion()); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3); + ImGui::Text("%d allocations", ImGui::GetIO().MetricsAllocs); + static bool show_clip_rects = true; + ImGui::Checkbox("Show clipping rectangles when hovering an ImDrawCmd", &show_clip_rects); + ImGui::Separator(); + + struct Funcs + { + static void NodeDrawList(ImDrawList* draw_list, const char* label) + { + bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size); + if (draw_list == ImGui::GetWindowDrawList()) + { + ImGui::SameLine(); + ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) + if (node_open) ImGui::TreePop(); + return; + } + if (!node_open) + return; + + ImDrawList* overlay_draw_list = &GImGui->OverlayDrawList; // Render additional visuals into the top-most draw list + int elem_offset = 0; + for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) + { + if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) + continue; + if (pcmd->UserCallback) + { + ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); + continue; + } + ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; + bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %-4d %s vtx, tex = %p, clip_rect = (%.0f,%.0f)..(%.0f,%.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); + if (show_clip_rects && ImGui::IsItemHovered()) + { + ImRect clip_rect = pcmd->ClipRect; + ImRect vtxs_rect; + for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) + vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); + clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); + vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); + } + if (!pcmd_node_open) + continue; + + // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. + ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. + while (clipper.Step()) + for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) + { + char buf[300]; + char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); + ImVec2 triangles_pos[3]; + for (int n = 0; n < 3; n++, vtx_i++) + { + ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; + triangles_pos[n] = v.pos; + buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d { pos = (%8.2f,%8.2f), uv = (%.6f,%.6f), col = %08X }\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); + } + ImGui::Selectable(buf, false); + if (ImGui::IsItemHovered()) + { + ImDrawListFlags backup_flags = overlay_draw_list->Flags; + overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles. + overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f); + overlay_draw_list->Flags = backup_flags; + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + static void NodeWindows(ImVector& windows, const char* label) + { + if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size)) + return; + for (int i = 0; i < windows.Size; i++) + Funcs::NodeWindow(windows[i], "Window"); + ImGui::TreePop(); + } + + static void NodeWindow(ImGuiWindow* window, const char* label) + { + if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) + return; + NodeDrawList(window->DrawList, "DrawList"); + ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); + if (ImGui::IsItemHovered()) + GImGui->OverlayDrawList.AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255,255,0,255)); + ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window)); + ImGui::BulletText("Active: %d, WriteAccessed: %d", window->Active, window->WriteAccessed); + if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); + if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); + ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair)); + ImGui::TreePop(); + } + }; + + ImGuiContext& g = *GImGui; // Access private state + Funcs::NodeWindows(g.Windows, "Windows"); + if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.RenderDrawLists[0].Size)) + { + for (int layer = 0; layer < IM_ARRAYSIZE(g.RenderDrawLists); layer++) + for (int i = 0; i < g.RenderDrawLists[layer].Size; i++) + Funcs::NodeDrawList(g.RenderDrawLists[0][i], "DrawList"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size)) + { + for (int i = 0; i < g.OpenPopupStack.Size; i++) + { + ImGuiWindow* window = g.OpenPopupStack[i].Window; + ImGui::BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : ""); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Basic state")) + { + ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); + ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); + ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec)", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not + ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec)", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer); + ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); + ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); + ImGui::TreePop(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- + +// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. +// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github. +#ifdef IMGUI_INCLUDE_IMGUI_USER_INL +#include "imgui_user.inl" +#endif + +//----------------------------------------------------------------------------- diff --git a/skeleton/imgui/imgui.h b/skeleton/imgui/imgui.h new file mode 100644 index 0000000..711e027 --- /dev/null +++ b/skeleton/imgui/imgui.h @@ -0,0 +1,1684 @@ +// dear imgui, v1.54 WIP +// (headers) + +// See imgui.cpp file for documentation. +// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. +// Read 'Programmer guide' in imgui.cpp for notes on how to setup ImGui in your codebase. +// Get latest version at https://github.com/ocornut/imgui + +#pragma once + +#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) +#include "imconfig.h" // User-editable configuration file +#endif +#include // FLT_MAX +#include // va_list +#include // ptrdiff_t, NULL +#include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp + +#define IMGUI_VERSION "1.54 WIP" + +// Define attributes of all API symbols declarations, e.g. for DLL under Windows. +#ifndef IMGUI_API +#define IMGUI_API +#endif + +// Define assertion handler. +#ifndef IM_ASSERT +#include +#define IM_ASSERT(_EXPR) assert(_EXPR) +#endif + +// Helpers +// Some compilers support applying printf-style warnings to user functions. +#if defined(__clang__) || defined(__GNUC__) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) +#else +#define IM_FMTARGS(FMT) +#define IM_FMTLIST(FMT) +#endif +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +// Forward declarations +struct ImDrawChannel; // Temporary storage for outputting drawing commands out of order, used by ImDrawList::ChannelsSplit() +struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call) +struct ImDrawData; // All draw command lists required to render the frame +struct ImDrawList; // A single draw command list (generally one per window) +struct ImDrawListSharedData; // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself) +struct ImDrawVert; // A single vertex (20 bytes by default, override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) +struct ImFont; // Runtime data for a single font within a parent ImFontAtlas +struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader +struct ImFontConfig; // Configuration data when adding a font or merging fonts +struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 +struct ImGuiIO; // Main configuration and I/O between your application and ImGui +struct ImGuiOnceUponAFrame; // Simple helper for running a block of code not more than once a frame, used by IMGUI_ONCE_UPON_A_FRAME macro +struct ImGuiStorage; // Simple custom key value storage +struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTextFilter; // Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextBuffer; // Text buffer for logging/accumulating text +struct ImGuiTextEditCallbackData; // Shared state of ImGui::InputText() when using custom ImGuiTextEditCallback (rare/advanced use) +struct ImGuiSizeConstraintCallbackData;// Structure used to constraint window size in custom ways when using custom ImGuiSizeConstraintCallback (rare/advanced use) +struct ImGuiListClipper; // Helper to manually clip large list of items +struct ImGuiPayload; // User data payload for drag and drop operations +struct ImGuiContext; // ImGui context (opaque) + +// Typedefs and Enumerations (declared as int for compatibility and to not pollute the top of this file) +typedef unsigned int ImU32; // 32-bit unsigned integer (typically used to store packed colors) +typedef unsigned int ImGuiID; // unique ID used by widgets (typically hashed from a stack of string) +typedef unsigned short ImWchar; // character for keyboard input/display +typedef void* ImTextureID; // user data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) +typedef int ImGuiCol; // enum: a color identifier for styling // enum ImGuiCol_ +typedef int ImGuiCond; // enum: a condition for Set*() // enum ImGuiCond_ +typedef int ImGuiKey; // enum: a key identifier (ImGui-side enum) // enum ImGuiKey_ +typedef int ImGuiMouseCursor; // enum: a mouse cursor identifier // enum ImGuiMouseCursor_ +typedef int ImGuiStyleVar; // enum: a variable identifier for styling // enum ImGuiStyleVar_ +typedef int ImDrawCornerFlags; // flags: for ImDrawList::AddRect*() etc. // enum ImDrawCornerFlags_ +typedef int ImDrawListFlags; // flags: for ImDrawList // enum ImDrawListFlags_ +typedef int ImGuiColorEditFlags; // flags: for ColorEdit*(), ColorPicker*() // enum ImGuiColorEditFlags_ +typedef int ImGuiColumnsFlags; // flags: for *Columns*() // enum ImGuiColumnsFlags_ +typedef int ImGuiDragDropFlags; // flags: for *DragDrop*() // enum ImGuiDragDropFlags_ +typedef int ImGuiComboFlags; // flags: for BeginCombo() // enum ImGuiComboFlags_ +typedef int ImGuiFocusedFlags; // flags: for IsWindowFocused() // enum ImGuiFocusedFlags_ +typedef int ImGuiHoveredFlags; // flags: for IsItemHovered() etc. // enum ImGuiHoveredFlags_ +typedef int ImGuiInputTextFlags; // flags: for InputText*() // enum ImGuiInputTextFlags_ +typedef int ImGuiSelectableFlags; // flags: for Selectable() // enum ImGuiSelectableFlags_ +typedef int ImGuiTreeNodeFlags; // flags: for TreeNode*(),CollapsingHeader()// enum ImGuiTreeNodeFlags_ +typedef int ImGuiWindowFlags; // flags: for Begin*() // enum ImGuiWindowFlags_ +typedef int (*ImGuiTextEditCallback)(ImGuiTextEditCallbackData *data); +typedef void (*ImGuiSizeConstraintCallback)(ImGuiSizeConstraintCallbackData* data); +#if defined(_MSC_VER) && !defined(__clang__) +typedef unsigned __int64 ImU64; // 64-bit unsigned integer +#else +typedef unsigned long long ImU64; // 64-bit unsigned integer +#endif + +// Others helpers at bottom of the file: +// class ImVector<> // Lightweight std::vector like class. +// IMGUI_ONCE_UPON_A_FRAME // Execute a block of code once per frame only (convenient for creating UI within deep-nested code that runs multiple times) + +struct ImVec2 +{ + float x, y; + ImVec2() { x = y = 0.0f; } + ImVec2(float _x, float _y) { x = _x; y = _y; } +#ifdef IM_VEC2_CLASS_EXTRA // Define constructor and implicit cast operators in imconfig.h to convert back<>forth from your math types and ImVec2. + IM_VEC2_CLASS_EXTRA +#endif +}; + +struct ImVec4 +{ + float x, y, z, w; + ImVec4() { x = y = z = w = 0.0f; } + ImVec4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; } +#ifdef IM_VEC4_CLASS_EXTRA // Define constructor and implicit cast operators in imconfig.h to convert back<>forth from your math types and ImVec4. + IM_VEC4_CLASS_EXTRA +#endif +}; + +// ImGui end-user API +// In a namespace so that user can add extra functions in a separate file (e.g. Value() helpers for your vector or common types) +namespace ImGui +{ + // Main + IMGUI_API ImGuiIO& GetIO(); + IMGUI_API ImGuiStyle& GetStyle(); + IMGUI_API ImDrawData* GetDrawData(); // same value as passed to your io.RenderDrawListsFn() function. valid after Render() and until the next call to NewFrame() + IMGUI_API void NewFrame(); // start a new ImGui frame, you can submit any command from this point until Render()/EndFrame(). + IMGUI_API void Render(); // ends the ImGui frame, finalize the draw data, then call your io.RenderDrawListsFn() function if set. + IMGUI_API void EndFrame(); // ends the ImGui frame. automatically called by Render(), so most likely don't need to ever call that yourself directly. If you don't need to render you may call EndFrame() but you'll have wasted CPU already. If you don't need to render, better to not create any imgui windows instead! + IMGUI_API void Shutdown(); + + // Demo, Debug, Informations + IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create demo/test window (previously called ShowTestWindow). demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! + IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create metrics window. display ImGui internals: draw commands (with individual draw calls and vertices), window list, basic internal state, etc. + IMGUI_API void ShowStyleEditor(ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) + IMGUI_API bool ShowStyleSelector(const char* label); + IMGUI_API void ShowFontSelector(const char* label); + IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as a end-user (mouse/keyboard controls). + + // Window + IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // push window to the stack and start appending to it. see .cpp for details. return false when window is collapsed, so you can early out in your code. 'bool* p_open' creates a widget on the upper-right to close the window (which sets your bool to false). + IMGUI_API void End(); // finish appending to current window, pop it off the window stack. + IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags extra_flags = 0); // begin a scrolling region. size==0.0f: use remaining window size, size<0.0f: use remaining window size minus abs(size). size>0.0f: fixed size. each axis can use a different mode, e.g. ImVec2(0,400). + IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags extra_flags = 0); // " + IMGUI_API void EndChild(); + IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates + IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() + IMGUI_API float GetContentRegionAvailWidth(); // + IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min (roughly (0,0)-Scroll), in window coordinates + IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max (roughly (0,0)+Size-Scroll) where Size can be override with SetNextWindowContentSize(), in window coordinates + IMGUI_API float GetWindowContentRegionWidth(); // + IMGUI_API ImDrawList* GetWindowDrawList(); // get rendering command-list if you want to append your own draw primitives + IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (useful if you want to do your own drawing via the DrawList api) + IMGUI_API ImVec2 GetWindowSize(); // get current window size + IMGUI_API float GetWindowWidth(); + IMGUI_API float GetWindowHeight(); + IMGUI_API bool IsWindowCollapsed(); + IMGUI_API bool IsWindowAppearing(); + IMGUI_API void SetWindowFontScale(float scale); // per-window font scale. Adjust IO.FontGlobalScale if you want to scale all windows + + IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0,0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. + IMGUI_API void SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() + IMGUI_API void SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeConstraintCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints. + IMGUI_API void SetNextWindowContentSize(const ImVec2& size); // set next window content size (~ enforce the range of scrollbars). not including window decorations (title bar, menu bar, etc.). set an axis to 0.0f to leave it automatic. call before Begin() + IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() + IMGUI_API void SetNextWindowFocus(); // set next window to be focused / front-most. call before Begin() + IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. + IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0,0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. + IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). + IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / front-most. prefer using SetNextWindowFocus(). + IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. + IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. + IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state + IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / front-most. use NULL to remove focus. + + IMGUI_API float GetScrollX(); // get scrolling amount [0..GetScrollMaxX()] + IMGUI_API float GetScrollY(); // get scrolling amount [0..GetScrollMaxY()] + IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.X - WindowSize.X + IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y + IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()] + IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] + IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. + IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions. + IMGUI_API void SetStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it) + IMGUI_API ImGuiStorage* GetStateStorage(); + + // Parameters stacks (shared) + IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + IMGUI_API void PopFont(); + IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); + IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); + IMGUI_API void PopStyleColor(int count = 1); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); + IMGUI_API void PopStyleVar(int count = 1); + IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwhise use GetColorU32() to get style color + style alpha. + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied + IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API + IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier + IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied + IMGUI_API ImU32 GetColorU32(ImU32 col); // retrieve given color with style alpha applied + + // Parameters stacks (current window) + IMGUI_API void PushItemWidth(float item_width); // width of items for the common item+label case, pixels. 0.0f = default to ~2/3 of windows width, >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -1.0f always align width to the right side) + IMGUI_API void PopItemWidth(); + IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position + IMGUI_API void PushTextWrapPos(float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space + IMGUI_API void PopTextWrapPos(); + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PopAllowKeyboardFocus(); + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. + IMGUI_API void PopButtonRepeat(); + + // Cursor / Layout + IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. + IMGUI_API void SameLine(float pos_x = 0.0f, float spacing_w = -1.0f); // call between widgets or groups to layout them horizontally + IMGUI_API void NewLine(); // undo a SameLine() + IMGUI_API void Spacing(); // add vertical spacing + IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size + IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void BeginGroup(); // lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) + IMGUI_API void EndGroup(); + IMGUI_API ImVec2 GetCursorPos(); // cursor position is relative to window position + IMGUI_API float GetCursorPosX(); // " + IMGUI_API float GetCursorPosY(); // " + IMGUI_API void SetCursorPos(const ImVec2& local_pos); // " + IMGUI_API void SetCursorPosX(float x); // " + IMGUI_API void SetCursorPosY(float y); // " + IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to work with ImDrawList API) + IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in absolute screen coordinates [0..io.DisplaySize] + IMGUI_API void AlignTextToFramePadding(); // vertically align/lower upcoming text to FramePadding.y so that it will aligns to upcoming widgets (call if you have text on a line before regular widgets) + IMGUI_API float GetTextLineHeight(); // ~ FontSize + IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text) + IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 + IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) + + // Columns + // You can also use SameLine(pos_x) for simplified columns. The columns API is still work-in-progress and rather lacking. + IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); + IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished + IMGUI_API int GetColumnIndex(); // get current column index + IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column + IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column + IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f + IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column + IMGUI_API int GetColumnsCount(); + + // ID scopes + // If you are creating widgets in a loop you most likely want to push a unique identifier (e.g. object pointer, loop index) so ImGui can differentiate them. + // You can also use the "##foobar" syntax within widget label to distinguish them from each others. Read "A primer on the use of labels/IDs" in the FAQ for more details. + IMGUI_API void PushID(const char* str_id); // push identifier into the ID stack. IDs are hash of the entire stack! + IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); + IMGUI_API void PushID(const void* ptr_id); + IMGUI_API void PushID(int int_id); + IMGUI_API void PopID(); + IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself + IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); + IMGUI_API ImGuiID GetID(const void* ptr_id); + + // Widgets: Text + IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. + IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // simple formatted text + IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void TextDisabled(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextDisabledV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void TextWrapped(const char* fmt, ...) IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). + IMGUI_API void TextWrappedV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void LabelText(const char* label, const char* fmt, ...) IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets + IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() + IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void Bullet(); // draw a small circle and keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses + + // Widgets: Main + IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0)); // button + IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text + IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); // button behavior without the visuals, useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.) + IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); + IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding + IMGUI_API bool Checkbox(const char* label, bool* v); + IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); + IMGUI_API bool RadioButton(const char* label, bool active); + IMGUI_API bool RadioButton(const char* label, int* v, int v_button); + IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); + IMGUI_API void PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); + IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0), int stride = sizeof(float)); + IMGUI_API void PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0,0)); + IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-1,0), const char* overlay = NULL); + + // Widgets: Combo Box + // The new BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it. + // The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. + IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); + IMGUI_API void EndCombo(); + IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" + IMGUI_API bool Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1); + + // Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go off-bounds) + // For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x + IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); // If v_min >= v_max we have no bound + IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* display_format = "%.3f", const char* display_format_max = NULL, float power = 1.0f); + IMGUI_API bool DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); // If v_min >= v_max we have no bound + IMGUI_API bool DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f"); + IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* display_format = "%.0f", const char* display_format_max = NULL); + + // Widgets: Input with Keyboard + IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0,0), ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat2(const char* label, float v[2], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat3(const char* label, float v[3], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputFloat4(const char* label, float v[4], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100, ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags = 0); + IMGUI_API bool InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags = 0); + + // Widgets: Sliders (tip: ctrl+click on a slider to input with keyboard. manually input values aren't clamped, can go off-bounds) + IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); // adjust display_format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for logarithmic sliders + IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f); + IMGUI_API bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format = "%.0f"); + IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format = "%.0f"); + + // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little colored preview square that can be left-clicked to open a picker, and right-clicked to open an option menu.) + // Note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can the pass the address of a first float element out of a contiguous structure, e.g. &myvector.x + IMGUI_API bool ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); + IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL); + IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0)); // display a colored square/button, hover for details, return true when pressed. + IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. + + // Widgets: Trees + IMGUI_API bool TreeNode(const char* label); // if returning 'true' the node is open and the tree id is pushed into the id stack. user is responsible for calling TreePop(). + IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). + IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " + IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); + IMGUI_API bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0); + IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); + IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); + IMGUI_API void TreePush(const char* str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call Push/Pop yourself for layout purpose + IMGUI_API void TreePush(const void* ptr_id = NULL); // " + IMGUI_API void TreePop(); // ~ Unindent()+PopId() + IMGUI_API void TreeAdvanceToLabelPos(); // advance cursor x position by GetTreeNodeToLabelSpacing() + IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode + IMGUI_API void SetNextTreeNodeOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. + IMGUI_API bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). + IMGUI_API bool CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags = 0); // when 'p_open' isn't NULL, display an additional small close button on upper right of the header + + // Widgets: Selectable / Lists + IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); // size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height + IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0)); + IMGUI_API bool ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1); + IMGUI_API bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0,0)); // use if you want to reimplement ListBox() will custom data or interactions. make sure to call ListBoxFooter() afterwards. + IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // " + IMGUI_API void ListBoxFooter(); // terminate the scrolling region + + // Widgets: Value() Helpers. Output single value in "name: value" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace) + IMGUI_API void Value(const char* prefix, bool b); + IMGUI_API void Value(const char* prefix, int v); + IMGUI_API void Value(const char* prefix, unsigned int v); + IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); + + // Tooltips + IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set text tooltip under mouse-cursor, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip(). + IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); + IMGUI_API void BeginTooltip(); // begin/append a tooltip window. to create full-featured tooltip (with any kind of contents). + IMGUI_API void EndTooltip(); + + // Menus + IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. only call EndMainMenuBar() if this returns true! + IMGUI_API void EndMainMenuBar(); + IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). only call EndMenuBar() if this returns true! + IMGUI_API void EndMenuBar(); + IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! + IMGUI_API void EndMenu(); + IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. shortcuts are displayed for convenience but not processed by ImGui at the moment + IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL + + // Popups + IMGUI_API void OpenPopup(const char* str_id); // call to mark popup as open (don't call every frame!). popups are closed when user click outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, int mouse_button = 1); // helper to open popup when clicked on last item. return true when just opened. + IMGUI_API bool BeginPopup(const char* str_id); // return true if the popup is open, and you can start outputting to it. only call EndPopup() if BeginPopup() returned true! + IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags extra_flags = 0); // modal dialog (block interactions behind the modal window, can't close the modal window by clicking outside) + IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! + IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, int mouse_button = 1, bool also_over_items = true); // helper to open and begin popup when clicked on current window. + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (where there are no imgui windows). + IMGUI_API void EndPopup(); + IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open + IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. + + // Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are automatically opened during logging. + IMGUI_API void LogToTTY(int max_depth = -1); // start logging to tty + IMGUI_API void LogToFile(int max_depth = -1, const char* filename = NULL); // start logging to file + IMGUI_API void LogToClipboard(int max_depth = -1); // start logging to OS clipboard + IMGUI_API void LogFinish(); // stop logging (close file, etc.) + IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard + IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) + + // Drag and Drop + // [BETA API] Missing Demo code. API may evolve. + IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0, int mouse_button = 0); // call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource() + IMGUI_API bool SetDragDropPayload(const char* type, const void* data, size_t size, ImGuiCond cond = 0);// type is a user defined string of maximum 8 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. + IMGUI_API void EndDragDropSource(); + IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive an item. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget() + IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0); // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released. + IMGUI_API void EndDragDropTarget(); + + // Clipping + IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect); + IMGUI_API void PopClipRect(); + + // Styles + IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); + IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); + IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); + + // Focus + // (FIXME: Those functions will be reworked after we merge the navigation branch + have a pass at focusing/tabbing features.) + // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your code more forward compatible when navigation branch is merged) + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window (WIP navigation branch only). Pleaase use instead of SetScrollHere(). + IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + + // Utilities + IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. + IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false) + IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) + IMGUI_API bool IsItemVisible(); // is the last item visible? (aka not out of sight due to clipping/scrolling.) + IMGUI_API bool IsAnyItemHovered(); + IMGUI_API bool IsAnyItemActive(); + IMGUI_API ImVec2 GetItemRectMin(); // get bounding rectangle of last item, in screen space + IMGUI_API ImVec2 GetItemRectMax(); // " + IMGUI_API ImVec2 GetItemRectSize(); // get size of last item, in screen space + IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. + IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags = 0); // is current window focused? or its root/child, depending on flags. see flags for options. + IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags = 0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. + IMGUI_API bool IsAnyWindowFocused(); + IMGUI_API bool IsAnyWindowHovered(); // is mouse hovering any visible window + IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. + IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. + IMGUI_API float GetTime(); + IMGUI_API int GetFrameCount(); + IMGUI_API ImDrawList* GetOverlayDrawList(); // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text + IMGUI_API ImDrawListSharedData* GetDrawListSharedData(); + IMGUI_API const char* GetStyleColorName(ImGuiCol idx); + IMGUI_API ImVec2 CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge = false, float outward = +0.0f); // utility to find the closest point the last item bounding rectangle edge. useful to visually link items + IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can. + + IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame + IMGUI_API void EndChildFrame(); + + IMGUI_API ImVec4 ColorConvertU32ToFloat4(ImU32 in); + IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); + IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); + IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); + + // Inputs + IMGUI_API int GetKeyIndex(ImGuiKey imgui_key); // map ImGuiKey_* values into user's key index. == io.KeyMap[key] + IMGUI_API bool IsKeyDown(int user_key_index); // is key being held. == io.KeysDown[user_key_index]. note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your backend/engine stored them into KeyDown[]! + IMGUI_API bool IsKeyPressed(int user_key_index, bool repeat = true); // was key pressed (went from !Down to Down). if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate + IMGUI_API bool IsKeyReleased(int user_key_index); // was key released (went from Down to !Down).. + IMGUI_API int GetKeyPressedAmount(int key_index, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate + IMGUI_API bool IsMouseDown(int button); // is mouse button held + IMGUI_API bool IsMouseClicked(int button, bool repeat = false); // did mouse button clicked (went from !Down to Down) + IMGUI_API bool IsMouseDoubleClicked(int button); // did mouse button double-clicked. a double-click returns false in IsMouseClicked(). uses io.MouseDoubleClickTime. + IMGUI_API bool IsMouseReleased(int button); // did mouse button released (went from Down to !Down) + IMGUI_API bool IsMouseDragging(int button = 0, float lock_threshold = -1.0f); // is mouse dragging. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true); // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings. disregarding of consideration of focus/window ordering/blocked by a popup. + IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // + IMGUI_API ImVec2 GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls + IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve backup of mouse positioning at the time of opening popup we have BeginPopup() into + IMGUI_API ImVec2 GetMouseDragDelta(int button = 0, float lock_threshold = -1.0f); // dragging amount since clicking. if lock_threshold < -1.0f uses io.MouseDraggingThreshold + IMGUI_API void ResetMouseDragDelta(int button = 0); // + IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you + IMGUI_API void SetMouseCursor(ImGuiMouseCursor type); // set desired cursor type + IMGUI_API void CaptureKeyboardFromApp(bool capture = true); // manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your application handle). e.g. force capture keyboard when your widget is being hovered. + IMGUI_API void CaptureMouseFromApp(bool capture = true); // manually override io.WantCaptureMouse flag next frame (said flag is entirely left for your application handle). + + // Helpers functions to access functions pointers in ImGui::GetIO() + IMGUI_API void* MemAlloc(size_t sz); + IMGUI_API void MemFree(void* ptr); + IMGUI_API const char* GetClipboardText(); + IMGUI_API void SetClipboardText(const char* text); + + // Internal context access - if you want to use multiple context, share context between modules (e.g. DLL). There is a default context created and active by default. + // All contexts share a same ImFontAtlas by default. If you want different font atlas, you can new() them and overwrite the GetIO().Fonts variable of an ImGui context. + IMGUI_API const char* GetVersion(); + IMGUI_API ImGuiContext* CreateContext(void* (*malloc_fn)(size_t) = NULL, void (*free_fn)(void*) = NULL); + IMGUI_API void DestroyContext(ImGuiContext* ctx); + IMGUI_API ImGuiContext* GetCurrentContext(); + IMGUI_API void SetCurrentContext(ImGuiContext* ctx); + +} // namespace ImGui + +// Flags for ImGui::Begin() +enum ImGuiWindowFlags_ +{ + ImGuiWindowFlags_NoTitleBar = 1 << 0, // Disable title-bar + ImGuiWindowFlags_NoResize = 1 << 1, // Disable user resizing with the lower-right grip + ImGuiWindowFlags_NoMove = 1 << 2, // Disable user moving the window + ImGuiWindowFlags_NoScrollbar = 1 << 3, // Disable scrollbars (window can still scroll with mouse or programatically) + ImGuiWindowFlags_NoScrollWithMouse = 1 << 4, // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set. + ImGuiWindowFlags_NoCollapse = 1 << 5, // Disable user collapsing window by double-clicking on it + ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame + //ImGuiWindowFlags_ShowBorders = 1 << 7, // Show borders around windows and items (OBSOLETE! Use e.g. style.FrameBorderSize=1.0f to enable borders). + ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file + ImGuiWindowFlags_NoInputs = 1 << 9, // Disable catching mouse or keyboard inputs, hovering test with pass through. + ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar + ImGuiWindowFlags_HorizontalScrollbar = 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. + ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state + ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programatically giving it focus) + ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) + ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) + ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) + ImGuiWindowFlags_ResizeFromAnySide = 1 << 17, // (WIP) Enable resize from any corners and borders. Your back-end needs to honor the different values of io.MouseCursor set by imgui. + + // [Internal] + ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() + ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() + ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() + ImGuiWindowFlags_ChildMenu = 1 << 28 // Don't use! For internal use by BeginMenu() +}; + +// Flags for ImGui::InputText() +enum ImGuiInputTextFlags_ +{ + ImGuiInputTextFlags_CharsDecimal = 1 << 0, // Allow 0123456789.+-*/ + ImGuiInputTextFlags_CharsHexadecimal = 1 << 1, // Allow 0123456789ABCDEFabcdef + ImGuiInputTextFlags_CharsUppercase = 1 << 2, // Turn a..z into A..Z + ImGuiInputTextFlags_CharsNoBlank = 1 << 3, // Filter out spaces, tabs + ImGuiInputTextFlags_AutoSelectAll = 1 << 4, // Select entire text when first taking mouse focus + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 5, // Return 'true' when Enter is pressed (as opposed to when the value was modified) + ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Call user function on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Call user function on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Call user function every time. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). + ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally + ImGuiInputTextFlags_AlwaysInsertMode = 1 << 13, // Insert mode + ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode + ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' + ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() +}; + +// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() +enum ImGuiTreeNodeFlags_ +{ + ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected + ImGuiTreeNodeFlags_Framed = 1 << 1, // Full colored frame (e.g. for CollapsingHeader) + ImGuiTreeNodeFlags_AllowItemOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one + ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack + ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) + ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open + ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node + ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. + ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). + ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow + ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). + //ImGuITreeNodeFlags_SpanAllAvailWidth = 1 << 11, // FIXME: TODO: Extend hit box horizontally even if not framed + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 12, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoAutoOpenOnLog + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiTreeNodeFlags_AllowOverlapMode = ImGuiTreeNodeFlags_AllowItemOverlap +#endif +}; + +// Flags for ImGui::Selectable() +enum ImGuiSelectableFlags_ +{ + ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this don't close parent popup window + ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Selectable frame can span all columns (text will still fit in current column) + ImGuiSelectableFlags_AllowDoubleClick = 1 << 2 // Generate press events on double clicks too +}; + +// Flags for ImGui::BeginCombo() +enum ImGuiComboFlags_ +{ + ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default + ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() + ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) + ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible + ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible + ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest +}; + +// Flags for ImGui::IsWindowFocused() +enum ImGuiFocusedFlags_ +{ + ImGuiFocusedFlags_ChildWindows = 1 << 0, // IsWindowFocused(): Return true if any children of the window is focused + ImGuiFocusedFlags_RootWindow = 1 << 1, // IsWindowFocused(): Test from root window (top most parent of the current hierarchy) + ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows +}; + +// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() +enum ImGuiHoveredFlags_ +{ + ImGuiHoveredFlags_Default = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + ImGuiHoveredFlags_ChildWindows = 1 << 0, // IsWindowHovered() only: Return true if any children of the window is hovered + ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 2, // Return true even if a popup window is normally blocking access to this item/window + //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 3, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 4, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. + ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 5, // Return true even if the position is overlapped by another window + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, + ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows +}; + +// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ +{ + // BeginDragDropSource() flags + ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disable this behavior. + ImGuiDragDropFlags_SourceNoDisableHover = 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return true, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item. + ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. + ImGuiDragDropFlags_SourceAllowNullID = 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. + ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously. + // AcceptDragDropPayload() flags + ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. + ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. + ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect // For peeking ahead and inspecting the payload before delivery. +}; + +// Standard Drag and Drop payload types. You can define you own payload types using 8-characters long strings. Types starting with '_' are defined by Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_COLOR_3F "_COL3F" // float[3] // Standard type for colors, without alpha. User code may use this type. +#define IMGUI_PAYLOAD_TYPE_COLOR_4F "_COL4F" // float[4] // Standard type for colors. User code may use this type. + +// User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array +enum ImGuiKey_ +{ + ImGuiKey_Tab, // for tabbing through fields + ImGuiKey_LeftArrow, // for text edit + ImGuiKey_RightArrow,// for text edit + ImGuiKey_UpArrow, // for text edit + ImGuiKey_DownArrow, // for text edit + ImGuiKey_PageUp, + ImGuiKey_PageDown, + ImGuiKey_Home, // for text edit + ImGuiKey_End, // for text edit + ImGuiKey_Delete, // for text edit + ImGuiKey_Backspace, // for text edit + ImGuiKey_Enter, // for text edit + ImGuiKey_Escape, // for text edit + ImGuiKey_A, // for text edit CTRL+A: select all + ImGuiKey_C, // for text edit CTRL+C: copy + ImGuiKey_V, // for text edit CTRL+V: paste + ImGuiKey_X, // for text edit CTRL+X: cut + ImGuiKey_Y, // for text edit CTRL+Y: redo + ImGuiKey_Z, // for text edit CTRL+Z: undo + ImGuiKey_COUNT +}; + +// Enumeration for PushStyleColor() / PopStyleColor() +enum ImGuiCol_ +{ + ImGuiCol_Text, + ImGuiCol_TextDisabled, + ImGuiCol_WindowBg, // Background of normal windows + ImGuiCol_ChildBg, // Background of child windows + ImGuiCol_PopupBg, // Background of popups, menus, tooltips windows + ImGuiCol_Border, + ImGuiCol_BorderShadow, + ImGuiCol_FrameBg, // Background of checkbox, radio button, plot, slider, text input + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBg, + ImGuiCol_TitleBgActive, + ImGuiCol_TitleBgCollapsed, + ImGuiCol_MenuBarBg, + ImGuiCol_ScrollbarBg, + ImGuiCol_ScrollbarGrab, + ImGuiCol_ScrollbarGrabHovered, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_Separator, + ImGuiCol_SeparatorHovered, + ImGuiCol_SeparatorActive, + ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_CloseButton, + ImGuiCol_CloseButtonHovered, + ImGuiCol_CloseButtonActive, + ImGuiCol_PlotLines, + ImGuiCol_PlotLinesHovered, + ImGuiCol_PlotHistogram, + ImGuiCol_PlotHistogramHovered, + ImGuiCol_TextSelectedBg, + ImGuiCol_ModalWindowDarkening, // darken entire screen when a modal window is active + ImGuiCol_DragDropTarget, + ImGuiCol_COUNT + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //, ImGuiCol_ComboBg = ImGuiCol_PopupBg // ComboBg has been merged with PopupBg, so a redirect isn't accurate. + , ImGuiCol_ChildWindowBg = ImGuiCol_ChildBg, ImGuiCol_Column = ImGuiCol_Separator, ImGuiCol_ColumnHovered = ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive = ImGuiCol_SeparatorActive +#endif +}; + +// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. +// NB: the enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. During initialization, feel free to just poke into ImGuiStyle directly. +// NB: if changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. +enum ImGuiStyleVar_ +{ + // Enum name ......................// Member in ImGuiStyle structure (see ImGuiStyle for descriptions) + ImGuiStyleVar_Alpha, // float Alpha + ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding + ImGuiStyleVar_WindowRounding, // float WindowRounding + ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize + ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize + ImGuiStyleVar_ChildRounding, // float ChildRounding + ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize + ImGuiStyleVar_PopupRounding, // float PopupRounding + ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize + ImGuiStyleVar_FramePadding, // ImVec2 FramePadding + ImGuiStyleVar_FrameRounding, // float FrameRounding + ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize + ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing + ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing + ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_GrabMinSize, // float GrabMinSize + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_Count_ + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiStyleVar_ChildWindowRounding = ImGuiStyleVar_ChildRounding +#endif +}; + +// Enumeration for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() +enum ImGuiColorEditFlags_ +{ + ImGuiColorEditFlags_NoAlpha = 1 << 1, // // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (read 3 components from the input pointer). + ImGuiColorEditFlags_NoPicker = 1 << 2, // // ColorEdit: disable picker when clicking on colored square. + ImGuiColorEditFlags_NoOptions = 1 << 3, // // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. + ImGuiColorEditFlags_NoSmallPreview = 1 << 4, // // ColorEdit, ColorPicker: disable colored square preview next to the inputs. (e.g. to show only the inputs) + ImGuiColorEditFlags_NoInputs = 1 << 5, // // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview colored square). + ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. + ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). + ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small colored square preview instead. + // User Options (right-click on widget to change some of them). You can set application defaults using SetColorEditOptions(). The idea is that you probably don't want to override them in most of your calls, let the user choose and/or call SetColorEditOptions() during startup. + ImGuiColorEditFlags_AlphaBar = 1 << 9, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + ImGuiColorEditFlags_AlphaPreview = 1 << 10, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 11, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. + ImGuiColorEditFlags_HDR = 1 << 12, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). + ImGuiColorEditFlags_RGB = 1 << 13, // [Inputs] // ColorEdit: choose one among RGB/HSV/HEX. ColorPicker: choose any combination using RGB/HSV/HEX. + ImGuiColorEditFlags_HSV = 1 << 14, // [Inputs] // " + ImGuiColorEditFlags_HEX = 1 << 15, // [Inputs] // " + ImGuiColorEditFlags_Uint8 = 1 << 16, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. + ImGuiColorEditFlags_Float = 1 << 17, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. + ImGuiColorEditFlags_PickerHueBar = 1 << 18, // [PickerMode] // ColorPicker: bar for Hue, rectangle for Sat/Value. + ImGuiColorEditFlags_PickerHueWheel = 1 << 19, // [PickerMode] // ColorPicker: wheel for Hue, triangle for Sat/Value. + // Internals/Masks + ImGuiColorEditFlags__InputsMask = ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_HSV|ImGuiColorEditFlags_HEX, + ImGuiColorEditFlags__DataTypeMask = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_Float, + ImGuiColorEditFlags__PickerMask = ImGuiColorEditFlags_PickerHueWheel|ImGuiColorEditFlags_PickerHueBar, + ImGuiColorEditFlags__OptionsDefault = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_PickerHueBar // Change application default using SetColorEditOptions() +}; + +// Enumeration for GetMouseCursor() +enum ImGuiMouseCursor_ +{ + ImGuiMouseCursor_None = -1, + ImGuiMouseCursor_Arrow = 0, + ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. + ImGuiMouseCursor_Move, // Unused + ImGuiMouseCursor_ResizeNS, // When hovering over an horizontal border + ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column + ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window + ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window + ImGuiMouseCursor_Count_ +}; + +// Condition for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions +// All those functions treat 0 as a shortcut to ImGuiCond_Always. From the point of view of the user use this as an enum (don't combine multiple values into flags). +enum ImGuiCond_ +{ + ImGuiCond_Always = 1 << 0, // Set the variable + ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call with succeed) + ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the window has no saved data (if doesn't exist in the .ini file) + ImGuiCond_Appearing = 1 << 3 // Set the variable if the window is appearing after being hidden/inactive (or the first time) + + // Obsolete names (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , ImGuiSetCond_Always = ImGuiCond_Always, ImGuiSetCond_Once = ImGuiCond_Once, ImGuiSetCond_FirstUseEver = ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing = ImGuiCond_Appearing +#endif +}; + +struct ImGuiStyle +{ + float Alpha; // Global alpha applies to everything in ImGui + ImVec2 WindowPadding; // Padding within a window + float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows + float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly) + ImVec2 WindowMinSize; // Minimum window size + ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. + float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. + float ChildBorderSize; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly) + float PopupRounding; // Radius of popup window corners rounding. + float PopupBorderSize; // Thickness of border around popup windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly) + ImVec2 FramePadding; // Padding within a framed rectangle (used by most widgets) + float FrameRounding; // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets). + float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly) + ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines + ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + float ColumnsMinSpacing; // Minimum horizontal spacing between two columns + float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar + float ScrollbarRounding; // Radius of grab corners for scrollbar + float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. + float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for horizontally+vertically centered. + ImVec2 DisplayWindowPadding; // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows. + ImVec2 DisplaySafeAreaPadding; // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + bool AntiAliasedLines; // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU. + bool AntiAliasedFill; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) + float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + ImVec4 Colors[ImGuiCol_COUNT]; + + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); +}; + +// This is where your app communicate with ImGui. Access via ImGui::GetIO(). +// Read 'Programmer guide' section in .cpp file for general usage. +struct ImGuiIO +{ + //------------------------------------------------------------------ + // Settings (fill once) // Default value: + //------------------------------------------------------------------ + + ImVec2 DisplaySize; // // Display size, in pixels. For clamping windows positions. + float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. + float IniSavingRate; // = 5.0f // Maximum time between saving positions/sizes to .ini file, in seconds. + const char* IniFilename; // = "imgui.ini" // Path to .ini file. NULL to disable .ini saving. + const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no file is specified). + float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging + int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array + float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). + float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. + void* UserData; // = NULL // Store your own data for retrieval by callbacks. + + ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. + float FontGlobalScale; // = 1.0f // Global scale all fonts + bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. + ImVec2 DisplayFramebufferScale; // = (1.0f,1.0f) // For retina display or other situations where window coordinates are different from framebuffer coordinates. User storage only, presently not used by ImGui. + ImVec2 DisplayVisibleMin; // (0.0f,0.0f) // If you use DisplaySize as a virtual space larger than your screen, set DisplayVisibleMin/Max to the visible area. + ImVec2 DisplayVisibleMax; // (0.0f,0.0f) // If the values are the same, we defaults to Min=(0.0f) and Max=DisplaySize + + // Advanced/subtle behaviors + bool OptMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl + bool OptCursorBlink; // = true // Enable blinking cursor, for users who consider it annoying. + + //------------------------------------------------------------------ + // Settings (User Functions) + //------------------------------------------------------------------ + + // Rendering function, will be called in Render(). + // Alternatively you can keep this to NULL and call GetDrawData() after Render() to get the same pointer. + // See example applications if you are unsure of how to implement this. + void (*RenderDrawListsFn)(ImDrawData* data); + + // Optional: access OS clipboard + // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) + const char* (*GetClipboardTextFn)(void* user_data); + void (*SetClipboardTextFn)(void* user_data, const char* text); + void* ClipboardUserData; + + // Optional: override memory allocations. MemFreeFn() may be called with a NULL pointer. + // (default to posix malloc/free) + void* (*MemAllocFn)(size_t sz); + void (*MemFreeFn)(void* ptr); + + // Optional: notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME in Windows) + // (default to use native imm32 api on Windows) + void (*ImeSetInputScreenPosFn)(int x, int y); + void* ImeWindowHandle; // (Windows) Set this to your HWND to get automatic IME cursor positioning. + + //------------------------------------------------------------------ + // Input - Fill before calling NewFrame() + //------------------------------------------------------------------ + + ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX,-FLT_MAX) if mouse is unavailable (on another screen, etc.) + bool MouseDown[5]; // Mouse buttons: left, right, middle + extras. ImGui itself mostly only uses left button (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. + float MouseWheel; // Mouse wheel: 1 unit scrolls about 5 lines text. + bool MouseDrawCursor; // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). + bool KeyCtrl; // Keyboard modifier pressed: Control + bool KeyShift; // Keyboard modifier pressed: Shift + bool KeyAlt; // Keyboard modifier pressed: Alt + bool KeySuper; // Keyboard modifier pressed: Cmd/Super/Windows + bool KeysDown[512]; // Keyboard keys that are pressed (in whatever storage order you naturally have access to keyboard data) + ImWchar InputCharacters[16+1]; // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper. + + // Functions + IMGUI_API void AddInputCharacter(ImWchar c); // Add new character into InputCharacters[] + IMGUI_API void AddInputCharactersUTF8(const char* utf8_chars); // Add new characters into InputCharacters[] from an UTF-8 string + inline void ClearInputCharacters() { InputCharacters[0] = 0; } // Clear the text input buffer manually + + //------------------------------------------------------------------ + // Output - Retrieve after calling NewFrame() + //------------------------------------------------------------------ + + bool WantCaptureMouse; // When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. This is set by ImGui when it wants to use your mouse (e.g. unclicked mouse is hovering a window, or a widget is active). + bool WantCaptureKeyboard; // When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. This is set by ImGui when it wants to use your keyboard inputs. + bool WantTextInput; // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). + bool WantMoveMouse; // [BETA-NAV] MousePos has been altered, back-end should reposition mouse on next frame. Set only when 'NavMovesMouse=true'. + float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames + int MetricsAllocs; // Number of active memory allocations + int MetricsRenderVertices; // Vertices output during last call to Render() + int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 + int MetricsActiveWindows; // Number of visible root windows (exclude child windows) + ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. + + //------------------------------------------------------------------ + // [Internal] ImGui will maintain those fields. Forward compatibility not guaranteed! + //------------------------------------------------------------------ + + ImVec2 MousePosPrev; // Previous mouse position temporary storage (nb: not for public use, set to MousePos in NewFrame()) + ImVec2 MouseClickedPos[5]; // Position at time of clicking + float MouseClickedTime[5]; // Time of last click (used to figure out double-click) + bool MouseClicked[5]; // Mouse button went from !Down to Down + bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? + bool MouseReleased[5]; // Mouse button went from Down to !Down + bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds. + float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) + float MouseDownDurationPrev[5]; // Previous time the mouse button has been down + ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point + float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point + float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed) + float KeysDownDurationPrev[512]; // Previous duration the key has been down + + IMGUI_API ImGuiIO(); +}; + +//----------------------------------------------------------------------------- +// Obsolete functions (Will be removed! Also see 'API BREAKING CHANGES' section in imgui.cpp) +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ + static inline void ShowTestWindow() { return ShowDemoWindow(); } // OBSOLETE 1.53+ + static inline bool IsRootWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); } // OBSOLETE 1.53+ + static inline bool IsRootWindowOrAnyChildFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } // OBSOLETE 1.53+ + static inline void SetNextWindowContentWidth(float width) { SetNextWindowContentSize(ImVec2(width, 0.0f)); } // OBSOLETE 1.53+ (nb: original version preserved last Y value set by SetNextWindowContentSize()) + static inline bool IsRootWindowOrAnyChildHovered(ImGuiHoveredFlags flags = 0) { return IsItemHovered(flags | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows); } // OBSOLETE 1.53+ use flags directly + bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // OBSOLETE 1.52+. use SetNextWindowSize() instead if you want to set a window size. + static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } // OBSOLETE 1.52+ + static inline void SetNextWindowPosCenter(ImGuiCond cond = 0) { SetNextWindowPos(ImVec2(GetIO().DisplaySize.x * 0.5f, GetIO().DisplaySize.y * 0.5f), cond, ImVec2(0.5f, 0.5f)); } // OBSOLETE 1.52+ + static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } // OBSOLETE 1.51+ + static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // OBSOLETE 1.51+. This was partly broken. You probably wanted to use ImGui::GetIO().WantCaptureMouse instead. + static inline bool IsMouseHoveringAnyWindow() { return IsAnyWindowHovered(); } // OBSOLETE 1.51+ + static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } // OBSOLETE 1.51+ + static inline bool CollapsingHeader(const char* label, const char* str_id, bool framed = true, bool default_open = false) { (void)str_id; (void)framed; ImGuiTreeNodeFlags default_open_flags = 1 << 5; return CollapsingHeader(label, (default_open ? default_open_flags : 0)); } // OBSOLETE 1.49+ +} +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +// Lightweight std::vector<> like class to avoid dragging dependencies (also: windows implementation of STL with debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug). +// Our implementation does NOT call C++ constructors/destructors. This is intentional and we do not require it. Do not use this class as a straight std::vector replacement in your code! +template +class ImVector +{ +public: + int Size; + int Capacity; + T* Data; + + typedef T value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + inline ImVector() { Size = Capacity = 0; Data = NULL; } + inline ~ImVector() { if (Data) ImGui::MemFree(Data); } + + inline bool empty() const { return Size == 0; } + inline int size() const { return Size; } + inline int capacity() const { return Capacity; } + + inline value_type& operator[](int i) { IM_ASSERT(i < Size); return Data[i]; } + inline const value_type& operator[](int i) const { IM_ASSERT(i < Size); return Data[i]; } + + inline void clear() { if (Data) { Size = Capacity = 0; ImGui::MemFree(Data); Data = NULL; } } + inline iterator begin() { return Data; } + inline const_iterator begin() const { return Data; } + inline iterator end() { return Data + Size; } + inline const_iterator end() const { return Data + Size; } + inline value_type& front() { IM_ASSERT(Size > 0); return Data[0]; } + inline const value_type& front() const { IM_ASSERT(Size > 0); return Data[0]; } + inline value_type& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline const value_type& back() const { IM_ASSERT(Size > 0); return Data[Size - 1]; } + inline void swap(ImVector& rhs) { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } + + inline int _grow_capacity(int sz) const { int new_capacity = Capacity ? (Capacity + Capacity/2) : 8; return new_capacity > sz ? new_capacity : sz; } + + inline void resize(int new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } + inline void resize(int new_size, const T& v){ if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) Data[n] = v; Size = new_size; } + inline void reserve(int new_capacity) + { + if (new_capacity <= Capacity) + return; + T* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(T)); + if (Data) + memcpy(new_data, Data, (size_t)Size * sizeof(T)); + ImGui::MemFree(Data); + Data = new_data; + Capacity = new_capacity; + } + + inline void push_back(const value_type& v) { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); Data[Size++] = v; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline void push_front(const value_type& v) { if (Size == 0) push_back(v); else insert(Data, v); } + + inline iterator erase(const_iterator it) { IM_ASSERT(it >= Data && it < Data+Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; } + inline iterator insert(const_iterator it, const value_type& v) { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(value_type)); Data[off] = v; Size++; return Data + off; } + inline bool contains(const value_type& v) const { const T* data = Data; const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; } +}; + +// Helper: execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame. +// Usage: +// static ImGuiOnceUponAFrame oaf; +// if (oaf) +// ImGui::Text("This will be called only once per frame"); +struct ImGuiOnceUponAFrame +{ + ImGuiOnceUponAFrame() { RefFrame = -1; } + mutable int RefFrame; + operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } +}; + +// Helper macro for ImGuiOnceUponAFrame. Attention: The macro expands into 2 statement so make sure you don't use it within e.g. an if() statement without curly braces. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Will obsolete +#define IMGUI_ONCE_UPON_A_FRAME static ImGuiOnceUponAFrame imgui_oaf; if (imgui_oaf) +#endif + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +struct ImGuiTextFilter +{ + struct TextRange + { + const char* b; + const char* e; + + TextRange() { b = e = NULL; } + TextRange(const char* _b, const char* _e) { b = _b; e = _e; } + const char* begin() const { return b; } + const char* end() const { return e; } + bool empty() const { return b == e; } + char front() const { return *b; } + static bool is_blank(char c) { return c == ' ' || c == '\t'; } + void trim_blanks() { while (b < e && is_blank(*b)) b++; while (e > b && is_blank(*(e-1))) e--; } + IMGUI_API void split(char separator, ImVector& out); + }; + + char InputBuf[256]; + ImVector Filters; + int CountGrep; + + IMGUI_API ImGuiTextFilter(const char* default_filter = ""); + IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build + IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; + IMGUI_API void Build(); + void Clear() { InputBuf[0] = 0; Build(); } + bool IsActive() const { return !Filters.empty(); } +}; + +// Helper: Text buffer for logging/accumulating text +struct ImGuiTextBuffer +{ + ImVector Buf; + + ImGuiTextBuffer() { Buf.push_back(0); } + inline char operator[](int i) { return Buf.Data[i]; } + const char* begin() const { return &Buf.front(); } + const char* end() const { return &Buf.back(); } // Buf is zero-terminated, so end() will point on the zero-terminator + int size() const { return Buf.Size - 1; } + bool empty() { return Buf.Size <= 1; } + void clear() { Buf.clear(); Buf.push_back(0); } + void reserve(int capacity) { Buf.reserve(capacity); } + const char* c_str() const { return Buf.Data; } + IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); +}; + +// Helper: Simple Key->value storage +// Typically you don't have to worry about this since a storage is held within each Window. +// We use it to e.g. store collapse state for a tree (Int 0/1), store color edit options. +// This is optimized for efficient reading (dichotomy into a contiguous buffer), rare writing (typically tied to user interactions) +// You can use it as custom user storage for temporary values. Declare your own storage if, for example: +// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state). +// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient) +// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types. +struct ImGuiStorage +{ + struct Pair + { + ImGuiID key; + union { int val_i; float val_f; void* val_p; }; + Pair(ImGuiID _key, int _val_i) { key = _key; val_i = _val_i; } + Pair(ImGuiID _key, float _val_f) { key = _key; val_f = _val_f; } + Pair(ImGuiID _key, void* _val_p) { key = _key; val_p = _val_p; } + }; + ImVector Data; + + // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) + // - Set***() functions find pair, insertion on demand if missing. + // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair. + void Clear() { Data.clear(); } + IMGUI_API int GetInt(ImGuiID key, int default_val = 0) const; + IMGUI_API void SetInt(ImGuiID key, int val); + IMGUI_API bool GetBool(ImGuiID key, bool default_val = false) const; + IMGUI_API void SetBool(ImGuiID key, bool val); + IMGUI_API float GetFloat(ImGuiID key, float default_val = 0.0f) const; + IMGUI_API void SetFloat(ImGuiID key, float val); + IMGUI_API void* GetVoidPtr(ImGuiID key) const; // default_val is NULL + IMGUI_API void SetVoidPtr(ImGuiID key, void* val); + + // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set. + // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. + // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct) + // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar; + IMGUI_API int* GetIntRef(ImGuiID key, int default_val = 0); + IMGUI_API bool* GetBoolRef(ImGuiID key, bool default_val = false); + IMGUI_API float* GetFloatRef(ImGuiID key, float default_val = 0.0f); + IMGUI_API void** GetVoidPtrRef(ImGuiID key, void* default_val = NULL); + + // Use on your own storage if you know only integer are being stored (open/close all tree nodes) + IMGUI_API void SetAllInt(int val); + + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. + IMGUI_API void BuildSortByKey(); +}; + +// Shared state of InputText(), passed to callback when a ImGuiInputTextFlags_Callback* flag is used and the corresponding callback is triggered. +struct ImGuiTextEditCallbackData +{ + ImGuiInputTextFlags EventFlag; // One of ImGuiInputTextFlags_Callback* // Read-only + ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only + void* UserData; // What user passed to InputText() // Read-only + bool ReadOnly; // Read-only mode // Read-only + + // CharFilter event: + ImWchar EventChar; // Character input // Read-write (replace character or set to zero) + + // Completion,History,Always events: + // If you modify the buffer contents make sure you update 'BufTextLen' and set 'BufDirty' to true. + ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only + char* Buf; // Current text buffer // Read-write (pointed data only, can't replace the actual pointer) + int BufTextLen; // Current text length in bytes // Read-write + int BufSize; // Maximum text length in bytes // Read-only + bool BufDirty; // Set if you modify Buf/BufTextLen!! // Write + int CursorPos; // // Read-write + int SelectionStart; // // Read-write (== to SelectionEnd when no selection) + int SelectionEnd; // // Read-write + + // NB: Helper functions for text manipulation. Calling those function loses selection. + IMGUI_API void DeleteChars(int pos, int bytes_count); + IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL); + bool HasSelection() const { return SelectionStart != SelectionEnd; } +}; + +// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin(). +// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough. +struct ImGuiSizeConstraintCallbackData +{ + void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints() + ImVec2 Pos; // Read-only. Window position, for reference. + ImVec2 CurrentSize; // Read-only. Current window size. + ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. +}; + +// Data payload for Drag and Drop operations +struct ImGuiPayload +{ + // Members + const void* Data; // Data (copied and owned by dear imgui) + int DataSize; // Data size + + // [Internal] + ImGuiID SourceId; // Source item id + ImGuiID SourceParentId; // Source parent id (if available) + int DataFrameCount; // Data timestamp + char DataType[8 + 1]; // Data type tag (short user-supplied string) + bool Preview; // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets) + bool Delivery; // Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + + ImGuiPayload() { Clear(); } + void Clear() { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; } + bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } + bool IsPreview() const { return Preview; } + bool IsDelivery() const { return Delivery; } +}; + +// Helpers macros to generate 32-bits encoded colors +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IM_COL32_R_SHIFT 16 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 0 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#else +#define IM_COL32_R_SHIFT 0 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 16 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#endif +#define IM_COL32(R,G,B,A) (((ImU32)(A)<>IM_COL32_R_SHIFT)&0xFF) * sc; Value.y = (float)((rgba>>IM_COL32_G_SHIFT)&0xFF) * sc; Value.z = (float)((rgba>>IM_COL32_B_SHIFT)&0xFF) * sc; Value.w = (float)((rgba>>IM_COL32_A_SHIFT)&0xFF) * sc; } + ImColor(float r, float g, float b, float a = 1.0f) { Value.x = r; Value.y = g; Value.z = b; Value.w = a; } + ImColor(const ImVec4& col) { Value = col; } + inline operator ImU32() const { return ImGui::ColorConvertFloat4ToU32(Value); } + inline operator ImVec4() const { return Value; } + + // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers. + inline void SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; } + static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r,g,b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r,g,b,a); } +}; + +// Helper: Manually clip large list of items. +// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse clipping based on visibility to save yourself from processing those items at all. +// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. +// ImGui already clip items based on their bounds but it needs to measure text size to do so. Coarse clipping before submission makes this cost and your own data fetching/submission cost null. +// Usage: +// ImGuiListClipper clipper(1000); // we have 1000 elements, evenly spaced. +// while (clipper.Step()) +// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) +// ImGui::Text("line number %d", i); +// - Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height (step skipped if we passed a known height as second arg to constructor). +// - Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. +// - (Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user call Step(). Does nothing and switch to Step 3.) +// - Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. +struct ImGuiListClipper +{ + float StartPosY; + float ItemsHeight; + int ItemsCount, StepNo, DisplayStart, DisplayEnd; + + // items_count: Use -1 to ignore (you can call Begin later). Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step). + // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). + // If you don't specify an items_height, you NEED to call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling Step(). + ImGuiListClipper(int items_count = -1, float items_height = -1.0f) { Begin(items_count, items_height); } // NB: Begin() initialize every fields (as we allow user to call Begin/End multiple times on a same instance if they want). + ~ImGuiListClipper() { IM_ASSERT(ItemsCount == -1); } // Assert if user forgot to call End() or Step() until false. + + IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1. + IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. +}; + +//----------------------------------------------------------------------------- +// Draw List +// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. +//----------------------------------------------------------------------------- + +// Draw callbacks for advanced uses. +// NB- You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering (you can poke into the draw list for that) +// Draw callback may be useful for example, A) Change your GPU render state, B) render a complex 3D scene inside a UI element (without an intermediate texture/render target), etc. +// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) cmd.UserCallback(parent_list, cmd); else RenderTriangles()' +typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); + +// Typically, 1 command = 1 GPU draw call (unless command is a callback) +struct ImDrawCmd +{ + unsigned int ElemCount; // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. + ImVec4 ClipRect; // Clipping rectangle (x1, y1, x2, y2) + ImTextureID TextureId; // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImDrawCallback UserCallback; // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. + void* UserCallbackData; // The draw callback code can access this. + + ImDrawCmd() { ElemCount = 0; ClipRect.x = ClipRect.y = ClipRect.z = ClipRect.w = 0.0f; TextureId = NULL; UserCallback = NULL; UserCallbackData = NULL; } +}; + +// Vertex index (override with '#define ImDrawIdx unsigned int' inside in imconfig.h) +#ifndef ImDrawIdx +typedef unsigned short ImDrawIdx; +#endif + +// Vertex layout +#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT +struct ImDrawVert +{ + ImVec2 pos; + ImVec2 uv; + ImU32 col; +}; +#else +// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h +// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine. +// The type has to be described within the macro (you can either declare the struct or use a typedef) +// NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM. +IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; +#endif + +// Draw channels are used by the Columns API to "split" the render list into different channels while building, so items of each column can be batched together. +// You can also use them to simulate drawing layers and submit primitives in a different order than how they will be rendered. +struct ImDrawChannel +{ + ImVector CmdBuffer; + ImVector IdxBuffer; +}; + +enum ImDrawCornerFlags_ +{ + ImDrawCornerFlags_TopLeft = 1 << 0, // 0x1 + ImDrawCornerFlags_TopRight = 1 << 1, // 0x2 + ImDrawCornerFlags_BotLeft = 1 << 2, // 0x4 + ImDrawCornerFlags_BotRight = 1 << 3, // 0x8 + ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, // 0x3 + ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, // 0xC + ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, // 0x5 + ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, // 0xA + ImDrawCornerFlags_All = 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience +}; + +enum ImDrawListFlags_ +{ + ImDrawListFlags_AntiAliasedLines = 1 << 0, + ImDrawListFlags_AntiAliasedFill = 1 << 1 +}; + +// Draw command list +// This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. +// Each ImGui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to access the current window draw list and draw custom primitives. +// You can interleave normal ImGui:: calls and adding primitives to the current draw list. +// All positions are generally in pixel coordinates (top-left at (0,0), bottom-right at io.DisplaySize), however you are totally free to apply whatever transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as well) +// Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. +struct ImDrawList +{ + // This is what you have to render + ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. + ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those + ImVector VtxBuffer; // Vertex buffer. + + // [Internal, used while building lists] + ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) + const char* _OwnerName; // Pointer to owner window's name for debugging + unsigned int _VtxCurrentIdx; // [Internal] == VtxBuffer.Size + ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] + ImVector _Path; // [Internal] current path building + int _ChannelsCurrent; // [Internal] current channel number (0) + int _ChannelsCount; // [Internal] number of active channels (1+) + ImVector _Channels; // [Internal] draw channels for columns API (not resized down so _ChannelsCount may be smaller than _Channels.Size) + + ImDrawList(const ImDrawListSharedData* shared_data) { _Data = shared_data; _OwnerName = NULL; Clear(); } + ~ImDrawList() { ClearFreeMemory(); } + IMGUI_API void PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) + IMGUI_API void PushClipRectFullScreen(); + IMGUI_API void PopClipRect(); + IMGUI_API void PushTextureID(const ImTextureID& texture_id); + IMGUI_API void PopTextureID(); + inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } + inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } + + // Primitives + IMGUI_API void AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All, float thickness = 1.0f); // a: upper-left, b: lower-right, rounding_corners_flags: 4-bits corresponding to which corner to round + IMGUI_API void AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); // a: upper-left, b: lower-right + IMGUI_API void AddRectFilledMultiColor(const ImVec2& a, const ImVec2& b, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); + IMGUI_API void AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col); + IMGUI_API void AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col); + IMGUI_API void AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12, float thickness = 1.0f); + IMGUI_API void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); + IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); + IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,0), const ImVec2& uv_c = ImVec2(1,1), const ImVec2& uv_d = ImVec2(0,1), ImU32 col = 0xFFFFFFFF); + IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners = ImDrawCornerFlags_All); + IMGUI_API void AddPolyline(const ImVec2* points, const int num_points, ImU32 col, bool closed, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, const int num_points, ImU32 col); + IMGUI_API void AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments = 0); + + // Stateful path API, add points then finish with PathFill() or PathStroke() + inline void PathClear() { _Path.resize(0); } + inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } + inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path[_Path.Size-1], &pos, 8) != 0) _Path.push_back(pos); } + inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); PathClear(); } + inline void PathStroke(ImU32 col, bool closed, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); PathClear(); } + IMGUI_API void PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments = 10); + IMGUI_API void PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle + IMGUI_API void PathBezierCurveTo(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, int num_segments = 0); + IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All); + + // Channels + // - Use to simulate layers. By switching channels to can render out-of-order (e.g. submit foreground primitives before background primitives) + // - Use to minimize draw calls (e.g. if going back-and-forth between multiple non-overlapping clipping rectangles, prefer to append into separate channels then merge at the end) + IMGUI_API void ChannelsSplit(int channels_count); + IMGUI_API void ChannelsMerge(); + IMGUI_API void ChannelsSetCurrent(int channel_index); + + // Advanced + IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible + + // Internal helpers + // NB: all primitives needs to be reserved via PrimReserve() beforehand! + IMGUI_API void Clear(); + IMGUI_API void ClearFreeMemory(); + IMGUI_API void PrimReserve(int idx_count, int vtx_count); + IMGUI_API void PrimRect(const ImVec2& a, const ImVec2& b, ImU32 col); // Axis aligned rectangle (composed of two triangles) + IMGUI_API void PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col); + IMGUI_API void PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col); + inline void PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col){ _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; } + inline void PrimWriteIdx(ImDrawIdx idx) { *_IdxWritePtr = idx; _IdxWritePtr++; } + inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } + IMGUI_API void UpdateClipRect(); + IMGUI_API void UpdateTextureID(); +}; + +// All draw data to render an ImGui frame +struct ImDrawData +{ + bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. + ImDrawList** CmdLists; + int CmdListsCount; + int TotalVtxCount; // For convenience, sum of all cmd_lists vtx_buffer.Size + int TotalIdxCount; // For convenience, sum of all cmd_lists idx_buffer.Size + + // Functions + ImDrawData() { Valid = false; CmdLists = NULL; CmdListsCount = TotalVtxCount = TotalIdxCount = 0; } + IMGUI_API void DeIndexAllBuffers(); // For backward compatibility or convenience: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! + IMGUI_API void ScaleClipRects(const ImVec2& sc); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +}; + +struct ImFontConfig +{ + void* FontData; // // TTF/OTF data + int FontDataSize; // // TTF/OTF data size + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + int FontNo; // 0 // Index of font within TTF/OTF file + float SizePixels; // // Size in pixels for rasterizer. + int OversampleH, OversampleV; // 3, 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis. + bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. + const ImWchar* GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. + bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + unsigned int RasterizerFlags; // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one. + float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. + + // [Internal] + char Name[32]; // Name (strictly to ease debugging) + ImFont* DstFont; + + IMGUI_API ImFontConfig(); +}; + +struct ImFontGlyph +{ + ImWchar Codepoint; // 0x0000..0xFFFF + float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) + float X0, Y0, X1, Y1; // Glyph corners + float U0, V0, U1, V1; // Texture coordinates +}; + +// Load and rasterize multiple TTF/OTF fonts into a same texture. +// Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering. +// We also add custom graphic data into the texture that serves for ImGui. +// 1. (Optional) Call AddFont*** functions. If you don't call any, the default font will be loaded for you. +// 2. Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. +// 3. Upload the pixels data into a texture within your graphics system. +// 4. Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture. This value will be passed back to you during rendering to identify the texture. +// IMPORTANT: If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the ImFont is build (when calling GetTextData*** or Build()). We only copy the pointer, not the data. +struct ImFontAtlas +{ + IMGUI_API ImFontAtlas(); + IMGUI_API ~ImFontAtlas(); + IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); + IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after Build(). Set font_cfg->FontDataOwnedByAtlas to false to keep ownership. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void ClearTexData(); // Clear the CPU-side texture data. Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void ClearInputData(); // Clear the input TTF data (inc sizes, glyph ranges) + IMGUI_API void ClearFonts(); // Clear the ImGui-side font data (glyphs storage, UV coordinates) + IMGUI_API void Clear(); // Clear all + + // Build atlas, retrieve pixel data. + // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // RGBA32 format is provided for convenience and compatibility, but note that unless you use CustomRect to draw color data, the RGB pixels emitted from Fonts will all be white (~75% of waste). + // Pitch = Width * BytesPerPixels + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { TexID = id; } + + //------------------------------------------- + // Glyph Ranges + //------------------------------------------- + + // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters + IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Default + Japanese + full set of about 21000 CJK Unified Ideographs + IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters + IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters + + // Helpers to build glyph ranges from text data. Feed your application strings/characters to it then call BuildRanges(). + struct GlyphRangesBuilder + { + ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) + GlyphRangesBuilder() { UsedChars.resize(0x10000 / 8); memset(UsedChars.Data, 0, 0x10000 / 8); } + bool GetBit(int n) { return (UsedChars[n >> 3] & (1 << (n & 7))) != 0; } + void SetBit(int n) { UsedChars[n >> 3] |= 1 << (n & 7); } // Set bit 'c' in the array + void AddChar(ImWchar c) { SetBit(c); } // Add character + IMGUI_API void AddText(const char* text, const char* text_end = NULL); // Add string (each character of the UTF-8 string are added) + IMGUI_API void AddRanges(const ImWchar* ranges); // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault) to force add all of ASCII/Latin+Ext + IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges + }; + + //------------------------------------------- + // Custom Rectangles/Glyphs API + //------------------------------------------- + + // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. After calling Build(), you can query the rectangle position and render your pixels. + // You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. + struct CustomRect + { + unsigned int ID; // Input // User ID. Use <0x10000 to map into a font glyph, >=0x10000 for other/internal/custom texture data. + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned short X, Y; // Output // Packed position in Atlas + float GlyphAdvanceX; // Input // For custom font glyphs only (ID<0x10000): glyph xadvance + ImVec2 GlyphOffset; // Input // For custom font glyphs only (ID<0x10000): glyph display offset + ImFont* Font; // Input // For custom font glyphs only (ID<0x10000): target font + CustomRect() { ID = 0xFFFFFFFF; Width = Height = 0; X = Y = 0xFFFF; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0,0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } + }; + + IMGUI_API int AddCustomRectRegular(unsigned int id, int width, int height); // Id needs to be >= 0x10000. Id >= 0x80000000 are reserved for ImGui and ImDrawList + IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0,0)); // Id needs to be < 0x10000 to register a rectangle to map into a specific font. + IMGUI_API void CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max); + const CustomRect* GetCustomRectByIndex(int index) const { if (index < 0) return NULL; return &CustomRects[index]; } + + //------------------------------------------- + // Members + //------------------------------------------- + + ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. + int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. + + // [Internal] + // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; // Texture width calculated during Build(). + int TexHeight; // Texture height calculated during Build(). + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. + ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. + ImVector ConfigData; // Internal data + int CustomRectIds[1]; // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList +}; + +// Font runtime data and rendering +// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). +struct ImFont +{ + // Members: Hot ~62/78 bytes + float FontSize; // // Height of characters, set during loading (don't change after loading) + float Scale; // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() + ImVec2 DisplayOffset; // = (0.f,1.f) // Offset font rendering by xx pixels + ImVector Glyphs; // // All glyphs. + ImVector IndexAdvanceX; // // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI). + ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. + const ImFontGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + float FallbackAdvanceX; // == FallbackGlyph->AdvanceX + ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() + + // Members: Cold ~18/26 bytes + short ConfigDataCount; // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. + ImFontConfig* ConfigData; // // Pointer within ContainerAtlas->ConfigData + ImFontAtlas* ContainerAtlas; // // What we has been loaded into + float Ascent, Descent; // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] + int MetricsTotalSurface;// // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + + // Methods + IMGUI_API ImFont(); + IMGUI_API ~ImFont(); + IMGUI_API void ClearOutputData(); + IMGUI_API void BuildLookupTable(); + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; + IMGUI_API void SetFallbackChar(ImWchar c); + float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + bool IsLoaded() const { return ContainerAtlas != NULL; } + const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } + + // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. + // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const; + IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + + // [Internal] + IMGUI_API void GrowIndex(int new_size); + IMGUI_API void AddGlyph(ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); + IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + typedef ImFontGlyph Glyph; // OBSOLETE 1.52+ +#endif +}; + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +// Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h) +#ifdef IMGUI_INCLUDE_IMGUI_USER_H +#include "imgui_user.h" +#endif diff --git a/skeleton/imgui/imgui_demo.cpp b/skeleton/imgui/imgui_demo.cpp new file mode 100644 index 0000000..94531a1 --- /dev/null +++ b/skeleton/imgui/imgui_demo.cpp @@ -0,0 +1,3068 @@ +// dear imgui, v1.54 WIP +// (demo code) + +// Message to the person tempted to delete this file when integrating ImGui into their code base: +// Don't do it! Do NOT remove this file from your project! It is useful reference code that you and other users will want to refer to. +// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). +// During development, you can call ImGui::ShowDemoWindow() in your code to learn about various features of ImGui. Have it wired in a debug menu! +// Removing this file from your project is hindering access to documentation for everyone in your team, likely leading you to poorer usage of the library. +// Note that you can #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h for the same effect. +// If you want to link core ImGui in your final builds but not those demo windows, #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h and those functions will be empty. +// In other situation, when you have ImGui available you probably want this to be available for reference and execution. +// Thank you, +// -Your beloved friend, imgui_demo.cpp (that you won't delete) + +// Message to beginner C/C++ programmers. About the meaning of 'static': in this demo code, we frequently we use 'static' variables inside functions. +// We do this as a way to gather code and data in the same place, just to make the demo code faster to read, faster to write, and use less code. +// A static variable persist across calls, so it is essentially like a global variable but declared inside the scope of the function. +// It also happens to be a convenient way of storing simple UI related information as long as your function doesn't need to be reentrant or used in threads. +// This might be a pattern you occasionally want to use in your code, but most of the real data you would be editing is likely to be stored outside your function. + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#include // toupper, isprint +#include // sqrtf, powf, cosf, sinf, floorf, ceilf +#include // vsnprintf, sscanf, printf +#include // NULL, malloc, free, atoi +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define snprintf _snprintf +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning : 'xx' is deprecated: The POSIX name for this item.. // for strdup used in demo code (so user can copy & paste the code) +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' +#pragma clang diagnostic ignored "-Wformat-security" // warning : warning: format string is not a string literal +#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat-security" // warning : format string is not a string literal (potentially insecure) +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#if (__GNUC__ >= 6) +#pragma GCC diagnostic ignored "-Wmisleading-indentation" // warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. +#endif +#endif + +// Play it nice with Windows users. Notepad in 2017 still doesn't display text data with Unix-style \n. +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" +#else +#define IM_NEWLINE "\n" +#endif + +#define IM_MAX(_A,_B) (((_A) >= (_B)) ? (_A) : (_B)) + +//----------------------------------------------------------------------------- +// DEMO CODE +//----------------------------------------------------------------------------- + +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && defined(IMGUI_DISABLE_TEST_WINDOWS) && !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Obsolete name since 1.53, TEST->DEMO +#define IMGUI_DISABLE_DEMO_WINDOWS +#endif + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) + +static void ShowExampleAppConsole(bool* p_open); +static void ShowExampleAppLog(bool* p_open); +static void ShowExampleAppLayout(bool* p_open); +static void ShowExampleAppPropertyEditor(bool* p_open); +static void ShowExampleAppLongText(bool* p_open); +static void ShowExampleAppAutoResize(bool* p_open); +static void ShowExampleAppConstrainedResize(bool* p_open); +static void ShowExampleAppFixedOverlay(bool* p_open); +static void ShowExampleAppWindowTitles(bool* p_open); +static void ShowExampleAppCustomRendering(bool* p_open); +static void ShowExampleAppMainMenuBar(); +static void ShowExampleMenuFile(); + +static void ShowHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(450.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +void ImGui::ShowUserGuide() +{ + ImGui::BulletText("Double-click on title bar to collapse window."); + ImGui::BulletText("Click and drag on lower right corner to resize window\n(double-click to auto fit window to its contents)."); + ImGui::BulletText("Click and drag on any empty space to move window."); + ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); + if (ImGui::GetIO().FontAllowUserScaling) + ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); + ImGui::BulletText("Mouse Wheel to scroll."); + ImGui::BulletText("While editing text:\n"); + ImGui::Indent(); + ImGui::BulletText("Hold SHIFT or use mouse to select text."); + ImGui::BulletText("CTRL+Left/Right to word jump."); + ImGui::BulletText("CTRL+A or double-click to select all."); + ImGui::BulletText("CTRL+X,CTRL+C,CTRL+V to use clipboard."); + ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); + ImGui::BulletText("ESCAPE to revert."); + ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); + ImGui::Unindent(); +} + +// Demonstrate most ImGui features (big function!) +void ImGui::ShowDemoWindow(bool* p_open) +{ + // Examples apps + static bool show_app_main_menu_bar = false; + static bool show_app_console = false; + static bool show_app_log = false; + static bool show_app_layout = false; + static bool show_app_property_editor = false; + static bool show_app_long_text = false; + static bool show_app_auto_resize = false; + static bool show_app_constrained_resize = false; + static bool show_app_fixed_overlay = false; + static bool show_app_window_titles = false; + static bool show_app_custom_rendering = false; + static bool show_app_style_editor = false; + + static bool show_app_metrics = false; + static bool show_app_about = false; + + if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); + if (show_app_console) ShowExampleAppConsole(&show_app_console); + if (show_app_log) ShowExampleAppLog(&show_app_log); + if (show_app_layout) ShowExampleAppLayout(&show_app_layout); + if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); + if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); + if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); + if (show_app_fixed_overlay) ShowExampleAppFixedOverlay(&show_app_fixed_overlay); + if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); + + if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } + if (show_app_style_editor) { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); } + if (show_app_about) + { + ImGui::Begin("About Dear ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Text("dear imgui, %s", ImGui::GetVersion()); + ImGui::Separator(); + ImGui::Text("By Omar Cornut and all dear imgui contributors."); + ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); + ImGui::End(); + } + + static bool no_titlebar = false; + static bool no_scrollbar = false; + static bool no_menu = false; + static bool no_move = false; + static bool no_resize = false; + static bool no_collapse = false; + static bool no_close = false; + + // Demonstrate the various window flags. Typically you would just use the default. + ImGuiWindowFlags window_flags = 0; + if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; + if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; + if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; + if (no_move) window_flags |= ImGuiWindowFlags_NoMove; + if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; + if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; + if (no_close) p_open = NULL; // Don't pass our bool* to Begin + + ImGui::SetNextWindowSize(ImVec2(550,680), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("ImGui Demo", p_open, window_flags)) + { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + //ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f); // 2/3 of the space for widget and 1/3 for labels + ImGui::PushItemWidth(-140); // Right align, keep 140 pixels for labels + + ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION); + + // Menu + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); + ImGui::MenuItem("Console", NULL, &show_app_console); + ImGui::MenuItem("Log", NULL, &show_app_log); + ImGui::MenuItem("Simple layout", NULL, &show_app_layout); + ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); + ImGui::MenuItem("Long text display", NULL, &show_app_long_text); + ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); + ImGui::MenuItem("Simple overlay", NULL, &show_app_fixed_overlay); + ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); + ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Help")) + { + ImGui::MenuItem("Metrics", NULL, &show_app_metrics); + ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); + ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + ImGui::Spacing(); + if (ImGui::CollapsingHeader("Help")) + { + ImGui::TextWrapped("This window is being created by the ShowDemoWindow() function. Please refer to the code in imgui_demo.cpp for reference.\n\n"); + ImGui::Text("USER GUIDE:"); + ImGui::ShowUserGuide(); + } + + if (ImGui::CollapsingHeader("Window options")) + { + ImGui::Checkbox("No titlebar", &no_titlebar); ImGui::SameLine(150); + ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300); + ImGui::Checkbox("No menu", &no_menu); + ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150); + ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); + ImGui::Checkbox("No collapse", &no_collapse); + ImGui::Checkbox("No close", &no_close); + + if (ImGui::TreeNode("Style")) + { + ImGui::ShowStyleEditor(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Capture/Logging")) + { + ImGui::TextWrapped("The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded. You can also call ImGui::LogText() to output directly to the log without a visual output."); + ImGui::LogButtons(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Widgets")) + { + if (ImGui::TreeNode("Basic")) + { + static int clicked = 0; + if (ImGui::Button("Button")) + clicked++; + if (clicked & 1) + { + ImGui::SameLine(); + ImGui::Text("Thanks for clicking me!"); + } + + static bool check = true; + ImGui::Checkbox("checkbox", &check); + + static int e = 0; + ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); + ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); + ImGui::RadioButton("radio c", &e, 2); + + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, 0.8f, 0.8f)); + ImGui::Button("Click"); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + + ImGui::Text("Hover over me"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip"); + + ImGui::SameLine(); + ImGui::Text("- or me"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("I am a fancy tooltip"); + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + ImGui::EndTooltip(); + } + + // Testing ImGuiOnceUponAFrame helper. + //static ImGuiOnceUponAFrame once; + //for (int i = 0; i < 5; i++) + // if (once) + // ImGui::Text("This will be displayed only once."); + + ImGui::Separator(); + + ImGui::LabelText("label", "Value"); + + { + // Simplified one-liner Combo() API, using values packed in a single constant string + static int current_item_1 = 1; + ImGui::Combo("combo", ¤t_item_1, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + //ImGui::Combo("combo w/ array of char*", ¤t_item_2_idx, items, IM_ARRAYSIZE(items)); // Combo using proper array. You can also pass a callback to retrieve array value, no need to create/copy an array just for that. + + // General BeginCombo() API, you have full control over your selection data and display type + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO", "PPPP", "QQQQQQQQQQ", "RRR", "SSSS" }; + static const char* current_item_2 = NULL; + if (ImGui::BeginCombo("combo 2", current_item_2)) // The second parameter is the label previewed before opening the combo. + { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) + { + bool is_selected = (current_item_2 == items[n]); // You can store your selection however you want, outside or inside your objects + if (ImGui::Selectable(items[n], is_selected)) + current_item_2 = items[n]; + if (is_selected) + ImGui::SetItemDefaultFocus(); // Set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch) + } + ImGui::EndCombo(); + } + } + + { + static char str0[128] = "Hello, world!"; + static int i0=123; + static float f0=0.001f; + ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::SameLine(); ShowHelpMarker("Hold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" "CTRL+A or double-click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n"); + + ImGui::InputInt("input int", &i0); + ImGui::SameLine(); ShowHelpMarker("You can apply arithmetic operators +,*,/ on numerical values.\n e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\nUse +- to subtract.\n"); + + ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); + + static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + ImGui::InputFloat3("input float3", vec4a); + } + + { + static int i1=50, i2=42; + ImGui::DragInt("drag int", &i1, 1); + ImGui::SameLine(); ShowHelpMarker("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input value."); + + ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%.0f%%"); + + static float f1=1.00f, f2=0.0067f; + ImGui::DragFloat("drag float", &f1, 0.005f); + ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + } + + { + static int i1=0; + ImGui::SliderInt("slider int", &i1, -1, 3); + ImGui::SameLine(); ShowHelpMarker("CTRL+click to input value."); + + static float f1=0.123f, f2=0.0f; + ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); + ImGui::SliderFloat("slider log float", &f2, -10.0f, 10.0f, "%.4f", 3.0f); + static float angle = 0.0f; + ImGui::SliderAngle("slider angle", &angle); + } + + static float col1[3] = { 1.0f,0.0f,0.2f }; + static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit3("color 1", col1); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nRight-click on the colored square to show options.\nCTRL+click on individual component to input value.\n"); + + ImGui::ColorEdit4("color 2", col2); + + const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; + static int listbox_item_current = 1; + ImGui::ListBox("listbox\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + + //static int listbox_item_current2 = 2; + //ImGui::PushItemWidth(-1); + //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); + //ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Trees")) + { + if (ImGui::TreeNode("Basic trees")) + { + for (int i = 0; i < 5; i++) + if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) + { + ImGui::Text("blah blah"); + ImGui::SameLine(); + if (ImGui::SmallButton("print")) printf("Child %d pressed", i); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced, with Selectable nodes")) + { + ShowHelpMarker("This is a more standard looking tree with selectable nodes.\nClick to select, CTRL+Click to toggle, click on arrows or double-click to open."); + static bool align_label_with_current_x_position = false; + ImGui::Checkbox("Align label with current X position)", &align_label_with_current_x_position); + ImGui::Text("Hello!"); + if (align_label_with_current_x_position) + ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + + static int selection_mask = (1 << 2); // Dumb representation of what may be user-side selection state. You may carry selection state inside or outside your objects in whatever format you see fit. + int node_clicked = -1; // Temporary storage of what node we have clicked to process selection at the end of the loop. May be a pointer to your own node type, etc. + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, ImGui::GetFontSize()*3); // Increase spacing to differentiate leaves from expanded contents. + for (int i = 0; i < 6; i++) + { + // Disable the default open on single-click behavior and pass in Selected flag according to our selection state. + ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ((selection_mask & (1 << i)) ? ImGuiTreeNodeFlags_Selected : 0); + if (i < 3) + { + // Node + bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + if (node_open) + { + ImGui::Text("Blah blah\nBlah Blah"); + ImGui::TreePop(); + } + } + else + { + // Leaf: The only reason we have a TreeNode at all is to allow selection of the leaf. Otherwise we can use BulletText() or TreeAdvanceToLabelPos()+Text(). + node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet + ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); + if (ImGui::IsItemClicked()) + node_clicked = i; + } + } + if (node_clicked != -1) + { + // Update selection state. Process outside of tree loop to avoid visual inconsistencies during the clicking-frame. + if (ImGui::GetIO().KeyCtrl) + selection_mask ^= (1 << node_clicked); // CTRL+click to toggle + else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, this commented bit preserve selection when clicking on item that is part of the selection + selection_mask = (1 << node_clicked); // Click to single-select + } + ImGui::PopStyleVar(); + if (align_label_with_current_x_position) + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Collapsing Headers")) + { + static bool closable_group = true; + ImGui::Checkbox("Enable extra group", &closable_group); + if (ImGui::CollapsingHeader("Header")) + { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("Some content %d", i); + } + if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) + { + ImGui::Text("IsItemHovered: %d", IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("More content %d", i); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Bullets")) + { + ImGui::BulletText("Bullet point 1"); + ImGui::BulletText("Bullet point 2\nOn multiple lines"); + ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); + ImGui::Bullet(); ImGui::SmallButton("Button"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text")) + { + if (ImGui::TreeNode("Colored Text")) + { + // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. + ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink"); + ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow"); + ImGui::TextDisabled("Disabled"); + ImGui::SameLine(); ShowHelpMarker("The TextDisabled color is stored in ImGuiStyle."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Word Wrapping")) + { + // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. + ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules suitable for English and possibly other languages."); + ImGui::Spacing(); + + static float wrap_width = 200.0f; + ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); + + ImGui::Text("Test paragraph 1:"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("The lazy dog is a good dog. This paragraph is made to fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::Text("Test paragraph 2:"); + pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); + ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); + ImGui::PopTextWrapPos(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("UTF-8 Text")) + { + // UTF-8 test with Japanese characters + // (needs a suitable font, try Arial Unicode or M+ fonts http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html) + // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 + // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. Visual Studio save your file as 'UTF-8 without signature') + // - HOWEVER, FOR THIS DEMO FILE, BECAUSE WE WANT TO SUPPORT COMPILER, WE ARE *NOT* INCLUDING RAW UTF-8 CHARACTERS IN THIS SOURCE FILE. + // Instead we are encoding a few string with hexadecimal constants. Don't do this in your application! + // Note that characters values are preserved even by InputText() if the font cannot be displayed, so you can safely copy & paste garbled characters into another application. + ImGui::TextWrapped("CJK text will only appears if the font was loaded with the appropriate CJK character ranges. Call io.Font->LoadFromFileTTF() manually to load extra character ranges."); + ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); + ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); + static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; // "nihongo" + ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Images")) + { + ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); + ImGuiIO& io = ImGui::GetIO(); + + // Here we are grabbing the font texture because that's the only one we have access to inside the demo code. + // Remember that ImTextureID is just storage for whatever you want it to be, it is essentially a value that will be passed to the render function inside the ImDrawCmd structure. + // If you use one of the default imgui_impl_XXXX.cpp renderer, they all have comments at the top of their file to specify what they expect to be stored in ImTextureID. + // (for example, the imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. The imgui_impl_glfw_gl3.cpp renderer expect a GLuint OpenGL texture identifier etc.) + // If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers to ImGui::Image(), and gather width/height through your own functions, etc. + // Using ShowMetricsWindow() as a "debugger" to inspect the draw data that are being passed to your render will help you debug issues if you are confused about this. + // Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). + ImTextureID my_tex_id = io.Fonts->TexID; + float my_tex_w = (float)io.Fonts->TexWidth; + float my_tex_h = (float)io.Fonts->TexHeight; + + ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + float focus_sz = 32.0f; + float focus_x = io.MousePos.x - pos.x - focus_sz * 0.5f; if (focus_x < 0.0f) focus_x = 0.0f; else if (focus_x > my_tex_w - focus_sz) focus_x = my_tex_w - focus_sz; + float focus_y = io.MousePos.y - pos.y - focus_sz * 0.5f; if (focus_y < 0.0f) focus_y = 0.0f; else if (focus_y > my_tex_h - focus_sz) focus_y = my_tex_h - focus_sz; + ImGui::Text("Min: (%.2f, %.2f)", focus_x, focus_y); + ImGui::Text("Max: (%.2f, %.2f)", focus_x + focus_sz, focus_y + focus_sz); + ImVec2 uv0 = ImVec2((focus_x) / my_tex_w, (focus_y) / my_tex_h); + ImVec2 uv1 = ImVec2((focus_x + focus_sz) / my_tex_w, (focus_y + focus_sz) / my_tex_h); + ImGui::Image(my_tex_id, ImVec2(128,128), uv0, uv1, ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::EndTooltip(); + } + ImGui::TextWrapped("And now some textured buttons.."); + static int pressed_count = 0; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); + int frame_padding = -1 + i; // -1 = uses default padding + if (ImGui::ImageButton(my_tex_id, ImVec2(32,32), ImVec2(0,0), ImVec2(32.0f/my_tex_w,32/my_tex_h), frame_padding, ImColor(0,0,0,255))) + pressed_count += 1; + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Text("Pressed %d times.", pressed_count); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Selectables")) + { + if (ImGui::TreeNode("Basic")) + { + static bool selected[4] = { false, true, false, false }; + ImGui::Selectable("1. I am selectable", &selected[0]); + ImGui::Selectable("2. I am selectable", &selected[1]); + ImGui::Text("3. I am not selectable"); + ImGui::Selectable("4. I am selectable", &selected[2]); + if (ImGui::Selectable("5. I am double clickable", selected[3], ImGuiSelectableFlags_AllowDoubleClick)) + if (ImGui::IsMouseDoubleClicked(0)) + selected[3] = !selected[3]; + ImGui::TreePop(); + } + if (ImGui::TreeNode("Rendering more text into the same block")) + { + static bool selected[3] = { false, false, false }; + ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes"); + ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("In columns")) + { + ImGui::Columns(3, NULL, false); + static bool selected[16] = { 0 }; + for (int i = 0; i < 16; i++) + { + char label[32]; sprintf(label, "Item %d", i); + if (ImGui::Selectable(label, &selected[i])) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Grid")) + { + static bool selected[16] = { true, false, false, false, false, true, false, false, false, false, true, false, false, false, false, true }; + for (int i = 0; i < 16; i++) + { + ImGui::PushID(i); + if (ImGui::Selectable("Sailor", &selected[i], 0, ImVec2(50,50))) + { + int x = i % 4, y = i / 4; + if (x > 0) selected[i - 1] ^= 1; + if (x < 3) selected[i + 1] ^= 1; + if (y > 0) selected[i - 4] ^= 1; + if (y < 3) selected[i + 4] ^= 1; + } + if ((i % 4) < 3) ImGui::SameLine(); + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Filtered Text Input")) + { + static char buf1[64] = ""; ImGui::InputText("default", buf1, 64); + static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal); + static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase); + static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank); + struct TextFilters { static int FilterImGuiLetters(ImGuiTextEditCallbackData* data) { if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) return 0; return 1; } }; + static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); + + ImGui::Text("Password input"); + static char bufpass[64] = "password123"; + ImGui::InputText("password", bufpass, 64, ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsNoBlank); + ImGui::SameLine(); ShowHelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); + ImGui::InputText("password (clear)", bufpass, 64, ImGuiInputTextFlags_CharsNoBlank); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-line Text Input")) + { + static bool read_only = false; + static char text[1024*16] = + "/*\n" + " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" + " the hexadecimal encoding of one offending instruction,\n" + " more formally, the invalid operand with locked CMPXCHG8B\n" + " instruction bug, is a design flaw in the majority of\n" + " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" + " processors (all in the P5 microarchitecture).\n" + "*/\n\n" + "label:\n" + "\tlock cmpxchg8b eax\n"; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + ImGui::Checkbox("Read-only", &read_only); + ImGui::PopStyleVar(); + ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 16), ImGuiInputTextFlags_AllowTabInput | (read_only ? ImGuiInputTextFlags_ReadOnly : 0)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Plots widgets")) + { + static bool animate = true; + ImGui::Checkbox("Animate", &animate); + + static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); + + // Create a dummy array of contiguous float values to plot + // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float and the sizeof() of your structure in the Stride parameter. + static float values[90] = { 0 }; + static int values_offset = 0; + static float refresh_time = 0.0f; + if (!animate || refresh_time == 0.0f) + refresh_time = ImGui::GetTime(); + while (refresh_time < ImGui::GetTime()) // Create dummy data at fixed 60 hz rate for the demo + { + static float phase = 0.0f; + values[values_offset] = cosf(phase); + values_offset = (values_offset+1) % IM_ARRAYSIZE(values); + phase += 0.10f*values_offset; + refresh_time += 1.0f/60.0f; + } + ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,80)); + + // Use functions to generate output + // FIXME: This is rather awkward because current plot API only pass in indices. We probably want an API passing floats and user provide sample rate/count. + struct Funcs + { + static float Sin(void*, int i) { return sinf(i * 0.1f); } + static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } + }; + static int func_type = 0, display_count = 70; + ImGui::Separator(); + ImGui::PushItemWidth(100); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); + ImGui::SliderInt("Sample count", &display_count, 1, 400); + float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; + ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); + ImGui::Separator(); + + // Animate a simple progress bar + static float progress = 0.0f, progress_dir = 1.0f; + if (animate) + { + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + } + + // Typically we would use ImVec2(-1.0f,0.0f) to use all available width, or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. + ImGui::ProgressBar(progress, ImVec2(0.0f,0.0f)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Progress Bar"); + + float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress; + char buf[32]; + sprintf(buf, "%d/%d", (int)(progress_saturated*1753), 1753); + ImGui::ProgressBar(progress, ImVec2(0.f,0.f), buf); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Color/Picker Widgets")) + { + static ImVec4 color = ImColor(114, 144, 154, 200); + + static bool hdr = false; + static bool alpha_preview = true; + static bool alpha_half_preview = false; + static bool options_menu = true; + ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); ShowHelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); + ImGui::Checkbox("With Alpha Preview", &alpha_preview); + ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); + ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); ShowHelpMarker("Right-click on the individual color widget to show options."); + int misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); + + ImGui::Text("Color widget:"); + ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nCTRL+click on individual component to input value.\n"); + ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags); + + ImGui::Text("Color widget HSV with Alpha:"); + ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_HSV | misc_flags); + + ImGui::Text("Color widget with Float Display:"); + ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags); + + ImGui::Text("Color button with Picker:"); + ImGui::SameLine(); ShowHelpMarker("With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\nWith the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only be used for the tooltip and picker popup."); + ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags); + + ImGui::Text("Color button with Custom Picker Popup:"); + + // Generate a dummy palette + static bool saved_palette_inited = false; + static ImVec4 saved_palette[32]; + if (!saved_palette_inited) + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); + saved_palette[n].w = 1.0f; // Alpha + } + saved_palette_inited = true; + + static ImVec4 backup_color; + bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); + ImGui::SameLine(); + open_popup |= ImGui::Button("Palette"); + if (open_popup) + { + ImGui::OpenPopup("mypicker"); + backup_color = color; + } + if (ImGui::BeginPopup("mypicker")) + { + // FIXME: Adding a drag and drop example here would be perfect! + ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); + ImGui::Separator(); + ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("Current"); + ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40)); + ImGui::Text("Previous"); + if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40))) + color = backup_color; + ImGui::Separator(); + ImGui::Text("Palette"); + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) + { + ImGui::PushID(n); + if ((n % 8) != 0) + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + if (ImGui::ColorButton("##palette", saved_palette[n], ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip, ImVec2(20,20))) + color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3); + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); + EndDragDropTarget(); + } + + ImGui::PopID(); + } + ImGui::EndGroup(); + ImGui::EndPopup(); + } + + ImGui::Text("Color button only:"); + ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags, ImVec2(80,80)); + + ImGui::Text("Color picker:"); + static bool alpha = true; + static bool alpha_bar = true; + static bool side_preview = true; + static bool ref_color = false; + static ImVec4 ref_color_v(1.0f,0.0f,1.0f,0.5f); + static int inputs_mode = 2; + static int picker_mode = 0; + ImGui::Checkbox("With Alpha", &alpha); + ImGui::Checkbox("With Alpha Bar", &alpha_bar); + ImGui::Checkbox("With Side Preview", &side_preview); + if (side_preview) + { + ImGui::SameLine(); + ImGui::Checkbox("With Ref Color", &ref_color); + if (ref_color) + { + ImGui::SameLine(); + ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); + } + } + ImGui::Combo("Inputs Mode", &inputs_mode, "All Inputs\0No Inputs\0RGB Input\0HSV Input\0HEX Input\0"); + ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0"); + ImGui::SameLine(); ShowHelpMarker("User can right-click the picker to change mode."); + ImGuiColorEditFlags flags = misc_flags; + if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() + if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar; + if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview; + if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; + if (inputs_mode == 1) flags |= ImGuiColorEditFlags_NoInputs; + if (inputs_mode == 2) flags |= ImGuiColorEditFlags_RGB; + if (inputs_mode == 3) flags |= ImGuiColorEditFlags_HSV; + if (inputs_mode == 4) flags |= ImGuiColorEditFlags_HEX; + ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); + + ImGui::Text("Programmatically set defaults/options:"); + ImGui::SameLine(); ShowHelpMarker("SetColorEditOptions() is designed to allow you to set boot-time default.\nWe don't have Push/Pop functions because you can force options on a per-widget basis if needed, and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid encouraging you to persistently save values that aren't forward-compatible."); + if (ImGui::Button("Uint8 + HSV")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_HSV); + ImGui::SameLine(); + if (ImGui::Button("Float + HDR")) + ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_RGB); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Range Widgets")) + { + static float begin = 10, end = 90; + static int begin_i = 100, end_i = 1000; + ImGui::DragFloatRange2("range", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%"); + ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %.0f units", "Max: %.0f units"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Multi-component Widgets")) + { + static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; + static int vec4i[4] = { 1, 5, 100, 255 }; + + ImGui::InputFloat2("input float2", vec4f); + ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); + ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); + ImGui::InputInt2("input int2", vec4i); + ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat3("input float3", vec4f); + ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); + ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); + ImGui::InputInt3("input int3", vec4i); + ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::Spacing(); + + ImGui::InputFloat4("input float4", vec4f); + ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); + ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); + ImGui::InputInt4("input int4", vec4i); + ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); + ImGui::SliderInt4("slider int4", vec4i, 0, 255); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Vertical Sliders")) + { + const float spacing = 4; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + + static int int_value = 0; + ImGui::VSliderInt("##int", ImVec2(18,160), &int_value, 0, 5); + ImGui::SameLine(); + + static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; + ImGui::PushID("set1"); + for (int i = 0; i < 7; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i/7.0f, 0.5f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i/7.0f, 0.9f, 0.9f)); + ImGui::VSliderFloat("##v", ImVec2(18,160), &values[i], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values[i]); + ImGui::PopStyleColor(4); + ImGui::PopID(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set2"); + static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; + const int rows = 3; + const ImVec2 small_slider_size(18, (160.0f-(rows-1)*spacing)/rows); + for (int nx = 0; nx < 4; nx++) + { + if (nx > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + for (int ny = 0; ny < rows; ny++) + { + ImGui::PushID(nx*rows+ny); + ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values2[nx]); + ImGui::PopID(); + } + ImGui::EndGroup(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set3"); + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); + ImGui::VSliderFloat("##v", ImVec2(40,160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); + ImGui::PopStyleVar(); + ImGui::PopID(); + } + ImGui::PopID(); + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Layout")) + { + if (ImGui::TreeNode("Child regions")) + { + static bool disable_mouse_wheel = false; + ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); + + ImGui::Text("Without border"); + static int line = 50; + bool goto_line = ImGui::Button("Goto"); + ImGui::SameLine(); + ImGui::PushItemWidth(100); + goto_line |= ImGui::InputInt("##Line", &line, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::PopItemWidth(); + + ImGui::BeginChild("Sub1", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f,300), false, ImGuiWindowFlags_HorizontalScrollbar | (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0)); + for (int i = 0; i < 100; i++) + { + ImGui::Text("%04d: scrollable region", i); + if (goto_line && line == i) + ImGui::SetScrollHere(); + } + if (goto_line && line >= 100) + ImGui::SetScrollHere(); + ImGui::EndChild(); + + ImGui::SameLine(); + + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild("Sub2", ImVec2(0,300), true, (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0)); + ImGui::Text("With border"); + ImGui::Columns(2); + for (int i = 0; i < 100; i++) + { + if (i == 50) + ImGui::NextColumn(); + char buf[32]; + sprintf(buf, "%08x", i*5731); + ImGui::Button(buf, ImVec2(-1.0f, 0.0f)); + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Widgets Width")) + { + static float f = 0.0f; + ImGui::Text("PushItemWidth(100)"); + ImGui::SameLine(); ShowHelpMarker("Fixed width."); + ImGui::PushItemWidth(100); + ImGui::DragFloat("float##1", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetWindowWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of window width."); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + ImGui::DragFloat("float##2", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(GetContentRegionAvailWidth() * 0.5f)"); + ImGui::SameLine(); ShowHelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.5f); + ImGui::DragFloat("float##3", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-100)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge minus 100"); + ImGui::PushItemWidth(-100); + ImGui::DragFloat("float##4", &f); + ImGui::PopItemWidth(); + + ImGui::Text("PushItemWidth(-1)"); + ImGui::SameLine(); ShowHelpMarker("Align to right edge"); + ImGui::PushItemWidth(-1); + ImGui::DragFloat("float##5", &f); + ImGui::PopItemWidth(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Basic Horizontal Layout")) + { + ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); + + // Text + ImGui::Text("Two items: Hello"); ImGui::SameLine(); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Adjust spacing + ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); + ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); + + // Button + ImGui::AlignTextToFramePadding(); + ImGui::Text("Normal buttons"); ImGui::SameLine(); + ImGui::Button("Banana"); ImGui::SameLine(); + ImGui::Button("Apple"); ImGui::SameLine(); + ImGui::Button("Corniflower"); + + // Button + ImGui::Text("Small buttons"); ImGui::SameLine(); + ImGui::SmallButton("Like this one"); ImGui::SameLine(); + ImGui::Text("can fit within a text block."); + + // Aligned to arbitrary position. Easy/cheap column. + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::Text("x=150"); + ImGui::SameLine(300); ImGui::Text("x=300"); + ImGui::Text("Aligned"); + ImGui::SameLine(150); ImGui::SmallButton("x=150"); + ImGui::SameLine(300); ImGui::SmallButton("x=300"); + + // Checkbox + static bool c1=false,c2=false,c3=false,c4=false; + ImGui::Checkbox("My", &c1); ImGui::SameLine(); + ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); + ImGui::Checkbox("Is", &c3); ImGui::SameLine(); + ImGui::Checkbox("Rich", &c4); + + // Various + static float f0=1.0f, f1=2.0f, f2=3.0f; + ImGui::PushItemWidth(80); + const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; + static int item = -1; + ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); + ImGui::SliderFloat("X", &f0, 0.0f,5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Y", &f1, 0.0f,5.0f); ImGui::SameLine(); + ImGui::SliderFloat("Z", &f2, 0.0f,5.0f); + ImGui::PopItemWidth(); + + ImGui::PushItemWidth(80); + ImGui::Text("Lists:"); + static int selection[4] = { 0, 1, 2, 3 }; + for (int i = 0; i < 4; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::PushID(i); + ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); + ImGui::PopID(); + //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i); + } + ImGui::PopItemWidth(); + + // Dummy + ImVec2 sz(30,30); + ImGui::Button("A", sz); ImGui::SameLine(); + ImGui::Dummy(sz); ImGui::SameLine(); + ImGui::Button("B", sz); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Groups")) + { + ImGui::TextWrapped("(Using ImGui::BeginGroup()/EndGroup() to layout items. BeginGroup() basically locks the horizontal position. EndGroup() bundles the whole group so that you can use functions such as IsItemHovered() on it.)"); + ImGui::BeginGroup(); + { + ImGui::BeginGroup(); + ImGui::Button("AAA"); + ImGui::SameLine(); + ImGui::Button("BBB"); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Button("CCC"); + ImGui::Button("DDD"); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::Button("EEE"); + ImGui::EndGroup(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("First group hovered"); + } + // Capture the group size and create widgets using the same size + ImVec2 size = ImGui::GetItemRectSize(); + const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; + ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); + + ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); + ImGui::SameLine(); + ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); + ImGui::EndGroup(); + ImGui::SameLine(); + + ImGui::Button("LEVERAGE\nBUZZWORD", size); + ImGui::SameLine(); + + ImGui::ListBoxHeader("List", size); + ImGui::Selectable("Selected", true); + ImGui::Selectable("Not Selected", false); + ImGui::ListBoxFooter(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Text Baseline Alignment")) + { + ImGui::TextWrapped("(This is testing the vertical alignment that occurs on text to keep it at the same baseline as widgets. Lines only composed of text or \"small\" widgets fit in less vertical spaces than lines with normal widgets)"); + + ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("One\nTwo\nThree"); + + ImGui::Button("HOP##1"); ImGui::SameLine(); + ImGui::Text("Banana"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("HOP##2"); ImGui::SameLine(); + ImGui::Text("Hello\nWorld"); ImGui::SameLine(); + ImGui::Text("Banana"); + + ImGui::Button("TEST##1"); ImGui::SameLine(); + ImGui::Text("TEST"); ImGui::SameLine(); + ImGui::SmallButton("TEST##2"); + + ImGui::AlignTextToFramePadding(); // If your line starts with text, call this to align it to upcoming widgets. + ImGui::Text("Text aligned to Widget"); ImGui::SameLine(); + ImGui::Button("Widget##1"); ImGui::SameLine(); + ImGui::Text("Widget"); ImGui::SameLine(); + ImGui::SmallButton("Widget##2"); ImGui::SameLine(); + ImGui::Button("Widget##3"); + + // Tree + const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::Button("Button##1"); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNode("Node##1")) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + ImGui::AlignTextToFramePadding(); // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit). + bool node_open = ImGui::TreeNode("Node##2"); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add child content. + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); + if (node_open) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data + + // Bullet + ImGui::Button("Button##3"); + ImGui::SameLine(0.0f, spacing); + ImGui::BulletText("Bullet text"); + + ImGui::AlignTextToFramePadding(); + ImGui::BulletText("Node"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Scrolling")) + { + ImGui::TextWrapped("(Use SetScrollHere() or SetScrollFromPosY() to scroll to a given position.)"); + static bool track = true; + static int track_line = 50, scroll_to_px = 200; + ImGui::Checkbox("Track", &track); + ImGui::PushItemWidth(100); + ImGui::SameLine(130); track |= ImGui::DragInt("##line", &track_line, 0.25f, 0, 99, "Line = %.0f"); + bool scroll_to = ImGui::Button("Scroll To Pos"); + ImGui::SameLine(130); scroll_to |= ImGui::DragInt("##pos_y", &scroll_to_px, 1.00f, 0, 9999, "Y = %.0f px"); + ImGui::PopItemWidth(); + if (scroll_to) track = false; + + for (int i = 0; i < 5; i++) + { + if (i > 0) ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Text("%s", i == 0 ? "Top" : i == 1 ? "25%" : i == 2 ? "Center" : i == 3 ? "75%" : "Bottom"); + ImGui::BeginChild(ImGui::GetID((void*)(intptr_t)i), ImVec2(ImGui::GetWindowWidth() * 0.17f, 200.0f), true); + if (scroll_to) + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_px, i * 0.25f); + for (int line = 0; line < 100; line++) + { + if (track && line == track_line) + { + ImGui::TextColored(ImColor(255,255,0), "Line %d", line); + ImGui::SetScrollHere(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom + } + else + { + ImGui::Text("Line %d", line); + } + } + float scroll_y = ImGui::GetScrollY(), scroll_max_y = ImGui::GetScrollMaxY(); + ImGui::EndChild(); + ImGui::Text("%.0f/%0.f", scroll_y, scroll_max_y); + ImGui::EndGroup(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::Bullet(); ImGui::TextWrapped("Horizontal scrolling for a window has to be enabled explicitly via the ImGuiWindowFlags_HorizontalScrollbar flag."); + ImGui::Bullet(); ImGui::TextWrapped("You may want to explicitly specify content width by calling SetNextWindowContentWidth() before Begin()."); + static int lines = 7; + ImGui::SliderInt("Lines", &lines, 1, 15); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); + ImGui::BeginChild("scrolling", ImVec2(0, ImGui::GetFrameHeightWithSpacing()*7 + 30), true, ImGuiWindowFlags_HorizontalScrollbar); + for (int line = 0; line < lines; line++) + { + // Display random stuff (for the sake of this trivial demo we are using basic Button+SameLine. If you want to create your own time line for a real application you may be better off + // manipulating the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets yourself. You may also want to use the lower-level ImDrawList API) + int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + for (int n = 0; n < num_buttons; n++) + { + if (n > 0) ImGui::SameLine(); + ImGui::PushID(n + line * 1000); + char num_buf[16]; + sprintf(num_buf, "%d", n); + const char* label = (!(n%15)) ? "FizzBuzz" : (!(n%3)) ? "Fizz" : (!(n%5)) ? "Buzz" : num_buf; + float hue = n*0.05f; + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); + ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + } + float scroll_x = ImGui::GetScrollX(), scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::PopStyleVar(2); + float scroll_x_delta = 0.0f; + ImGui::SmallButton("<<"); if (ImGui::IsItemActive()) scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("Scroll from code"); ImGui::SameLine(); + ImGui::SmallButton(">>"); if (ImGui::IsItemActive()) scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); + ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); + if (scroll_x_delta != 0.0f) + { + ImGui::BeginChild("scrolling"); // Demonstrate a trick: you can use Begin to set yourself in the context of another window (here we are already out of your child window) + ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); + ImGui::End(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Clipping")) + { + static ImVec2 size(100, 100), offset(50, 20); + ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text CPU-side if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); + ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f"); + ImGui::TextWrapped("(Click and drag)"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec4 clip_rect(pos.x, pos.y, pos.x+size.x, pos.y+size.y); + ImGui::InvisibleButton("##dummy", size); + if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { offset.x += ImGui::GetIO().MouseDelta.x; offset.y += ImGui::GetIO().MouseDelta.y; } + ImGui::GetWindowDrawList()->AddRectFilled(pos, ImVec2(pos.x+size.x,pos.y+size.y), ImColor(90,90,120,255)); + ImGui::GetWindowDrawList()->AddText(ImGui::GetFont(), ImGui::GetFontSize()*2.0f, ImVec2(pos.x+offset.x,pos.y+offset.y), ImColor(255,255,255,255), "Line 1 hello\nLine 2 clip me!", NULL, 0.0f, &clip_rect); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Popups & Modal windows")) + { + if (ImGui::TreeNode("Popups")) + { + ImGui::TextWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it."); + + static int selected_fish = -1; + const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; + static bool toggles[] = { true, false, false, false, false }; + + // Simple selection popup + // (If you want to show the current selection inside the Button itself, you may want to build a string using the "###" operator to preserve a constant ID with a variable label) + if (ImGui::Button("Select..")) + ImGui::OpenPopup("select"); + ImGui::SameLine(); + ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); + if (ImGui::BeginPopup("select")) + { + ImGui::Text("Aquarium"); + ImGui::Separator(); + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + if (ImGui::Selectable(names[i])) + selected_fish = i; + ImGui::EndPopup(); + } + + // Showing a menu with toggles + if (ImGui::Button("Toggle..")) + ImGui::OpenPopup("toggle"); + if (ImGui::BeginPopup("toggle")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + + ImGui::Separator(); + ImGui::Text("Tooltip here"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip over a popup"); + + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + ImGui::EndPopup(); + } + + if (ImGui::Button("Popup Menu..")) + ImGui::OpenPopup("FilePopup"); + if (ImGui::BeginPopup("FilePopup")) + { + ShowExampleMenuFile(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Context menus")) + { + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: + // if (IsItemHovered() && IsMouseClicked(0)) + // OpenPopup(id); + // return BeginPopup(id); + // For more advanced uses you may want to replicate and cuztomize this code. This the comments inside BeginPopupContextItem() implementation. + static float value = 0.5f; + ImGui::Text("Value = %.3f (<-- right-click here)", value); + if (ImGui::BeginPopupContextItem("item context menu")) + { + if (ImGui::Selectable("Set to zero")) value = 0.0f; + if (ImGui::Selectable("Set to PI")) value = 3.1415f; + ImGui::PushItemWidth(-1); + ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); + ImGui::PopItemWidth(); + ImGui::EndPopup(); + } + + static char name[32] = "Label1"; + char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label + ImGui::Button(buf); + if (ImGui::BeginPopupContextItem()) // When used after an item that has an ID (here the Button), we can skip providing an ID to BeginPopupContextItem(). + { + ImGui::Text("Edit name:"); + ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Modals")) + { + ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside the window."); + + if (ImGui::Button("Delete..")) + ImGui::OpenPopup("Delete?"); + if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ImGui::Separator(); + + //static int dummy_i = 0; + //ImGui::Combo("Combo", &dummy_i, "Delete\0Delete harder\0"); + + static bool dont_ask_me_next_time = false; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); + ImGui::PopStyleVar(); + + if (ImGui::Button("OK", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } + ImGui::EndPopup(); + } + + if (ImGui::Button("Stacked modals..")) + ImGui::OpenPopup("Stacked 1"); + if (ImGui::BeginPopupModal("Stacked 1")) + { + ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDarkening] for darkening."); + static int item = 1; + ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + static float color[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit4("color", color); // This is to test behavior of stacked regular popups over a modal + + if (ImGui::Button("Add another modal..")) + ImGui::OpenPopup("Stacked 2"); + if (ImGui::BeginPopupModal("Stacked 2")) + { + ImGui::Text("Hello from Stacked The Second"); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Menus inside a regular window")) + { + ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); + ImGui::Separator(); + // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above. + // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here + // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus. + ImGui::PushID("foo"); + ImGui::MenuItem("Menu item", "CTRL+M"); + if (ImGui::BeginMenu("Menu inside a regular window")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::PopID(); + ImGui::Separator(); + ImGui::TreePop(); + } + } + + if (ImGui::CollapsingHeader("Columns")) + { + ImGui::PushID("Columns"); + + // Basic columns + if (ImGui::TreeNode("Basic")) + { + ImGui::Text("Without border:"); + ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border + ImGui::Separator(); + for (int n = 0; n < 14; n++) + { + char label[32]; + sprintf(label, "Item %d", n); + if (ImGui::Selectable(label)) {} + //if (ImGui::Button(label, ImVec2(-1,0))) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + + ImGui::Text("With border:"); + ImGui::Columns(4, "mycolumns"); // 4-ways, with border + ImGui::Separator(); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Text("Hovered"); ImGui::NextColumn(); + ImGui::Separator(); + const char* names[3] = { "One", "Two", "Three" }; + const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; + static int selected = -1; + for (int i = 0; i < 3; i++) + { + char label[32]; + sprintf(label, "%04d", i); + if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) + selected = i; + bool hovered = ImGui::IsItemHovered(); + ImGui::NextColumn(); + ImGui::Text(names[i]); ImGui::NextColumn(); + ImGui::Text(paths[i]); ImGui::NextColumn(); + ImGui::Text("%d", hovered); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Create multiple items in a same cell before switching to next column + if (ImGui::TreeNode("Mixed items")) + { + ImGui::Columns(3, "mixed"); + ImGui::Separator(); + + ImGui::Text("Hello"); + ImGui::Button("Banana"); + ImGui::NextColumn(); + + ImGui::Text("ImGui"); + ImGui::Button("Apple"); + static float foo = 1.0f; + ImGui::InputFloat("red", &foo, 0.05f, 0, 3); + ImGui::Text("An extra line here."); + ImGui::NextColumn(); + + ImGui::Text("Sailor"); + ImGui::Button("Corniflower"); + static float bar = 1.0f; + ImGui::InputFloat("blue", &bar, 0.05f, 0, 3); + ImGui::NextColumn(); + + if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Word wrapping + if (ImGui::TreeNode("Word-wrapping")) + { + ImGui::Columns(2, "word-wrapping"); + ImGui::Separator(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Left"); + ImGui::NextColumn(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Right"); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Borders")) + { + // NB: Future columns API should allow automatic horizontal borders. + static bool h_borders = true; + static bool v_borders = true; + ImGui::Checkbox("horizontal", &h_borders); + ImGui::SameLine(); + ImGui::Checkbox("vertical", &v_borders); + ImGui::Columns(4, NULL, v_borders); + for (int i = 0; i < 4*3; i++) + { + if (h_borders && ImGui::GetColumnIndex() == 0) + ImGui::Separator(); + ImGui::Text("%c%c%c", 'a'+i, 'a'+i, 'a'+i); + ImGui::Text("Width %.2f\nOffset %.2f", ImGui::GetColumnWidth(), ImGui::GetColumnOffset()); + ImGui::NextColumn(); + } + ImGui::Columns(1); + if (h_borders) + ImGui::Separator(); + ImGui::TreePop(); + } + + // Scrolling columns + /* + if (ImGui::TreeNode("Vertical Scrolling")) + { + ImGui::BeginChild("##header", ImVec2(0, ImGui::GetTextLineHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)); + ImGui::Columns(3); + ImGui::Text("ID"); ImGui::NextColumn(); + ImGui::Text("Name"); ImGui::NextColumn(); + ImGui::Text("Path"); ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::EndChild(); + ImGui::BeginChild("##scrollingregion", ImVec2(0, 60)); + ImGui::Columns(3); + for (int i = 0; i < 10; i++) + { + ImGui::Text("%04d", i); ImGui::NextColumn(); + ImGui::Text("Foobar"); ImGui::NextColumn(); + ImGui::Text("/path/foobar/%04d/", i); ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + */ + + if (ImGui::TreeNode("Horizontal Scrolling")) + { + ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); + ImGui::BeginChild("##ScrollingRegion", ImVec2(0, ImGui::GetFontSize() * 20), false, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::Columns(10); + int ITEMS_COUNT = 2000; + ImGuiListClipper clipper(ITEMS_COUNT); // Also demonstrate using the clipper for large list + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + for (int j = 0; j < 10; j++) + { + ImGui::Text("Line %d Column %d...", i, j); + ImGui::NextColumn(); + } + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + + bool node_open = ImGui::TreeNode("Tree within single cell"); + ImGui::SameLine(); ShowHelpMarker("NB: Tree node must be poped before ending the cell. There's no storage of state per-cell."); + if (node_open) + { + ImGui::Columns(2, "tree items"); + ImGui::Separator(); + if (ImGui::TreeNode("Hello")) { ImGui::BulletText("Sailor"); ImGui::TreePop(); } ImGui::NextColumn(); + if (ImGui::TreeNode("Bonjour")) { ImGui::BulletText("Marin"); ImGui::TreePop(); } ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + ImGui::PopID(); + } + + if (ImGui::CollapsingHeader("Filtering")) + { + static ImGuiTextFilter filter; + ImGui::Text("Filter usage:\n" + " \"\" display all lines\n" + " \"xxx\" display lines containing \"xxx\"\n" + " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" + " \"-xxx\" hide lines containing \"xxx\""); + filter.Draw(); + const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; + for (int i = 0; i < IM_ARRAYSIZE(lines); i++) + if (filter.PassFilter(lines[i])) + ImGui::BulletText("%s", lines[i]); + } + + if (ImGui::CollapsingHeader("Inputs & Focus")) + { + ImGuiIO& io = ImGui::GetIO(); + ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); + ImGui::SameLine(); ShowHelpMarker("Request ImGui to render a mouse cursor for you in software. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + + ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); + ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); + ImGui::Text("WantTextInput: %d", io.WantTextInput); + ImGui::Text("WantMoveMouse: %d", io.WantMoveMouse); + + if (ImGui::TreeNode("Keyboard & Mouse State")) + { + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse pos: "); + ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } + ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse dbl-clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } + ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); + + ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.KeysDownDuration[i]); } + ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + + + ImGui::Button("Hovering me sets the\nkeyboard capture flag"); + if (ImGui::IsItemHovered()) + ImGui::CaptureKeyboardFromApp(true); + ImGui::SameLine(); + ImGui::Button("Holding me clears the\nthe keyboard capture flag"); + if (ImGui::IsItemActive()) + ImGui::CaptureKeyboardFromApp(false); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Tabbing")) + { + ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); + static char buf[32] = "dummy"; + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); + ImGui::PushAllowKeyboardFocus(false); + ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); + //ImGui::SameLine(); ShowHelperMarker("Use ImGui::PushAllowKeyboardFocus(bool)\nto disable tabbing through certain widgets."); + ImGui::PopAllowKeyboardFocus(); + ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focus from code")) + { + bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); + bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); + bool focus_3 = ImGui::Button("Focus on 3"); + int has_focus = 0; + static char buf[128] = "click on a button to set focus"; + + if (focus_1) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 1; + + if (focus_2) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 2; + + ImGui::PushAllowKeyboardFocus(false); + if (focus_3) ImGui::SetKeyboardFocusHere(); + ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); + if (ImGui::IsItemActive()) has_focus = 3; + ImGui::PopAllowKeyboardFocus(); + if (has_focus) + ImGui::Text("Item with focus: %d", has_focus); + else + ImGui::Text("Item with focus: "); + ImGui::TextWrapped("Cursor & selection are preserved when refocusing last used item in code."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Focused & Hovered Test")) + { + static bool embed_all_inside_a_child_window = false; + ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); + if (embed_all_inside_a_child_window) + ImGui::BeginChild("embeddingchild", ImVec2(0, ImGui::GetFontSize() * 25), true); + + // Testing IsWindowFocused() function with its various flags (note that the flags can be combined) + ImGui::BulletText( + "IsWindowFocused() = %d\n" + "IsWindowFocused(_ChildWindows) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" + "IsWindowFocused(_RootWindow) = %d\n", + ImGui::IsWindowFocused(), + ImGui::IsWindowFocused(ImGuiHoveredFlags_ChildWindows), + ImGui::IsWindowFocused(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiHoveredFlags_RootWindow)); + + // Testing IsWindowHovered() function with its various flags (note that the flags can be combined) + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsWindowHovered(_ChildWindows) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" + "IsWindowHovered(_RootWindow) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow)); + + // Testing IsItemHovered() function (because BulletText is an item itself and that would affect the output of IsItemHovered, we pass all lines in a single items to shorten the code) + ImGui::Button("ITEM"); + ImGui::BulletText( + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlapped) = %d\n" + "IsItemhovered(_RectOnly) = %d\n", + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)); + + ImGui::BeginChild("child", ImVec2(0,50), true); + ImGui::Text("This is another child window for testing IsWindowHovered() flags."); + ImGui::EndChild(); + + if (embed_all_inside_a_child_window) + EndChild(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Dragging")) + { + ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); + for (int button = 0; button < 3; button++) + ImGui::Text("IsMouseDragging(%d):\n w/ default threshold: %d,\n w/ zero threshold: %d\n w/ large threshold: %d", + button, ImGui::IsMouseDragging(button), ImGui::IsMouseDragging(button, 0.0f), ImGui::IsMouseDragging(button, 20.0f)); + ImGui::Button("Drag Me"); + if (ImGui::IsItemActive()) + { + // Draw a line between the button and the mouse cursor + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->PushClipRectFullScreen(); + draw_list->AddLine(ImGui::CalcItemRectClosestPoint(io.MousePos, true, -2.0f), io.MousePos, ImColor(ImGui::GetStyle().Colors[ImGuiCol_Button]), 4.0f); + draw_list->PopClipRect(); + + // Drag operations gets "unlocked" when the mouse has moved past a certain threshold (the default threshold is stored in io.MouseDragThreshold) + // You can request a lower or higher threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta() + ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); + ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); + ImVec2 mouse_delta = io.MouseDelta; + ImGui::SameLine(); ImGui::Text("Raw (%.1f, %.1f), WithLockThresold (%.1f, %.1f), MouseDelta (%.1f, %.1f)", value_raw.x, value_raw.y, value_with_lock_threshold.x, value_with_lock_threshold.y, mouse_delta.x, mouse_delta.y); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Mouse cursors")) + { + const char* mouse_cursors_names[] = { "Arrow", "TextInput", "Move", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE" }; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_Count_); + + ImGui::Text("Current mouse cursor = %d: %s", ImGui::GetMouseCursor(), mouse_cursors_names[ImGui::GetMouseCursor()]); + ImGui::Text("Hover to see mouse cursors:"); + ImGui::SameLine(); ShowHelpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, otherwise your backend needs to handle it."); + for (int i = 0; i < ImGuiMouseCursor_Count_; i++) + { + char label[32]; + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); + ImGui::Bullet(); ImGui::Selectable(label, false); + if (ImGui::IsItemHovered()) + ImGui::SetMouseCursor(i); + } + ImGui::TreePop(); + } + } + + ImGui::End(); +} + +// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. +// Here we use the simplified Combo() api that packs items into a single literal string. Useful for quick combo boxes where the choices are known locally. +bool ImGui::ShowStyleSelector(const char* label) +{ + static int style_idx = 0; + if (ImGui::Combo(label, &style_idx, "Classic\0Dark\0Light\0")) + { + switch (style_idx) + { + case 0: ImGui::StyleColorsClassic(); break; + case 1: ImGui::StyleColorsDark(); break; + case 2: ImGui::StyleColorsLight(); break; + } + return true; + } + return false; +} + +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = ImGui::GetIO(); + ImFont* font_current = ImGui::GetFont(); + if (ImGui::BeginCombo(label, font_current->GetDebugName())) + { + for (int n = 0; n < io.Fonts->Fonts.Size; n++) + if (ImGui::Selectable(io.Fonts->Fonts[n]->GetDebugName(), io.Fonts->Fonts[n] == font_current)) + io.FontDefault = io.Fonts->Fonts[n]; + ImGui::EndCombo(); + } + ImGui::SameLine(); + ShowHelpMarker( + "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and documentation in extra_fonts/ for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} + +void ImGui::ShowStyleEditor(ImGuiStyle* ref) +{ + // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it compares to an internally stored reference) + ImGuiStyle& style = ImGui::GetStyle(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) + ref_saved_style = style; + init = false; + if (ref == NULL) + ref = &ref_saved_style; + + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + + if (ImGui::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + ImGui::ShowFontSelector("Fonts##Selector"); + + // Simplified Settings + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool window_border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &window_border)) style.WindowBorderSize = window_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool frame_border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &frame_border)) style.FrameBorderSize = frame_border ? 1.0f : 0.0f; } + ImGui::SameLine(); + { bool popup_border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &popup_border)) style.PopupBorderSize = popup_border ? 1.0f : 0.0f; } + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::SameLine(); + ShowHelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. Use \"Export Colors\" below to save them somewhere."); + + if (ImGui::TreeNode("Rendering")) + { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); ShowHelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + ImGui::PushItemWidth(100); + ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, NULL, 2.0f); + if (style.CurveTessellationTol < 0.0f) style.CurveTessellationTol = 0.10f; + ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. + ImGui::PopItemWidth(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Settings")) + { + ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + ImGui::Text("BorderSize"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::Text("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 14.0f, "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::Text("Alignment"); + ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ShowHelpMarker("Alignment applies when a button is larger than its text content."); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Colors")) + { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export Unsaved")) + { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const ImVec4& col = style.Colors[i]; + const char* name = ImGui::GetStyleColorName(i); + if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) + ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23-(int)strlen(name), "", col.x, col.y, col.z, col.w); + } + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::PushItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); ImGui::PopItemWidth(); + ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + ImGui::Text("Tip: Left-click on colored square to open color picker,\nRight-click to open edit options menu."); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", 200); + + static ImGuiColorEditFlags alpha_flags = 0; + ImGui::RadioButton("Opaque", &alpha_flags, 0); ImGui::SameLine(); + ImGui::RadioButton("Alpha", &alpha_flags, ImGuiColorEditFlags_AlphaPreview); ImGui::SameLine(); + ImGui::RadioButton("Both", &alpha_flags, ImGuiColorEditFlags_AlphaPreviewHalf); + + ImGui::BeginChild("#colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) + { + // Tips: in a real user application, you may want to merge and use an icon font into the main font, so instead of "Save"/"Revert" you'd use icons. + // Read the FAQ and extra_fonts/README.txt about using icon fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) ref->Colors[i] = style.Colors[i]; + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) style.Colors[i] = ref->Colors[i]; + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::TreePop(); + } + + bool fonts_opened = ImGui::TreeNode("Fonts", "Fonts (%d)", ImGui::GetIO().Fonts->Fonts.Size); + if (fonts_opened) + { + ImFontAtlas* atlas = ImGui::GetIO().Fonts; + if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + { + ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); + ImGui::TreePop(); + } + ImGui::PushItemWidth(100); + for (int i = 0; i < atlas->Fonts.Size; i++) + { + ImFont* font = atlas->Fonts[i]; + ImGui::PushID(font); + bool font_details_opened = ImGui::TreeNode(font, "Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size); + ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) ImGui::GetIO().FontDefault = font; + if (font_details_opened) + { + ImGui::PushFont(font); + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::PopFont(); + ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font + ImGui::SameLine(); ShowHelpMarker("Note than the default embedded font is NOT meant to be scaled.\n\nFont are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime.\n\n(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)"); + ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar); + ImGui::Text("Texture surface: %d pixels (approx) ~ %dx%d", font->MetricsTotalSurface, (int)sqrtf((float)font->MetricsTotalSurface), (int)sqrtf((float)font->MetricsTotalSurface)); + for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) + { + ImFontConfig* cfg = &font->ConfigData[config_i]; + ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d", config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH); + } + if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + { + // Display all glyphs of the fonts in separate pages of 256 characters + const ImFontGlyph* glyph_fallback = font->FallbackGlyph; // Forcefully/dodgily make FindGlyph() return NULL on fallback, which isn't the default behavior. + font->FallbackGlyph = NULL; + for (int base = 0; base < 0x10000; base += 256) + { + int count = 0; + for (int n = 0; n < 256; n++) + count += font->FindGlyph((ImWchar)(base + n)) ? 1 : 0; + if (count > 0 && ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base+255, count, count > 1 ? "glyphs" : "glyph")) + { + float cell_spacing = style.ItemSpacing.y; + ImVec2 cell_size(font->FontSize * 1, font->FontSize * 1); + ImVec2 base_pos = ImGui::GetCursorScreenPos(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + for (int n = 0; n < 256; n++) + { + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size.x + cell_spacing), base_pos.y + (n / 16) * (cell_size.y + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size.x, cell_p1.y + cell_size.y); + const ImFontGlyph* glyph = font->FindGlyph((ImWchar)(base+n));; + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255,255,255,100) : IM_COL32(255,255,255,50)); + font->RenderChar(draw_list, cell_size.x, cell_p1, ImGui::GetColorU32(ImGuiCol_Text), (ImWchar)(base+n)); // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions available to generate a string. + if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) + { + ImGui::BeginTooltip(); + ImGui::Text("Codepoint: U+%04X", base+n); + ImGui::Separator(); + ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); + ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); + ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + ImGui::EndTooltip(); + } + } + ImGui::Dummy(ImVec2((cell_size.x + cell_spacing) * 16, (cell_size.y + cell_spacing) * 16)); + ImGui::TreePop(); + } + } + font->FallbackGlyph = glyph_fallback; + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + static float window_scale = 1.0f; + ImGui::DragFloat("this window scale", &window_scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this window + ImGui::DragFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale everything + ImGui::PopItemWidth(); + ImGui::SetWindowFontScale(window_scale); + ImGui::TreePop(); + } + + ImGui::PopItemWidth(); +} + +// Demonstrate creating a fullscreen menu bar and populating it. +static void ShowExampleAppMainMenuBar() +{ + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Undo", "CTRL+Z")) {} + if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item + ImGui::Separator(); + if (ImGui::MenuItem("Cut", "CTRL+X")) {} + if (ImGui::MenuItem("Copy", "CTRL+C")) {} + if (ImGui::MenuItem("Paste", "CTRL+V")) {} + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + +static void ShowExampleMenuFile() +{ + ImGui::MenuItem("(dummy menu)", NULL, false, false); + if (ImGui::MenuItem("New")) {} + if (ImGui::MenuItem("Open", "Ctrl+O")) {} + if (ImGui::BeginMenu("Open Recent")) + { + ImGui::MenuItem("fish_hat.c"); + ImGui::MenuItem("fish_hat.inl"); + ImGui::MenuItem("fish_hat.h"); + if (ImGui::BeginMenu("More..")) + { + ImGui::MenuItem("Hello"); + ImGui::MenuItem("Sailor"); + if (ImGui::BeginMenu("Recurse..")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) {} + if (ImGui::MenuItem("Save As..")) {} + ImGui::Separator(); + if (ImGui::BeginMenu("Options")) + { + static bool enabled = true; + ImGui::MenuItem("Enabled", "", &enabled); + ImGui::BeginChild("child", ImVec2(0, 60), true); + for (int i = 0; i < 10; i++) + ImGui::Text("Scrolling Text %d", i); + ImGui::EndChild(); + static float f = 0.5f; + static int n = 0; + static bool b = true; + ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); + ImGui::InputFloat("Input", &f, 0.1f); + ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); + ImGui::Checkbox("Check", &b); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Colors")) + { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName((ImGuiCol)i); + ImGui::ColorButton(name, ImGui::GetStyleColorVec4((ImGuiCol)i)); + ImGui::SameLine(); + ImGui::MenuItem(name); + } + ImGui::PopStyleVar(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Disabled", false)) // Disabled + { + IM_ASSERT(0); + } + if (ImGui::MenuItem("Checked", NULL, true)) {} + if (ImGui::MenuItem("Quit", "Alt+F4")) {} +} + +// Demonstrate creating a window which gets auto-resized according to its content. +static void ShowExampleAppAutoResize(bool* p_open) +{ + if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + + static int lines = 10; + ImGui::Text("Window will resize every-frame to the size of its content.\nNote that you probably don't want to query the window size to\noutput your content because that would create a feedback loop."); + ImGui::SliderInt("Number of lines", &lines, 1, 20); + for (int i = 0; i < lines; i++) + ImGui::Text("%*sThis is line %d", i*4, "", i); // Pad with space to extend size horizontally + ImGui::End(); +} + +// Demonstrate creating a window with custom resize constraints. +static void ShowExampleAppConstrainedResize(bool* p_open) +{ + struct CustomConstraints // Helper functions to demonstrate programmatic constraints + { + static void Square(ImGuiSizeConstraintCallbackData* data) { data->DesiredSize = ImVec2(IM_MAX(data->DesiredSize.x, data->DesiredSize.y), IM_MAX(data->DesiredSize.x, data->DesiredSize.y)); } + static void Step(ImGuiSizeConstraintCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } + }; + + static bool auto_resize = false; + static int type = 0; + static int display_lines = 10; + if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 + if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)100);// Fixed Step + + ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; + if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) + { + const char* desc[] = + { + "Resize vertical only", + "Resize horizontal only", + "Width > 100, Height > 100", + "Width 400-500", + "Height 400-500", + "Custom: Always Square", + "Custom: Fixed Steps (100)", + }; + if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); + if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); + if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } + ImGui::PushItemWidth(200); + ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc)); + ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); + ImGui::PopItemWidth(); + ImGui::Checkbox("Auto-resize", &auto_resize); + for (int i = 0; i < display_lines; i++) + ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + } + ImGui::End(); +} + +// Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use. +static void ShowExampleAppFixedOverlay(bool* p_open) +{ + const float DISTANCE = 10.0f; + static int corner = 0; + ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE); + ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); // Transparent background + if (ImGui::Begin("Example: Fixed Overlay", p_open, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings)) + { + ImGui::Text("Simple overlay\nin the corner of the screen.\n(right-click to change position)"); + ImGui::Separator(); + ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; + if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; + if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; + if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; + ImGui::EndPopup(); + } + ImGui::End(); + } + ImGui::PopStyleColor(); +} + +// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. +// This apply to regular items as well. Read FAQ section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." for details. +static void ShowExampleAppWindowTitles(bool*) +{ + // By default, Windows are uniquely identified by their title. + // You can use the "##" and "###" markers to manipulate the display/ID. + + // Using "##" to display same title but have unique identifier. + ImGui::SetNextWindowPos(ImVec2(100,100), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##1"); + ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); + ImGui::End(); + + ImGui::SetNextWindowPos(ImVec2(100,200), ImGuiCond_FirstUseEver); + ImGui::Begin("Same title as another window##2"); + ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); + ImGui::End(); + + // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" + char buf[128]; + sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime()/0.25f)&3], ImGui::GetFrameCount()); + ImGui::SetNextWindowPos(ImVec2(100,300), ImGuiCond_FirstUseEver); + ImGui::Begin(buf); + ImGui::Text("This window has a changing title."); + ImGui::End(); +} + +// Demonstrate using the low-level ImDrawList to draw custom shapes. +static void ShowExampleAppCustomRendering(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(350,560), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Custom rendering", p_open)) + { + ImGui::End(); + return; + } + + // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of overloaded operators, etc. + // Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your types and ImVec2/ImVec4. + // ImGui defines overloaded operators but they are internal to imgui.cpp and not exposed outside (to avoid messing with your types) + // In this example we are not using the maths operators! + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Primitives + ImGui::Text("Primitives"); + static float sz = 36.0f; + static ImVec4 col = ImVec4(1.0f,1.0f,0.4f,1.0f); + ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f"); + ImGui::ColorEdit3("Color", &col.x); + { + const ImVec2 p = ImGui::GetCursorScreenPos(); + const ImU32 col32 = ImColor(col); + float x = p.x + 4.0f, y = p.y + 4.0f, spacing = 8.0f; + for (int n = 0; n < 2; n++) + { + float thickness = (n == 0) ? 1.0f : 4.0f; + draw_list->AddCircle(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 20, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 0.0f, ImDrawCornerFlags_All, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_All, thickness); x += sz+spacing; + draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight, thickness); x += sz+spacing; + draw_list->AddTriangle(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y ), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, thickness); x += sz+spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x, y+sz), col32, thickness); x += spacing; + draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x+sz*1.3f,y+sz*0.3f), ImVec2(x+sz-sz*1.3f,y+sz-sz*0.3f), ImVec2(x+sz, y+sz), col32, thickness); + x = p.x + 4; + y += sz+spacing; + } + draw_list->AddCircleFilled(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f); x += sz+spacing; + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight); x += sz+spacing; + draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32); x += sz+spacing; + draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x+sz, y+sz), ImColor(0,0,0), ImColor(255,0,0), ImColor(255,255,0), ImColor(0,255,0)); + ImGui::Dummy(ImVec2((sz+spacing)*8, (sz+spacing)*3)); + } + ImGui::Separator(); + { + static ImVector points; + static bool adding_line = false; + ImGui::Text("Canvas example"); + if (ImGui::Button("Clear")) points.clear(); + if (points.Size >= 2) { ImGui::SameLine(); if (ImGui::Button("Undo")) { points.pop_back(); points.pop_back(); } } + ImGui::Text("Left-click and drag to add lines,\nRight-click to undo"); + + // Here we are using InvisibleButton() as a convenience to 1) advance the cursor and 2) allows us to use IsItemHovered() + // However you can draw directly and poll mouse/keyboard by yourself. You can manipulate the cursor using GetCursorPos() and SetCursorPos(). + // If you only use the ImDrawList API, you can notify the owner window of its extends by using SetCursorPos(max). + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + if (canvas_size.x < 50.0f) canvas_size.x = 50.0f; + if (canvas_size.y < 50.0f) canvas_size.y = 50.0f; + draw_list->AddRectFilledMultiColor(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), ImColor(50,50,50), ImColor(50,50,60), ImColor(60,60,70), ImColor(50,50,60)); + draw_list->AddRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), ImColor(255,255,255)); + + bool adding_preview = false; + ImGui::InvisibleButton("canvas", canvas_size); + ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); + if (adding_line) + { + adding_preview = true; + points.push_back(mouse_pos_in_canvas); + if (!ImGui::GetIO().MouseDown[0]) + adding_line = adding_preview = false; + } + if (ImGui::IsItemHovered()) + { + if (!adding_line && ImGui::IsMouseClicked(0)) + { + points.push_back(mouse_pos_in_canvas); + adding_line = true; + } + if (ImGui::IsMouseClicked(1) && !points.empty()) + { + adding_line = adding_preview = false; + points.pop_back(); + points.pop_back(); + } + } + draw_list->PushClipRect(canvas_pos, ImVec2(canvas_pos.x+canvas_size.x, canvas_pos.y+canvas_size.y), true); // clip lines within the canvas (if we resize it, etc.) + for (int i = 0; i < points.Size - 1; i += 2) + draw_list->AddLine(ImVec2(canvas_pos.x + points[i].x, canvas_pos.y + points[i].y), ImVec2(canvas_pos.x + points[i+1].x, canvas_pos.y + points[i+1].y), IM_COL32(255,255,0,255), 2.0f); + draw_list->PopClipRect(); + if (adding_preview) + points.pop_back(); + } + ImGui::End(); +} + +// Demonstrating creating a simple console window, with scrolling, filtering, completion and history. +// For the console example, here we are using a more C++ like approach of declaring a class to hold the data and the functions. +struct ExampleAppConsole +{ + char InputBuf[256]; + ImVector Items; + bool ScrollToBottom; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + ImVector Commands; + + ExampleAppConsole() + { + ClearLog(); + memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; + Commands.push_back("HELP"); + Commands.push_back("HISTORY"); + Commands.push_back("CLEAR"); + Commands.push_back("CLASSIFY"); // "classify" is here to provide an example of "C"+[tab] completing to "CL" and displaying matches. + AddLog("Welcome to ImGui!"); + } + ~ExampleAppConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + free(History[i]); + } + + // Portable helpers + static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } + static int Strnicmp(const char* str1, const char* str2, int n) { int d = 0; while (n > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; n--; } return d; } + static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buff = malloc(len); return (char*)memcpy(buff, (const void*)str, len); } + + void ClearLog() + { + for (int i = 0; i < Items.Size; i++) + free(Items[i]); + Items.clear(); + ScrollToBottom = true; + } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + // FIXME-OPT + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf)-1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + + // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. So e.g. IsItemHovered() will return true when hovering the title bar. + // Here we create a context menu only available from the title bar. + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::MenuItem("Close")) + *p_open = false; + ImGui::EndPopup(); + } + + ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); + ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion."); + + // TODO: display items starting from the bottom + + if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); + if (ImGui::SmallButton("Add Dummy Error")) { AddLog("[error] something went wrong"); } ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { ClearLog(); } ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); ImGui::SameLine(); + if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true; + //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); + static ImGuiTextFilter filter; + filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::PopStyleVar(); + ImGui::Separator(); + + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } + + // Display every line as a separate entry so we can change their color or add custom widgets. If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping to only process visible items. + // You can seek and display only the lines that are visible using the ImGuiListClipper helper, if your elements are evenly spaced and you have cheap random access to the elements. + // To use the clipper we could replace the 'for (int i = 0; i < Items.Size; i++)' loop with: + // ImGuiListClipper clipper(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // However take note that you can not use this code as is if a filter is active because it breaks the 'cheap random-access' property. We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage to improve this example code! + // If your items are of variable size you may want to implement code similar to what ImGuiListClipper does. Or split your data into fixed height items to allow random-seeking into your list. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + ImVec4 col_default_text = ImGui::GetStyleColorVec4(ImGuiCol_Text); + for (int i = 0; i < Items.Size; i++) + { + const char* item = Items[i]; + if (!filter.PassFilter(item)) + continue; + ImVec4 col = col_default_text; + if (strstr(item, "[error]")) col = ImColor(1.0f,0.4f,0.4f,1.0f); + else if (strncmp(item, "# ", 2) == 0) col = ImColor(1.0f,0.78f,0.58f,1.0f); + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::TextUnformatted(item); + ImGui::PopStyleColor(); + } + if (copy_to_clipboard) + ImGui::LogFinish(); + if (ScrollToBottom) + ImGui::SetScrollHere(); + ScrollToBottom = false; + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::Separator(); + + // Command-line + if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) + { + char* input_end = InputBuf+strlen(InputBuf); + while (input_end > InputBuf && input_end[-1] == ' ') { input_end--; } *input_end = 0; + if (InputBuf[0]) + ExecCommand(InputBuf); + strcpy(InputBuf, ""); + } + + // Demonstrate keeping auto focus on the input box + if (ImGui::IsItemHovered() || (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0))) + ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + + ImGui::End(); + } + + void ExecCommand(const char* command_line) + { + AddLog("# %s\n", command_line); + + // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal. + HistoryPos = -1; + for (int i = History.Size-1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) + { + free(History[i]); + History.erase(History.begin() + i); + break; + } + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "CLEAR") == 0) + { + ClearLog(); + } + else if (Stricmp(command_line, "HELP") == 0) + { + AddLog("Commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } + else if (Stricmp(command_line, "HISTORY") == 0) + { + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } + else + { + AddLog("Unknown command: '%s'\n", command_line); + } + } + + static int TextEditCallbackStub(ImGuiTextEditCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks + { + ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; + return console->TextEditCallback(data); + } + + int TextEditCallback(ImGuiTextEditCallbackData* data) + { + //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + // Example of TEXT COMPLETION + + // Locate beginning of current word + const char* word_end = data->Buf + data->CursorPos; + const char* word_start = word_end; + while (word_start > data->Buf) + { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + break; + word_start--; + } + + // Build a list of candidates + ImVector candidates; + for (int i = 0; i < Commands.Size; i++) + if (Strnicmp(Commands[i], word_start, (int)(word_end-word_start)) == 0) + candidates.push_back(Commands[i]); + + if (candidates.Size == 0) + { + // No match + AddLog("No match for \"%.*s\"!\n", (int)(word_end-word_start), word_start); + } + else if (candidates.Size == 1) + { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing + data->DeleteChars((int)(word_start-data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } + else + { + // Multiple matches. Complete as much as we can, so inputing "C" will complete to "CL" and display "CLEAR" and "CLASSIFY" + int match_len = (int)(word_end - word_start); + for (;;) + { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + break; + match_len++; + } + + if (match_len > 0) + { + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end-word_start)); + data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); + } + + // List matches + AddLog("Possible matches:\n"); + for (int i = 0; i < candidates.Size; i++) + AddLog("- %s\n", candidates[i]); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) + { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (HistoryPos != -1) + if (++HistoryPos >= History.Size) + HistoryPos = -1; + } + + // A better implementation would preserve the data on the current input line along with cursor position. + if (prev_history_pos != HistoryPos) + { + data->CursorPos = data->SelectionStart = data->SelectionEnd = data->BufTextLen = (int)snprintf(data->Buf, (size_t)data->BufSize, "%s", (HistoryPos >= 0) ? History[HistoryPos] : ""); + data->BufDirty = true; + } + } + } + return 0; + } +}; + +static void ShowExampleAppConsole(bool* p_open) +{ + static ExampleAppConsole console; + console.Draw("Example: Console", p_open); +} + +// Usage: +// static ExampleAppLog my_log; +// my_log.AddLog("Hello %d world\n", 123); +// my_log.Draw("title"); +struct ExampleAppLog +{ + ImGuiTextBuffer Buf; + ImGuiTextFilter Filter; + ImVector LineOffsets; // Index to lines offset + bool ScrollToBottom; + + void Clear() { Buf.clear(); LineOffsets.clear(); } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') + LineOffsets.push_back(old_size); + ScrollToBottom = true; + } + + void Draw(const char* title, bool* p_open = NULL) + { + ImGui::SetNextWindowSize(ImVec2(500,400), ImGuiCond_FirstUseEver); + ImGui::Begin(title, p_open); + if (ImGui::Button("Clear")) Clear(); + ImGui::SameLine(); + bool copy = ImGui::Button("Copy"); + ImGui::SameLine(); + Filter.Draw("Filter", -100.0f); + ImGui::Separator(); + ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); + if (copy) ImGui::LogToClipboard(); + + if (Filter.IsActive()) + { + const char* buf_begin = Buf.begin(); + const char* line = buf_begin; + for (int line_no = 0; line != NULL; line_no++) + { + const char* line_end = (line_no < LineOffsets.Size) ? buf_begin + LineOffsets[line_no] : NULL; + if (Filter.PassFilter(line, line_end)) + ImGui::TextUnformatted(line, line_end); + line = line_end && line_end[1] ? line_end + 1 : NULL; + } + } + else + { + ImGui::TextUnformatted(Buf.begin()); + } + + if (ScrollToBottom) + ImGui::SetScrollHere(1.0f); + ScrollToBottom = false; + ImGui::EndChild(); + ImGui::End(); + } +}; + +// Demonstrate creating a simple log window with basic filtering. +static void ShowExampleAppLog(bool* p_open) +{ + static ExampleAppLog log; + + // Demo: add random items (unless Ctrl is held) + static float last_time = -1.0f; + float time = ImGui::GetTime(); + if (time - last_time >= 0.20f && !ImGui::GetIO().KeyCtrl) + { + const char* random_words[] = { "system", "info", "warning", "error", "fatal", "notice", "log" }; + log.AddLog("[%s] Hello, time is %.1f, frame count is %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, ImGui::GetFrameCount()); + last_time = time; + } + + log.Draw("Example: Log", p_open); +} + +// Demonstrate create a window with multiple child windows. +static void ShowExampleAppLayout(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Example: Layout", p_open, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Close")) *p_open = false; + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // left + static int selected = 0; + ImGui::BeginChild("left pane", ImVec2(150, 0), true); + for (int i = 0; i < 100; i++) + { + char label[128]; + sprintf(label, "MyObject %d", i); + if (ImGui::Selectable(label, selected == i)) + selected = i; + } + ImGui::EndChild(); + ImGui::SameLine(); + + // right + ImGui::BeginGroup(); + ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us + ImGui::Text("MyObject: %d", selected); + ImGui::Separator(); + ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); + ImGui::EndChild(); + if (ImGui::Button("Revert")) {} + ImGui::SameLine(); + if (ImGui::Button("Save")) {} + ImGui::EndGroup(); + } + ImGui::End(); +} + +// Demonstrate create a simple property editor. +static void ShowExampleAppPropertyEditor(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(430,450), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Property editor", p_open)) + { + ImGui::End(); + return; + } + + ShowHelpMarker("This example shows how you may implement a property editor using two columns.\nAll objects/fields data are dummies here.\nRemember that in many simple cases, you can use ImGui::SameLine(xxx) to position\nyour cursor horizontally instead of using the Columns() API."); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2,2)); + ImGui::Columns(2); + ImGui::Separator(); + + struct funcs + { + static void ShowDummyObject(const char* prefix, int uid) + { + ImGui::PushID(uid); // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. + ImGui::AlignTextToFramePadding(); // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high. + bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); + ImGui::NextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("my sailor is rich"); + ImGui::NextColumn(); + if (node_open) + { + static float dummy_members[8] = { 0.0f,0.0f,1.0f,3.1416f,100.0f,999.0f }; + for (int i = 0; i < 8; i++) + { + ImGui::PushID(i); // Use field index as identifier. + if (i < 2) + { + ShowDummyObject("Child", 424242); + } + else + { + ImGui::AlignTextToFramePadding(); + // Here we use a Selectable (instead of Text) to highlight on hover + //ImGui::Text("Field_%d", i); + char label[32]; + sprintf(label, "Field_%d", i); + ImGui::Bullet(); + ImGui::Selectable(label); + ImGui::NextColumn(); + ImGui::PushItemWidth(-1); + if (i >= 5) + ImGui::InputFloat("##value", &dummy_members[i], 1.0f); + else + ImGui::DragFloat("##value", &dummy_members[i], 0.01f); + ImGui::PopItemWidth(); + ImGui::NextColumn(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::PopID(); + } + }; + + // Iterate dummy objects with dummy members (all the same data) + for (int obj_i = 0; obj_i < 3; obj_i++) + funcs::ShowDummyObject("Object", obj_i); + + ImGui::Columns(1); + ImGui::Separator(); + ImGui::PopStyleVar(); + ImGui::End(); +} + +// Demonstrate/test rendering huge amount of text, and the incidence of clipping. +static void ShowExampleAppLongText(bool* p_open) +{ + ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Long text display", p_open)) + { + ImGui::End(); + return; + } + + static int test_type = 0; + static ImGuiTextBuffer log; + static int lines = 0; + ImGui::Text("Printing unusually long amount of text."); + ImGui::Combo("Test type", &test_type, "Single call to TextUnformatted()\0Multiple calls to Text(), clipped manually\0Multiple calls to Text(), not clipped (slow)\0"); + ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); + if (ImGui::Button("Clear")) { log.clear(); lines = 0; } + ImGui::SameLine(); + if (ImGui::Button("Add 1000 lines")) + { + for (int i = 0; i < 1000; i++) + log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines+i); + lines += 1000; + } + ImGui::BeginChild("Log"); + switch (test_type) + { + case 0: + // Single call to TextUnformatted() with a big buffer + ImGui::TextUnformatted(log.begin(), log.end()); + break; + case 1: + { + // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + ImGuiListClipper clipper(lines); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + case 2: + // Multiple calls to Text(), not clipped (slow) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + for (int i = 0; i < lines; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + ImGui::EndChild(); + ImGui::End(); +} + +// End of Demo code +#else + +void ImGui::ShowDemoWindow(bool*) {} +void ImGui::ShowUserGuide() {} +void ImGui::ShowStyleEditor(ImGuiStyle*) {} + +#endif diff --git a/skeleton/imgui/imgui_draw.cpp b/skeleton/imgui/imgui_draw.cpp new file mode 100644 index 0000000..b56d90c --- /dev/null +++ b/skeleton/imgui/imgui_draw.cpp @@ -0,0 +1,2906 @@ +// dear imgui, v1.54 WIP +// (drawing and font code) + +// Contains implementation for +// - Default styles +// - ImDrawList +// - ImDrawData +// - ImFontAtlas +// - ImFont +// - Default font data + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" + +#include // vsnprintf, sscanf, printf +#if !defined(alloca) +#ifdef _WIN32 +#include // alloca +#if !defined(alloca) +#define alloca _alloca // for clang with MS Codegen +#endif +#elif defined(__GLIBC__) || defined(__sun) +#include // alloca +#else +#include // alloca +#endif +#endif + +#ifdef _MSC_VER +#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#define snprintf _snprintf +#endif + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants ok. +#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // +#pragma clang diagnostic ignored "-Wcomma" // warning : possible misuse of comma operator here // +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // +#endif +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#endif +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers +#endif + +//------------------------------------------------------------------------- +// STB libraries implementation +//------------------------------------------------------------------------- + +//#define IMGUI_STB_NAMESPACE ImGuiStb +//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION + +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE +{ +#endif + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] +#endif + +#define STBRP_ASSERT(x) IM_ASSERT(x) +#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +#define STBRP_STATIC +#define STB_RECT_PACK_IMPLEMENTATION +#endif +#include "stb_rect_pack.h" + +#define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) +#define STBTT_free(x,u) ((void)(u), ImGui::MemFree(x)) +#define STBTT_assert(x) IM_ASSERT(x) +#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#else +#define STBTT_DEF extern +#endif +#include "stb_truetype.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +#ifdef IMGUI_STB_NAMESPACE +} // namespace ImGuiStb +using namespace IMGUI_STB_NAMESPACE; +#endif + +//----------------------------------------------------------------------------- +// Style functions +//----------------------------------------------------------------------------- + +void ImGui::StyleColorsClassic(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.47f, 0.69f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.41f, 0.64f, 0.69f); + colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.48f, 0.71f, 0.79f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.54f, 0.80f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.16f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_CloseButton] = ImVec4(0.50f, 0.50f, 0.90f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.70f, 0.70f, 0.90f, 0.60f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); +} + +void ImGui::StyleColorsDark(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); + colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = colors[ImGuiCol_Border];//ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_CloseButton] = ImVec4(0.41f, 0.41f, 0.41f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); +} + +// Those light colors are better suited with a thicker font than the default one + FrameBorder +void ImGui::StyleColorsLight(ImGuiStyle* dst) +{ + ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + //colors[ImGuiCol_TextHovered] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + //colors[ImGuiCol_TextActive] = ImVec4(1.00f, 1.00f, 0.00f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.80f, 0.80f, 0.80f, 0.56f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_CloseButton] = ImVec4(0.59f, 0.59f, 0.59f, 0.50f); + colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_CloseButtonActive] = ImVec4(0.98f, 0.39f, 0.36f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); +} + +//----------------------------------------------------------------------------- +// ImDrawListData +//----------------------------------------------------------------------------- + +ImDrawListSharedData::ImDrawListSharedData() +{ + Font = NULL; + FontSize = 0.0f; + CurveTessellationTol = 0.0f; + ClipRectFullscreen = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f); + + // Const data + for (int i = 0; i < IM_ARRAYSIZE(CircleVtx12); i++) + { + const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(CircleVtx12); + CircleVtx12[i] = ImVec2(cosf(a), sinf(a)); + } +} + +//----------------------------------------------------------------------------- +// ImDrawList +//----------------------------------------------------------------------------- + +void ImDrawList::Clear() +{ + CmdBuffer.resize(0); + IdxBuffer.resize(0); + VtxBuffer.resize(0); + Flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill; + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.resize(0); + _TextureIdStack.resize(0); + _Path.resize(0); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + // NB: Do not clear channels so our allocations are re-used after the first frame. +} + +void ImDrawList::ClearFreeMemory() +{ + CmdBuffer.clear(); + IdxBuffer.clear(); + VtxBuffer.clear(); + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.clear(); + _TextureIdStack.clear(); + _Path.clear(); + _ChannelsCurrent = 0; + _ChannelsCount = 1; + for (int i = 0; i < _Channels.Size; i++) + { + if (i == 0) memset(&_Channels[0], 0, sizeof(_Channels[0])); // channel 0 is a copy of CmdBuffer/IdxBuffer, don't destruct again + _Channels[i].CmdBuffer.clear(); + _Channels[i].IdxBuffer.clear(); + } + _Channels.clear(); +} + +// Using macros because C++ is a terrible language, we want guaranteed inline, no code in header, and no overhead in Debug builds +#define GetCurrentClipRect() (_ClipRectStack.Size ? _ClipRectStack.Data[_ClipRectStack.Size-1] : _Data->ClipRectFullscreen) +#define GetCurrentTextureId() (_TextureIdStack.Size ? _TextureIdStack.Data[_TextureIdStack.Size-1] : NULL) + +void ImDrawList::AddDrawCmd() +{ + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = GetCurrentClipRect(); + draw_cmd.TextureId = GetCurrentTextureId(); + + IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w); + CmdBuffer.push_back(draw_cmd); +} + +void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) +{ + ImDrawCmd* current_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!current_cmd || current_cmd->ElemCount != 0 || current_cmd->UserCallback != NULL) + { + AddDrawCmd(); + current_cmd = &CmdBuffer.back(); + } + current_cmd->UserCallback = callback; + current_cmd->UserCallbackData = callback_data; + + AddDrawCmd(); // Force a new command after us (see comment below) +} + +// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. +// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. +void ImDrawList::UpdateClipRect() +{ + // If current command is used with different settings we need to add a new command + const ImVec4 curr_clip_rect = GetCurrentClipRect(); + ImDrawCmd* curr_cmd = CmdBuffer.Size > 0 ? &CmdBuffer.Data[CmdBuffer.Size-1] : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) != 0) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && memcmp(&prev_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) == 0 && prev_cmd->TextureId == GetCurrentTextureId() && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->ClipRect = curr_clip_rect; +} + +void ImDrawList::UpdateTextureID() +{ + // If current command is used with different settings we need to add a new command + const ImTextureID curr_texture_id = GetCurrentTextureId(); + ImDrawCmd* curr_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; + if (!curr_cmd || (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != curr_texture_id) || curr_cmd->UserCallback != NULL) + { + AddDrawCmd(); + return; + } + + // Try to merge with previous command if it matches, else use current command + ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; + if (curr_cmd->ElemCount == 0 && prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL) + CmdBuffer.pop_back(); + else + curr_cmd->TextureId = curr_texture_id; +} + +#undef GetCurrentClipRect +#undef GetCurrentTextureId + +// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) +void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect) +{ + ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y); + if (intersect_with_current_clip_rect && _ClipRectStack.Size) + { + ImVec4 current = _ClipRectStack.Data[_ClipRectStack.Size-1]; + if (cr.x < current.x) cr.x = current.x; + if (cr.y < current.y) cr.y = current.y; + if (cr.z > current.z) cr.z = current.z; + if (cr.w > current.w) cr.w = current.w; + } + cr.z = ImMax(cr.x, cr.z); + cr.w = ImMax(cr.y, cr.w); + + _ClipRectStack.push_back(cr); + UpdateClipRect(); +} + +void ImDrawList::PushClipRectFullScreen() +{ + PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w)); +} + +void ImDrawList::PopClipRect() +{ + IM_ASSERT(_ClipRectStack.Size > 0); + _ClipRectStack.pop_back(); + UpdateClipRect(); +} + +void ImDrawList::PushTextureID(const ImTextureID& texture_id) +{ + _TextureIdStack.push_back(texture_id); + UpdateTextureID(); +} + +void ImDrawList::PopTextureID() +{ + IM_ASSERT(_TextureIdStack.Size > 0); + _TextureIdStack.pop_back(); + UpdateTextureID(); +} + +void ImDrawList::ChannelsSplit(int channels_count) +{ + IM_ASSERT(_ChannelsCurrent == 0 && _ChannelsCount == 1); + int old_channels_count = _Channels.Size; + if (old_channels_count < channels_count) + _Channels.resize(channels_count); + _ChannelsCount = channels_count; + + // _Channels[] (24/32 bytes each) hold storage that we'll swap with this->_CmdBuffer/_IdxBuffer + // The content of _Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. + // When we switch to the next channel, we'll copy _CmdBuffer/_IdxBuffer into _Channels[0] and then _Channels[1] into _CmdBuffer/_IdxBuffer + memset(&_Channels[0], 0, sizeof(ImDrawChannel)); + for (int i = 1; i < channels_count; i++) + { + if (i >= old_channels_count) + { + IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel(); + } + else + { + _Channels[i].CmdBuffer.resize(0); + _Channels[i].IdxBuffer.resize(0); + } + if (_Channels[i].CmdBuffer.Size == 0) + { + ImDrawCmd draw_cmd; + draw_cmd.ClipRect = _ClipRectStack.back(); + draw_cmd.TextureId = _TextureIdStack.back(); + _Channels[i].CmdBuffer.push_back(draw_cmd); + } + } +} + +void ImDrawList::ChannelsMerge() +{ + // Note that we never use or rely on channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. + if (_ChannelsCount <= 1) + return; + + ChannelsSetCurrent(0); + if (CmdBuffer.Size && CmdBuffer.back().ElemCount == 0) + CmdBuffer.pop_back(); + + int new_cmd_buffer_count = 0, new_idx_buffer_count = 0; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (ch.CmdBuffer.Size && ch.CmdBuffer.back().ElemCount == 0) + ch.CmdBuffer.pop_back(); + new_cmd_buffer_count += ch.CmdBuffer.Size; + new_idx_buffer_count += ch.IdxBuffer.Size; + } + CmdBuffer.resize(CmdBuffer.Size + new_cmd_buffer_count); + IdxBuffer.resize(IdxBuffer.Size + new_idx_buffer_count); + + ImDrawCmd* cmd_write = CmdBuffer.Data + CmdBuffer.Size - new_cmd_buffer_count; + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size - new_idx_buffer_count; + for (int i = 1; i < _ChannelsCount; i++) + { + ImDrawChannel& ch = _Channels[i]; + if (int sz = ch.CmdBuffer.Size) { memcpy(cmd_write, ch.CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; } + if (int sz = ch.IdxBuffer.Size) { memcpy(_IdxWritePtr, ch.IdxBuffer.Data, sz * sizeof(ImDrawIdx)); _IdxWritePtr += sz; } + } + UpdateClipRect(); // We call this instead of AddDrawCmd(), so that empty channels won't produce an extra draw call. + _ChannelsCount = 1; +} + +void ImDrawList::ChannelsSetCurrent(int idx) +{ + IM_ASSERT(idx < _ChannelsCount); + if (_ChannelsCurrent == idx) return; + memcpy(&_Channels.Data[_ChannelsCurrent].CmdBuffer, &CmdBuffer, sizeof(CmdBuffer)); // copy 12 bytes, four times + memcpy(&_Channels.Data[_ChannelsCurrent].IdxBuffer, &IdxBuffer, sizeof(IdxBuffer)); + _ChannelsCurrent = idx; + memcpy(&CmdBuffer, &_Channels.Data[_ChannelsCurrent].CmdBuffer, sizeof(CmdBuffer)); + memcpy(&IdxBuffer, &_Channels.Data[_ChannelsCurrent].IdxBuffer, sizeof(IdxBuffer)); + _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size; +} + +// NB: this can be called with negative count for removing primitives (as long as the result does not underflow) +void ImDrawList::PrimReserve(int idx_count, int vtx_count) +{ + ImDrawCmd& draw_cmd = CmdBuffer.Data[CmdBuffer.Size-1]; + draw_cmd.ElemCount += idx_count; + + int vtx_buffer_old_size = VtxBuffer.Size; + VtxBuffer.resize(vtx_buffer_old_size + vtx_count); + _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size; + + int idx_buffer_old_size = IdxBuffer.Size; + IdxBuffer.resize(idx_buffer_old_size + idx_count); + _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; +} + +// Fully unrolled with inline call to keep our debug builds decently fast. +void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); + _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); + _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _VtxCurrentIdx += 4; + _IdxWritePtr += 6; +} + +// TODO: Thickness anti-aliased lines cap are missing their AA fringe. +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, bool closed, float thickness) +{ + if (points_count < 2) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + + int count = points_count; + if (!closed) + count = points_count-1; + + const bool thick_line = thickness > 1.0f; + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + // Anti-aliased stroke + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + const int idx_count = thick_line ? count*18 : count*12; + const int vtx_count = thick_line ? points_count*4 : points_count*3; + PrimReserve(idx_count, vtx_count); + + // Temporary buffer + ImVec2* temp_normals = (ImVec2*)alloca(points_count * (thick_line ? 5 : 3) * sizeof(ImVec2)); + ImVec2* temp_points = temp_normals + points_count; + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + ImVec2 diff = points[i2] - points[i1]; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i1].x = diff.y; + temp_normals[i1].y = -diff.x; + } + if (!closed) + temp_normals[points_count-1] = temp_normals[points_count-2]; + + if (!thick_line) + { + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * AA_SIZE; + temp_points[1] = points[0] - temp_normals[0] * AA_SIZE; + temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * AA_SIZE; + temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * AA_SIZE; + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+3; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE; + temp_points[i2*2+0] = points[i2] + dm; + temp_points[i2*2+1] = points[i2] - dm; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+0); _IdxWritePtr[1] = (ImDrawIdx)(idx1+0); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+0); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10]= (ImDrawIdx)(idx2+0); _IdxWritePtr[11]= (ImDrawIdx)(idx2+1); + _IdxWritePtr += 12; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = temp_points[i*2+0]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; + _VtxWritePtr[2].pos = temp_points[i*2+1]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col_trans; + _VtxWritePtr += 3; + } + } + else + { + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + if (!closed) + { + temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); + temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); + temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+0] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + temp_points[(points_count-1)*4+1] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+2] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness); + temp_points[(points_count-1)*4+3] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); + } + + // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. + unsigned int idx1 = _VtxCurrentIdx; + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+4; + + // Average normals + ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + ImVec2 dm_out = dm * (half_inner_thickness + AA_SIZE); + ImVec2 dm_in = dm * half_inner_thickness; + temp_points[i2*4+0] = points[i2] + dm_out; + temp_points[i2*4+1] = points[i2] + dm_in; + temp_points[i2*4+2] = points[i2] - dm_in; + temp_points[i2*4+3] = points[i2] - dm_out; + + // Add indexes + _IdxWritePtr[0] = (ImDrawIdx)(idx2+1); _IdxWritePtr[1] = (ImDrawIdx)(idx1+1); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); + _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10] = (ImDrawIdx)(idx2+0); _IdxWritePtr[11] = (ImDrawIdx)(idx2+1); + _IdxWritePtr[12] = (ImDrawIdx)(idx2+2); _IdxWritePtr[13] = (ImDrawIdx)(idx1+2); _IdxWritePtr[14] = (ImDrawIdx)(idx1+3); + _IdxWritePtr[15] = (ImDrawIdx)(idx1+3); _IdxWritePtr[16] = (ImDrawIdx)(idx2+3); _IdxWritePtr[17] = (ImDrawIdx)(idx2+2); + _IdxWritePtr += 18; + + idx1 = idx2; + } + + // Add vertexes + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = temp_points[i*4+0]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col_trans; + _VtxWritePtr[1].pos = temp_points[i*4+1]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = temp_points[i*4+2]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = temp_points[i*4+3]; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col_trans; + _VtxWritePtr += 4; + } + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Stroke + const int idx_count = count*6; + const int vtx_count = count*4; // FIXME-OPT: Not sharing edges + PrimReserve(idx_count, vtx_count); + + for (int i1 = 0; i1 < count; i1++) + { + const int i2 = (i1+1) == points_count ? 0 : i1+1; + const ImVec2& p1 = points[i1]; + const ImVec2& p2 = points[i2]; + ImVec2 diff = p2 - p1; + diff *= ImInvLength(diff, 1.0f); + + const float dx = diff.x * (thickness * 0.5f); + const float dy = diff.y * (thickness * 0.5f); + _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+2); + _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx+2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx+3); + _IdxWritePtr += 6; + _VtxCurrentIdx += 4; + } + } +} + +void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + const ImVec2 uv = _Data->TexUvWhitePixel; + + if (Flags & ImDrawListFlags_AntiAliasedFill) + { + // Anti-aliased Fill + const float AA_SIZE = 1.0f; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + const int idx_count = (points_count-2)*3 + points_count*6; + const int vtx_count = (points_count*2); + PrimReserve(idx_count, vtx_count); + + // Add indexes for fill + unsigned int vtx_inner_idx = _VtxCurrentIdx; + unsigned int vtx_outer_idx = _VtxCurrentIdx+1; + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1)); + _IdxWritePtr += 3; + } + + // Compute normals + ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + ImVec2 diff = p1 - p0; + diff *= ImInvLength(diff, 1.0f); + temp_normals[i0].x = diff.y; + temp_normals[i0].y = -diff.x; + } + + for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + ImVec2 dm = (n0 + n1) * 0.5f; + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) + { + float scale = 1.0f / dmr2; + if (scale > 100.0f) scale = 100.0f; + dm *= scale; + } + dm *= AA_SIZE * 0.5f; + + // Add vertices + _VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner + _VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer + _VtxWritePtr += 2; + + // Add indexes for fringes + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); + _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Fill + const int idx_count = (points_count-2)*3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+i-1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+i); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} + +void ImDrawList::PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12) +{ + if (radius == 0.0f || a_min_of_12 > a_max_of_12) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); + for (int a = a_min_of_12; a <= a_max_of_12; a++) + { + const ImVec2& c = _Data->CircleVtx12[a % IM_ARRAYSIZE(_Data->CircleVtx12)]; + _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius)); + } +} + +void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments) +{ + if (radius == 0.0f) + { + _Path.push_back(centre); + return; + } + _Path.reserve(_Path.Size + (num_segments + 1)); + for (int i = 0; i <= num_segments; i++) + { + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); + _Path.push_back(ImVec2(centre.x + cosf(a) * radius, centre.y + sinf(a) * radius)); + } +} + +static void PathBezierToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2+d3) * (d2+d3) < tess_tol * (dx*dx + dy*dy)) + { + path->push_back(ImVec2(x4, y4)); + } + else if (level < 10) + { + float x12 = (x1+x2)*0.5f, y12 = (y1+y2)*0.5f; + float x23 = (x2+x3)*0.5f, y23 = (y2+y3)*0.5f; + float x34 = (x3+x4)*0.5f, y34 = (y3+y4)*0.5f; + float x123 = (x12+x23)*0.5f, y123 = (y12+y23)*0.5f; + float x234 = (x23+x34)*0.5f, y234 = (y23+y34)*0.5f; + float x1234 = (x123+x234)*0.5f, y1234 = (y123+y234)*0.5f; + + PathBezierToCasteljau(path, x1,y1, x12,y12, x123,y123, x1234,y1234, tess_tol, level+1); + PathBezierToCasteljau(path, x1234,y1234, x234,y234, x34,y34, x4,y4, tess_tol, level+1); + } +} + +void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments) +{ + ImVec2 p1 = _Path.back(); + if (num_segments == 0) + { + // Auto-tessellated + PathBezierToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); + } + else + { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + { + float t = t_step * i_step; + float u = 1.0f - t; + float w1 = u*u*u; + float w2 = 3*u*u*t; + float w3 = 3*u*t*t; + float w4 = t*t*t; + _Path.push_back(ImVec2(w1*p1.x + w2*p2.x + w3*p3.x + w4*p4.x, w1*p1.y + w2*p2.y + w3*p3.y + w4*p4.y)); + } + } +} + +void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, int rounding_corners) +{ + rounding = ImMin(rounding, fabsf(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f ) - 1.0f); + rounding = ImMin(rounding, fabsf(b.y - a.y) * ( ((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f ) - 1.0f); + + if (rounding <= 0.0f || rounding_corners == 0) + { + PathLineTo(a); + PathLineTo(ImVec2(b.x, a.y)); + PathLineTo(b); + PathLineTo(ImVec2(a.x, b.y)); + } + else + { + const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f; + const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f; + const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f; + const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f; + PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); + } +} + +void ImDrawList::AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathLineTo(a + ImVec2(0.5f,0.5f)); + PathLineTo(b + ImVec2(0.5f,0.5f)); + PathStroke(col, false, thickness); +} + +// a: upper-left, b: lower-right. we don't render 1 px sized rectangles properly. +void ImDrawList::AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.5f,0.5f), rounding, rounding_corners_flags); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (rounding > 0.0f) + { + PathRect(a, b, rounding, rounding_corners_flags); + PathFillConvex(col); + } + else + { + PrimReserve(6, 4); + PrimRect(a, b, col); + } +} + +void ImDrawList::AddRectFilledMultiColor(const ImVec2& a, const ImVec2& c, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) +{ + if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + PrimReserve(6, 4); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+3)); + PrimWriteVtx(a, uv, col_upr_left); + PrimWriteVtx(ImVec2(c.x, a.y), uv, col_upr_right); + PrimWriteVtx(c, uv, col_bot_right); + PrimWriteVtx(ImVec2(a.x, c.y), uv, col_bot_left); +} + +void ImDrawList::AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathLineTo(d); + PathFillConvex(col); +} + +void ImDrawList::AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(a); + PathLineTo(b); + PathLineTo(c); + PathFillConvex(col); +} + +void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments, float thickness) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius-0.5f, 0.0f, a_max, num_segments); + PathStroke(col, true, thickness); +} + +void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(centre, radius, 0.0f, a_max, num_segments); + PathFillConvex(col); +} + +void ImDrawList::AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + PathLineTo(pos0); + PathBezierCurveTo(cp0, cp1, pos1, num_segments); + PathStroke(col, false, thickness); +} + +void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (text_end == NULL) + text_end = text_begin + strlen(text_begin); + if (text_begin == text_end) + return; + + // Pull default font/size from the shared ImDrawListSharedData instance + if (font == NULL) + font = _Data->Font; + if (font_size == 0.0f) + font_size = _Data->FontSize; + + IM_ASSERT(font->ContainerAtlas->TexID == _TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + + ImVec4 clip_rect = _ClipRectStack.back(); + if (cpu_fine_clip_rect) + { + clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); + clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); + clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); + clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); + } + font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); +} + +void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) +{ + AddText(NULL, 0.0f, pos, col, text_begin, text_end); +} + +void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimRectUV(a, b, uv_a, uv_b, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col); + + if (push_texture_id) + PopTextureID(); +} + +void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + if (rounding <= 0.0f || (rounding_corners & ImDrawCornerFlags_All) == 0) + { + AddImage(user_texture_id, a, b, uv_a, uv_b, col); + return; + } + + const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + int vert_start_idx = VtxBuffer.Size; + PathRect(a, b, rounding, rounding_corners); + PathFillConvex(col); + int vert_end_idx = VtxBuffer.Size; + ImGui::ShadeVertsLinearUV(VtxBuffer.Data + vert_start_idx, VtxBuffer.Data + vert_end_idx, a, b, uv_a, uv_b, true); + + if (push_texture_id) + PopTextureID(); +} + +//----------------------------------------------------------------------------- +// ImDrawData +//----------------------------------------------------------------------------- + +// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! +void ImDrawData::DeIndexAllBuffers() +{ + ImVector new_vtx_buffer; + TotalVtxCount = TotalIdxCount = 0; + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + if (cmd_list->IdxBuffer.empty()) + continue; + new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); + for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; + cmd_list->VtxBuffer.swap(new_vtx_buffer); + cmd_list->IdxBuffer.resize(0); + TotalVtxCount += cmd_list->VtxBuffer.Size; + } +} + +// Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. +void ImDrawData::ScaleClipRects(const ImVec2& scale) +{ + for (int i = 0; i < CmdListsCount; i++) + { + ImDrawList* cmd_list = CmdLists[i]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i]; + cmd->ClipRect = ImVec4(cmd->ClipRect.x * scale.x, cmd->ClipRect.y * scale.y, cmd->ClipRect.z * scale.x, cmd->ClipRect.w * scale.y); + } + } +} + +//----------------------------------------------------------------------------- +// Shade functions +//----------------------------------------------------------------------------- + +// Generic linear color gradient, write to RGB fields, leave A untouched. +void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) +{ + ImVec2 gradient_extent = gradient_p1 - gradient_p0; + float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); + for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) + { + float d = ImDot(vert->pos - gradient_p0, gradient_extent); + float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t); + int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t); + vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); + } +} + +// Scan and shade backward from the end of given vertices. Assume vertices are text only (= vert_start..vert_end going left to right) so we can break as soon as we are out the gradient bounds. +void ImGui::ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x) +{ + float gradient_extent_x = gradient_p1_x - gradient_p0_x; + float gradient_inv_length2 = 1.0f / (gradient_extent_x * gradient_extent_x); + int full_alpha_count = 0; + for (ImDrawVert* vert = vert_end - 1; vert >= vert_start; vert--) + { + float d = (vert->pos.x - gradient_p0_x) * (gradient_extent_x); + float alpha_mul = 1.0f - ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + if (alpha_mul >= 1.0f && ++full_alpha_count > 2) + return; // Early out + int a = (int)(((vert->col >> IM_COL32_A_SHIFT) & 0xFF) * alpha_mul); + vert->col = (vert->col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); + } +} + +// Distribute UV over (a, b) rectangle +void ImGui::ShadeVertsLinearUV(ImDrawVert* vert_start, ImDrawVert* vert_end, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp) +{ + const ImVec2 size = b - a; + const ImVec2 uv_size = uv_b - uv_a; + const ImVec2 scale = ImVec2( + size.x != 0.0f ? (uv_size.x / size.x) : 0.0f, + size.y != 0.0f ? (uv_size.y / size.y) : 0.0f); + + if (clamp) + { + const ImVec2 min = ImMin(uv_a, uv_b); + const ImVec2 max = ImMax(uv_a, uv_b); + + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max); + } + else + { + for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); + } +} + +//----------------------------------------------------------------------------- +// ImFontConfig +//----------------------------------------------------------------------------- + +ImFontConfig::ImFontConfig() +{ + FontData = NULL; + FontDataSize = 0; + FontDataOwnedByAtlas = true; + FontNo = 0; + SizePixels = 0.0f; + OversampleH = 3; + OversampleV = 1; + PixelSnapH = false; + GlyphExtraSpacing = ImVec2(0.0f, 0.0f); + GlyphOffset = ImVec2(0.0f, 0.0f); + GlyphRanges = NULL; + MergeMode = false; + RasterizerFlags = 0x00; + RasterizerMultiply = 1.0f; + memset(Name, 0, sizeof(Name)); + DstFont = NULL; +} + +//----------------------------------------------------------------------------- +// ImFontAtlas +//----------------------------------------------------------------------------- + +// A work of art lies ahead! (. = white layer, X = black layer, others are blank) +// The white texels on the top left are the ones we'll use everywhere in ImGui to render filled shapes. +const int FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF = 90; +const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; +const unsigned int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0x80000000; +static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = +{ + "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX" + "..- -X.....X- X.X - X.X -X.....X - X.....X" + "--- -XXX.XXX- X...X - X...X -X....X - X....X" + "X - X.X - X.....X - X.....X -X...X - X...X" + "XX - X.X -X.......X- X.......X -X..X.X - X.X..X" + "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X" + "X..X - X.X - X.X - X.X -XX X.X - X.X XX" + "X...X - X.X - X.X - XX X.X XX - X.X - X.X " + "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X " + "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X " + "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X " + "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X " + "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X " + "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X " + "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X " + "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X " + "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX " + "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------" + "X.X X..X - -X.......X- X.......X - XX XX - " + "XX X..X - - X.....X - X.....X - X.X X.X - " + " X..X - X...X - X...X - X..X X..X - " + " XX - X.X - X.X - X...XXXXXXXXXXXXX...X - " + "------------ - X - X -X.....................X- " + " ----------------------------------- X...XXXXXXXXXXXXX...X - " + " - X..X X..X - " + " - X.X X.X - " + " - XX XX - " +}; + +static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_Count_][3] = +{ + // Pos ........ Size ......... Offset ...... + { ImVec2(0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow + { ImVec2(13,0), ImVec2(7,16), ImVec2( 4, 8) }, // ImGuiMouseCursor_TextInput + { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_Move + { ImVec2(21,0), ImVec2( 9,23), ImVec2( 5,11) }, // ImGuiMouseCursor_ResizeNS + { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 5) }, // ImGuiMouseCursor_ResizeEW + { ImVec2(73,0), ImVec2(17,17), ImVec2( 9, 9) }, // ImGuiMouseCursor_ResizeNESW + { ImVec2(55,0), ImVec2(17,17), ImVec2( 9, 9) }, // ImGuiMouseCursor_ResizeNWSE +}; + + +ImFontAtlas::ImFontAtlas() +{ + TexID = NULL; + TexDesiredWidth = 0; + TexGlyphPadding = 1; + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; + TexWidth = TexHeight = 0; + TexUvWhitePixel = ImVec2(0, 0); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +ImFontAtlas::~ImFontAtlas() +{ + Clear(); +} + +void ImFontAtlas::ClearInputData() +{ + for (int i = 0; i < ConfigData.Size; i++) + if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas) + { + ImGui::MemFree(ConfigData[i].FontData); + ConfigData[i].FontData = NULL; + } + + // When clearing this we lose access to the font name and other information used to build the font. + for (int i = 0; i < Fonts.Size; i++) + if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) + { + Fonts[i]->ConfigData = NULL; + Fonts[i]->ConfigDataCount = 0; + } + ConfigData.clear(); + CustomRects.clear(); + for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) + CustomRectIds[n] = -1; +} + +void ImFontAtlas::ClearTexData() +{ + if (TexPixelsAlpha8) + ImGui::MemFree(TexPixelsAlpha8); + if (TexPixelsRGBA32) + ImGui::MemFree(TexPixelsRGBA32); + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; +} + +void ImFontAtlas::ClearFonts() +{ + for (int i = 0; i < Fonts.Size; i++) + IM_DELETE(Fonts[i]); + Fonts.clear(); +} + +void ImFontAtlas::Clear() +{ + ClearInputData(); + ClearTexData(); + ClearFonts(); +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Build atlas on demand + if (TexPixelsAlpha8 == NULL) + { + if (ConfigData.empty()) + AddFontDefault(); + Build(); + } + + *out_pixels = TexPixelsAlpha8; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Convert to RGBA32 format on demand + // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp + if (!TexPixelsRGBA32) + { + unsigned char* pixels; + GetTexDataAsAlpha8(&pixels, NULL, NULL); + TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc((size_t)(TexWidth * TexHeight * 4)); + const unsigned char* src = pixels; + unsigned int* dst = TexPixelsRGBA32; + for (int n = TexWidth * TexHeight; n > 0; n--) + *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + } + + *out_pixels = (unsigned char*)TexPixelsRGBA32; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +} + +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +{ + IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); + IM_ASSERT(font_cfg->SizePixels > 0.0f); + + // Create new font + if (!font_cfg->MergeMode) + Fonts.push_back(IM_NEW(ImFont)); + else + IM_ASSERT(!Fonts.empty()); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + + ConfigData.push_back(*font_cfg); + ImFontConfig& new_font_cfg = ConfigData.back(); + if (!new_font_cfg.DstFont) + new_font_cfg.DstFont = Fonts.back(); + if (!new_font_cfg.FontDataOwnedByAtlas) + { + new_font_cfg.FontData = ImGui::MemAlloc(new_font_cfg.FontDataSize); + new_font_cfg.FontDataOwnedByAtlas = true; + memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + } + + // Invalidate texture + ClearTexData(); + return new_font_cfg.DstFont; +} + +// Default font TTF is compressed with stb_compress then base85 encoded (see extra_fonts/binary_to_compressed_c.cpp for encoder) +static unsigned int stb_decompress_length(unsigned char *input); +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); +static const char* GetDefaultCompressedFontDataTTFBase85(); +static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } +static void Decode85(const unsigned char* src, unsigned char* dst) +{ + while (*src) + { + unsigned int tmp = Decode85Byte(src[0]) + 85*(Decode85Byte(src[1]) + 85*(Decode85Byte(src[2]) + 85*(Decode85Byte(src[3]) + 85*Decode85Byte(src[4])))); + dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF); // We can't assume little-endianness. + src += 5; + dst += 4; + } +} + +// Load embedded ProggyClean.ttf at size 13, disable oversampling +ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (!font_cfg_template) + { + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.PixelSnapH = true; + } + if (font_cfg.Name[0] == '\0') strcpy(font_cfg.Name, "ProggyClean.ttf, 13px"); + if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f; + + const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, GetGlyphRangesDefault()); + return font; +} + +ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + int data_size = 0; + void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); + if (!data) + { + IM_ASSERT(0); // Could not load file. + return NULL; + } + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (font_cfg.Name[0] == '\0') + { + // Store a short copy of filename into into the font name for convenience + const char* p; + for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} + snprintf(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + } + return AddFontFromMemoryTTF(data, data_size, size_pixels, &font_cfg, glyph_ranges); +} + +// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). +ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontData = ttf_data; + font_cfg.FontDataSize = ttf_size; + font_cfg.SizePixels = size_pixels; + if (glyph_ranges) + font_cfg.GlyphRanges = glyph_ranges; + return AddFont(&font_cfg); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) +{ + const unsigned int buf_decompressed_size = stb_decompress_length((unsigned char*)compressed_ttf_data); + unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); + stb_decompress(buf_decompressed_data, (unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); + + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontDataOwnedByAtlas = true; + return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges); +} + +ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) +{ + int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; + void* compressed_ttf = ImGui::MemAlloc((size_t)compressed_ttf_size); + Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf); + ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); + ImGui::MemFree(compressed_ttf); + return font; +} + +int ImFontAtlas::AddCustomRectRegular(unsigned int id, int width, int height) +{ + IM_ASSERT(id >= 0x10000); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +{ + IM_ASSERT(font != NULL); + IM_ASSERT(width > 0 && width <= 0xFFFF); + IM_ASSERT(height > 0 && height <= 0xFFFF); + CustomRect r; + r.ID = id; + r.Width = (unsigned short)width; + r.Height = (unsigned short)height; + r.GlyphAdvanceX = advance_x; + r.GlyphOffset = offset; + r.Font = font; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +void ImFontAtlas::CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) +{ + IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates + IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed + *out_uv_min = ImVec2((float)rect->X / TexWidth, (float)rect->Y / TexHeight); + *out_uv_max = ImVec2((float)(rect->X + rect->Width) / TexWidth, (float)(rect->Y + rect->Height) / TexHeight); +} + +bool ImFontAtlas::Build() +{ + return ImFontAtlasBuildWithStbTruetype(this); +} + +void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) +{ + for (unsigned int i = 0; i < 256; i++) + { + unsigned int value = (unsigned int)(i * in_brighten_factor); + out_table[i] = value > 255 ? 255 : (value & 0xFF); + } +} + +void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) +{ + unsigned char* data = pixels + x + y * stride; + for (int j = h; j > 0; j--, data += stride) + for (int i = 0; i < w; i++) + data[i] = table[data[i]]; +} + +bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->ConfigData.Size > 0); + + ImFontAtlasBuildRegisterDefaultCustomRects(atlas); + + atlas->TexID = NULL; + atlas->TexWidth = atlas->TexHeight = 0; + atlas->TexUvWhitePixel = ImVec2(0, 0); + atlas->ClearTexData(); + + // Count glyphs/ranges + int total_glyphs_count = 0; + int total_ranges_count = 0; + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + if (!cfg.GlyphRanges) + cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, total_ranges_count++) + total_glyphs_count += (in_range[1] - in_range[0]) + 1; + } + + // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish. + // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. + atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512; + atlas->TexHeight = 0; + + // Start packing + const int max_tex_height = 1024*32; + stbtt_pack_context spc = {}; + stbtt_PackBegin(&spc, NULL, atlas->TexWidth, max_tex_height, 0, atlas->TexGlyphPadding, NULL); + stbtt_PackSetOversampling(&spc, 1, 1); + + // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). + ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); + + // Initialize font information (so we can error without any cleanup) + struct ImFontTempBuildData + { + stbtt_fontinfo FontInfo; + stbrp_rect* Rects; + int RectsCount; + stbtt_pack_range* Ranges; + int RangesCount; + }; + ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)atlas->ConfigData.Size * sizeof(ImFontTempBuildData)); + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); + + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); + IM_ASSERT(font_offset >= 0); + if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) + { + ImGui::MemFree(tmp_array); + return false; + } + } + + // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) + int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0; + stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar)); + stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect)); + stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range)); + memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar)); + memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity. + memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range)); + + // First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point) + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + + // Setup ranges + int font_glyphs_count = 0; + int font_ranges_count = 0; + for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++) + font_glyphs_count += (in_range[1] - in_range[0]) + 1; + tmp.Ranges = buf_ranges + buf_ranges_n; + tmp.RangesCount = font_ranges_count; + buf_ranges_n += font_ranges_count; + for (int i = 0; i < font_ranges_count; i++) + { + const ImWchar* in_range = &cfg.GlyphRanges[i * 2]; + stbtt_pack_range& range = tmp.Ranges[i]; + range.font_size = cfg.SizePixels; + range.first_unicode_codepoint_in_range = in_range[0]; + range.num_chars = (in_range[1] - in_range[0]) + 1; + range.chardata_for_range = buf_packedchars + buf_packedchars_n; + buf_packedchars_n += range.num_chars; + } + + // Pack + tmp.Rects = buf_rects + buf_rects_n; + tmp.RectsCount = font_glyphs_count; + buf_rects_n += font_glyphs_count; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + IM_ASSERT(n == font_glyphs_count); + stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n); + + // Extend texture height + for (int i = 0; i < n; i++) + if (tmp.Rects[i].was_packed) + atlas->TexHeight = ImMax(atlas->TexHeight, tmp.Rects[i].y + tmp.Rects[i].h); + } + IM_ASSERT(buf_rects_n == total_glyphs_count); + IM_ASSERT(buf_packedchars_n == total_glyphs_count); + IM_ASSERT(buf_ranges_n == total_ranges_count); + + // Create texture + atlas->TexHeight = ImUpperPowerOfTwo(atlas->TexHeight); + atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); + memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); + spc.pixels = atlas->TexPixelsAlpha8; + spc.height = atlas->TexHeight; + + // Second pass: render font characters + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); + stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); + if (cfg.RasterizerMultiply != 1.0f) + { + unsigned char multiply_table[256]; + ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); + for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++) + if (r->was_packed) + ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes); + } + tmp.Rects = NULL; + } + + // End packing + stbtt_PackEnd(&spc); + ImGui::MemFree(buf_rects); + buf_rects = NULL; + + // Third pass: setup ImFont and glyphs for runtime + for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) + { + ImFontConfig& cfg = atlas->ConfigData[input_i]; + ImFontTempBuildData& tmp = tmp_array[input_i]; + ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) + + const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + const float ascent = unscaled_ascent * font_scale; + const float descent = unscaled_descent * font_scale; + ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); + const float off_x = cfg.GlyphOffset.x; + const float off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); + + for (int i = 0; i < tmp.RangesCount; i++) + { + stbtt_pack_range& range = tmp.Ranges[i]; + for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1) + { + const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; + if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) + continue; + + const int codepoint = range.first_unicode_codepoint_in_range + char_idx; + if (cfg.MergeMode && dst_font->FindGlyph((unsigned short)codepoint)) + continue; + + stbtt_aligned_quad q; + float dummy_x = 0.0f, dummy_y = 0.0f; + stbtt_GetPackedQuad(range.chardata_for_range, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); + dst_font->AddGlyph((ImWchar)codepoint, q.x0 + off_x, q.y0 + off_y, q.x1 + off_x, q.y1 + off_y, q.s0, q.t0, q.s1, q.t1, pc.xadvance); + } + } + } + + // Cleanup temporaries + ImGui::MemFree(buf_packedchars); + ImGui::MemFree(buf_ranges); + ImGui::MemFree(tmp_array); + + ImFontAtlasBuildFinish(atlas); + + return true; +} + +void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas) +{ + if (atlas->CustomRectIds[0] < 0) + atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H); +} + +void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) +{ + if (!font_config->MergeMode) + { + font->ClearOutputData(); + font->FontSize = font_config->SizePixels; + font->ConfigData = font_config; + font->ContainerAtlas = atlas; + font->Ascent = ascent; + font->Descent = descent; + } + font->ConfigDataCount++; +} + +void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaque) +{ + stbrp_context* pack_context = (stbrp_context*)pack_context_opaque; + + ImVector& user_rects = atlas->CustomRects; + IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. + + ImVector pack_rects; + pack_rects.resize(user_rects.Size); + memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size); + for (int i = 0; i < user_rects.Size; i++) + { + pack_rects[i].w = user_rects[i].Width; + pack_rects[i].h = user_rects[i].Height; + } + stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); + for (int i = 0; i < pack_rects.Size; i++) + if (pack_rects[i].was_packed) + { + user_rects[i].X = pack_rects[i].x; + user_rects[i].Y = pack_rects[i].y; + IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + } +} + +static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->CustomRectIds[0] >= 0); + ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->CustomRectIds[0]]; + IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); + IM_ASSERT(r.Width == FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1); + IM_ASSERT(r.Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); + IM_ASSERT(r.IsPacked()); + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); + + // Render/copy pixels + for (int y = 0, n = 0; y < FONT_ATLAS_DEFAULT_TEX_DATA_H; y++) + for (int x = 0; x < FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF; x++, n++) + { + const int offset0 = (int)(r.X + x) + (int)(r.Y + y) * atlas->TexWidth; + const int offset1 = offset0 + FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + atlas->TexPixelsAlpha8[offset0] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == '.' ? 0xFF : 0x00; + atlas->TexPixelsAlpha8[offset1] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == 'X' ? 0xFF : 0x00; + } + const ImVec2 tex_uv_scale(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); + atlas->TexUvWhitePixel = ImVec2((r.X + 0.5f) * tex_uv_scale.x, (r.Y + 0.5f) * tex_uv_scale.y); + + // Setup mouse cursors + for (int type = 0; type < ImGuiMouseCursor_Count_; type++) + { + ImGuiMouseCursorData& cursor_data = GImGui->MouseCursorData[type]; + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[type][0] + ImVec2((float)r.X, (float)r.Y); + const ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[type][1]; + cursor_data.Type = type; + cursor_data.Size = size; + cursor_data.HotOffset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[type][2]; + cursor_data.TexUvMin[0] = (pos) * tex_uv_scale; + cursor_data.TexUvMax[0] = (pos + size) * tex_uv_scale; + pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; + cursor_data.TexUvMin[1] = (pos) * tex_uv_scale; + cursor_data.TexUvMax[1] = (pos + size) * tex_uv_scale; + } +} + +void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +{ + // Render into our custom data block + ImFontAtlasBuildRenderDefaultTexData(atlas); + + // Register custom rectangle glyphs + for (int i = 0; i < atlas->CustomRects.Size; i++) + { + const ImFontAtlas::CustomRect& r = atlas->CustomRects[i]; + if (r.Font == NULL || r.ID > 0x10000) + continue; + + IM_ASSERT(r.Font->ContainerAtlas == atlas); + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(&r, &uv0, &uv1); + r.Font->AddGlyph((ImWchar)r.ID, r.GlyphOffset.x, r.GlyphOffset.y, r.GlyphOffset.x + r.Width, r.GlyphOffset.y + r.Height, uv0.x, uv0.y, uv1.x, uv1.y, r.GlyphAdvanceX); + } + + // Build all fonts lookup tables + for (int i = 0; i < atlas->Fonts.Size; i++) + atlas->Fonts[i]->BuildLookupTable(); +} + +// Retrieve list of range (2 int per range, values are inclusive) +const ImWchar* ImFontAtlas::GetGlyphRangesDefault() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesKorean() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3131, 0x3163, // Korean alphabets + 0xAC00, 0xD79D, // Korean characters + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesChinese() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + 0x4e00, 0x9FAF, // CJK Ideograms + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() +{ + // Store the 1946 ideograms code points as successive offsets from the initial unicode codepoint 0x4E00. Each offset has an implicit +1. + // This encoding is designed to helps us reduce the source code size. + // FIXME: Source a list of the revised 2136 joyo kanji list from 2010 and rebuild this. + // The current list was sourced from http://theinstructionlimit.com/author/renaudbedardrenaudbedard/page/3 + // Note that you may use ImFontAtlas::GlyphRangesBuilder to create your own ranges, by merging existing ranges or adding new characters. + static const short offsets_from_0x4E00[] = + { + -1,0,1,3,0,0,0,0,1,0,5,1,1,0,7,4,6,10,0,1,9,9,7,1,3,19,1,10,7,1,0,1,0,5,1,0,6,4,2,6,0,0,12,6,8,0,3,5,0,1,0,9,0,0,8,1,1,3,4,5,13,0,0,8,2,17, + 4,3,1,1,9,6,0,0,0,2,1,3,2,22,1,9,11,1,13,1,3,12,0,5,9,2,0,6,12,5,3,12,4,1,2,16,1,1,4,6,5,3,0,6,13,15,5,12,8,14,0,0,6,15,3,6,0,18,8,1,6,14,1, + 5,4,12,24,3,13,12,10,24,0,0,0,1,0,1,1,2,9,10,2,2,0,0,3,3,1,0,3,8,0,3,2,4,4,1,6,11,10,14,6,15,3,4,15,1,0,0,5,2,2,0,0,1,6,5,5,6,0,3,6,5,0,0,1,0, + 11,2,2,8,4,7,0,10,0,1,2,17,19,3,0,2,5,0,6,2,4,4,6,1,1,11,2,0,3,1,2,1,2,10,7,6,3,16,0,8,24,0,0,3,1,1,3,0,1,6,0,0,0,2,0,1,5,15,0,1,0,0,2,11,19, + 1,4,19,7,6,5,1,0,0,0,0,5,1,0,1,9,0,0,5,0,2,0,1,0,3,0,11,3,0,2,0,0,0,0,0,9,3,6,4,12,0,14,0,0,29,10,8,0,14,37,13,0,31,16,19,0,8,30,1,20,8,3,48, + 21,1,0,12,0,10,44,34,42,54,11,18,82,0,2,1,2,12,1,0,6,2,17,2,12,7,0,7,17,4,2,6,24,23,8,23,39,2,16,23,1,0,5,1,2,15,14,5,6,2,11,0,8,6,2,2,2,14, + 20,4,15,3,4,11,10,10,2,5,2,1,30,2,1,0,0,22,5,5,0,3,1,5,4,1,0,0,2,2,21,1,5,1,2,16,2,1,3,4,0,8,4,0,0,5,14,11,2,16,1,13,1,7,0,22,15,3,1,22,7,14, + 22,19,11,24,18,46,10,20,64,45,3,2,0,4,5,0,1,4,25,1,0,0,2,10,0,0,0,1,0,1,2,0,0,9,1,2,0,0,0,2,5,2,1,1,5,5,8,1,1,1,5,1,4,9,1,3,0,1,0,1,1,2,0,0, + 2,0,1,8,22,8,1,0,0,0,0,4,2,1,0,9,8,5,0,9,1,30,24,2,6,4,39,0,14,5,16,6,26,179,0,2,1,1,0,0,0,5,2,9,6,0,2,5,16,7,5,1,1,0,2,4,4,7,15,13,14,0,0, + 3,0,1,0,0,0,2,1,6,4,5,1,4,9,0,3,1,8,0,0,10,5,0,43,0,2,6,8,4,0,2,0,0,9,6,0,9,3,1,6,20,14,6,1,4,0,7,2,3,0,2,0,5,0,3,1,0,3,9,7,0,3,4,0,4,9,1,6,0, + 9,0,0,2,3,10,9,28,3,6,2,4,1,2,32,4,1,18,2,0,3,1,5,30,10,0,2,2,2,0,7,9,8,11,10,11,7,2,13,7,5,10,0,3,40,2,0,1,6,12,0,4,5,1,5,11,11,21,4,8,3,7, + 8,8,33,5,23,0,0,19,8,8,2,3,0,6,1,1,1,5,1,27,4,2,5,0,3,5,6,3,1,0,3,1,12,5,3,3,2,0,7,7,2,1,0,4,0,1,1,2,0,10,10,6,2,5,9,7,5,15,15,21,6,11,5,20, + 4,3,5,5,2,5,0,2,1,0,1,7,28,0,9,0,5,12,5,5,18,30,0,12,3,3,21,16,25,32,9,3,14,11,24,5,66,9,1,2,0,5,9,1,5,1,8,0,8,3,3,0,1,15,1,4,8,1,2,7,0,7,2, + 8,3,7,5,3,7,10,2,1,0,0,2,25,0,6,4,0,10,0,4,2,4,1,12,5,38,4,0,4,1,10,5,9,4,0,14,4,2,5,18,20,21,1,3,0,5,0,7,0,3,7,1,3,1,1,8,1,0,0,0,3,2,5,2,11, + 6,0,13,1,3,9,1,12,0,16,6,2,1,0,2,1,12,6,13,11,2,0,28,1,7,8,14,13,8,13,0,2,0,5,4,8,10,2,37,42,19,6,6,7,4,14,11,18,14,80,7,6,0,4,72,12,36,27, + 7,7,0,14,17,19,164,27,0,5,10,7,3,13,6,14,0,2,2,5,3,0,6,13,0,0,10,29,0,4,0,3,13,0,3,1,6,51,1,5,28,2,0,8,0,20,2,4,0,25,2,10,13,10,0,16,4,0,1,0, + 2,1,7,0,1,8,11,0,0,1,2,7,2,23,11,6,6,4,16,2,2,2,0,22,9,3,3,5,2,0,15,16,21,2,9,20,15,15,5,3,9,1,0,0,1,7,7,5,4,2,2,2,38,24,14,0,0,15,5,6,24,14, + 5,5,11,0,21,12,0,3,8,4,11,1,8,0,11,27,7,2,4,9,21,59,0,1,39,3,60,62,3,0,12,11,0,3,30,11,0,13,88,4,15,5,28,13,1,4,48,17,17,4,28,32,46,0,16,0, + 18,11,1,8,6,38,11,2,6,11,38,2,0,45,3,11,2,7,8,4,30,14,17,2,1,1,65,18,12,16,4,2,45,123,12,56,33,1,4,3,4,7,0,0,0,3,2,0,16,4,2,4,2,0,7,4,5,2,26, + 2,25,6,11,6,1,16,2,6,17,77,15,3,35,0,1,0,5,1,0,38,16,6,3,12,3,3,3,0,9,3,1,3,5,2,9,0,18,0,25,1,3,32,1,72,46,6,2,7,1,3,14,17,0,28,1,40,13,0,20, + 15,40,6,38,24,12,43,1,1,9,0,12,6,0,6,2,4,19,3,7,1,48,0,9,5,0,5,6,9,6,10,15,2,11,19,3,9,2,0,1,10,1,27,8,1,3,6,1,14,0,26,0,27,16,3,4,9,6,2,23, + 9,10,5,25,2,1,6,1,1,48,15,9,15,14,3,4,26,60,29,13,37,21,1,6,4,0,2,11,22,23,16,16,2,2,1,3,0,5,1,6,4,0,0,4,0,0,8,3,0,2,5,0,7,1,7,3,13,2,4,10, + 3,0,2,31,0,18,3,0,12,10,4,1,0,7,5,7,0,5,4,12,2,22,10,4,2,15,2,8,9,0,23,2,197,51,3,1,1,4,13,4,3,21,4,19,3,10,5,40,0,4,1,1,10,4,1,27,34,7,21, + 2,17,2,9,6,4,2,3,0,4,2,7,8,2,5,1,15,21,3,4,4,2,2,17,22,1,5,22,4,26,7,0,32,1,11,42,15,4,1,2,5,0,19,3,1,8,6,0,10,1,9,2,13,30,8,2,24,17,19,1,4, + 4,25,13,0,10,16,11,39,18,8,5,30,82,1,6,8,18,77,11,13,20,75,11,112,78,33,3,0,0,60,17,84,9,1,1,12,30,10,49,5,32,158,178,5,5,6,3,3,1,3,1,4,7,6, + 19,31,21,0,2,9,5,6,27,4,9,8,1,76,18,12,1,4,0,3,3,6,3,12,2,8,30,16,2,25,1,5,5,4,3,0,6,10,2,3,1,0,5,1,19,3,0,8,1,5,2,6,0,0,0,19,1,2,0,5,1,2,5, + 1,3,7,0,4,12,7,3,10,22,0,9,5,1,0,2,20,1,1,3,23,30,3,9,9,1,4,191,14,3,15,6,8,50,0,1,0,0,4,0,0,1,0,2,4,2,0,2,3,0,2,0,2,2,8,7,0,1,1,1,3,3,17,11, + 91,1,9,3,2,13,4,24,15,41,3,13,3,1,20,4,125,29,30,1,0,4,12,2,21,4,5,5,19,11,0,13,11,86,2,18,0,7,1,8,8,2,2,22,1,2,6,5,2,0,1,2,8,0,2,0,5,2,1,0, + 2,10,2,0,5,9,2,1,2,0,1,0,4,0,0,10,2,5,3,0,6,1,0,1,4,4,33,3,13,17,3,18,6,4,7,1,5,78,0,4,1,13,7,1,8,1,0,35,27,15,3,0,0,0,1,11,5,41,38,15,22,6, + 14,14,2,1,11,6,20,63,5,8,27,7,11,2,2,40,58,23,50,54,56,293,8,8,1,5,1,14,0,1,12,37,89,8,8,8,2,10,6,0,0,0,4,5,2,1,0,1,1,2,7,0,3,3,0,4,6,0,3,2, + 19,3,8,0,0,0,4,4,16,0,4,1,5,1,3,0,3,4,6,2,17,10,10,31,6,4,3,6,10,126,7,3,2,2,0,9,0,0,5,20,13,0,15,0,6,0,2,5,8,64,50,3,2,12,2,9,0,0,11,8,20, + 109,2,18,23,0,0,9,61,3,0,28,41,77,27,19,17,81,5,2,14,5,83,57,252,14,154,263,14,20,8,13,6,57,39,38, + }; + static ImWchar base_ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana + 0x31F0, 0x31FF, // Katakana Phonetic Extensions + 0xFF00, 0xFFEF, // Half-width characters + }; + static bool full_ranges_unpacked = false; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(offsets_from_0x4E00)*2 + 1]; + if (!full_ranges_unpacked) + { + // Unpack + int codepoint = 0x4e00; + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + ImWchar* dst = full_ranges + IM_ARRAYSIZE(base_ranges);; + for (int n = 0; n < IM_ARRAYSIZE(offsets_from_0x4E00); n++, dst += 2) + dst[0] = dst[1] = (ImWchar)(codepoint += (offsets_from_0x4E00[n] + 1)); + dst[0] = 0; + full_ranges_unpacked = true; + } + return &full_ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement + 0x2DE0, 0x2DFF, // Cyrillic Extended-A + 0xA640, 0xA69F, // Cyrillic Extended-B + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFontAtlas::GetGlyphRangesThai() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + 0x2010, 0x205E, // Punctuations + 0x0E00, 0x0E7F, // Thai + 0, + }; + return &ranges[0]; +} + +//----------------------------------------------------------------------------- +// ImFontAtlas::GlyphRangesBuilder +//----------------------------------------------------------------------------- + +void ImFontAtlas::GlyphRangesBuilder::AddText(const char* text, const char* text_end) +{ + while (text_end ? (text < text_end) : *text) + { + unsigned int c = 0; + int c_len = ImTextCharFromUtf8(&c, text, text_end); + text += c_len; + if (c_len == 0) + break; + if (c < 0x10000) + AddChar((ImWchar)c); + } +} + +void ImFontAtlas::GlyphRangesBuilder::AddRanges(const ImWchar* ranges) +{ + for (; ranges[0]; ranges += 2) + for (ImWchar c = ranges[0]; c <= ranges[1]; c++) + AddChar(c); +} + +void ImFontAtlas::GlyphRangesBuilder::BuildRanges(ImVector* out_ranges) +{ + for (int n = 0; n < 0x10000; n++) + if (GetBit(n)) + { + out_ranges->push_back((ImWchar)n); + while (n < 0x10000 && GetBit(n + 1)) + n++; + out_ranges->push_back((ImWchar)n); + } + out_ranges->push_back(0); +} + +//----------------------------------------------------------------------------- +// ImFont +//----------------------------------------------------------------------------- + +ImFont::ImFont() +{ + Scale = 1.0f; + FallbackChar = (ImWchar)'?'; + DisplayOffset = ImVec2(0.0f, 1.0f); + ClearOutputData(); +} + +ImFont::~ImFont() +{ + // Invalidate active font so that the user gets a clear crash instead of a dangling pointer. + // If you want to delete fonts you need to do it between Render() and NewFrame(). + // FIXME-CLEANUP + /* + ImGuiContext& g = *GImGui; + if (g.Font == this) + g.Font = NULL; + */ + ClearOutputData(); +} + +void ImFont::ClearOutputData() +{ + FontSize = 0.0f; + Glyphs.clear(); + IndexAdvanceX.clear(); + IndexLookup.clear(); + FallbackGlyph = NULL; + FallbackAdvanceX = 0.0f; + ConfigDataCount = 0; + ConfigData = NULL; + ContainerAtlas = NULL; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; +} + +void ImFont::BuildLookupTable() +{ + int max_codepoint = 0; + for (int i = 0; i != Glyphs.Size; i++) + max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); + + IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved + IndexAdvanceX.clear(); + IndexLookup.clear(); + GrowIndex(max_codepoint + 1); + for (int i = 0; i < Glyphs.Size; i++) + { + int codepoint = (int)Glyphs[i].Codepoint; + IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; + IndexLookup[codepoint] = (unsigned short)i; + } + + // Create a glyph to handle TAB + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (FindGlyph((unsigned short)' ')) + { + if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& tab_glyph = Glyphs.back(); + tab_glyph = *FindGlyph((unsigned short)' '); + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX *= 4; + IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; + IndexLookup[(int)tab_glyph.Codepoint] = (unsigned short)(Glyphs.Size-1); + } + + FallbackGlyph = NULL; + FallbackGlyph = FindGlyph(FallbackChar); + FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; + for (int i = 0; i < max_codepoint + 1; i++) + if (IndexAdvanceX[i] < 0.0f) + IndexAdvanceX[i] = FallbackAdvanceX; +} + +void ImFont::SetFallbackChar(ImWchar c) +{ + FallbackChar = c; + BuildLookupTable(); +} + +void ImFont::GrowIndex(int new_size) +{ + IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); + if (new_size <= IndexLookup.Size) + return; + IndexAdvanceX.resize(new_size, -1.0f); + IndexLookup.resize(new_size, (unsigned short)-1); +} + +void ImFont::AddGlyph(ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +{ + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph& glyph = Glyphs.back(); + glyph.Codepoint = (ImWchar)codepoint; + glyph.X0 = x0; + glyph.Y0 = y0; + glyph.X1 = x1; + glyph.Y1 = y1; + glyph.U0 = u0; + glyph.V0 = v0; + glyph.U1 = u1; + glyph.V1 = v1; + glyph.AdvanceX = advance_x + ConfigData->GlyphExtraSpacing.x; // Bake spacing into AdvanceX + + if (ConfigData->PixelSnapH) + glyph.AdvanceX = (float)(int)(glyph.AdvanceX + 0.5f); + + // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) + MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + 1.99f); +} + +void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) +{ + IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. + int index_size = IndexLookup.Size; + + if (dst < index_size && IndexLookup.Data[dst] == (unsigned short)-1 && !overwrite_dst) // 'dst' already exists + return; + if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op + return; + + GrowIndex(dst + 1); + IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (unsigned short)-1; + IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; +} + +const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +{ + if (c < IndexLookup.Size) + { + const unsigned short i = IndexLookup[c]; + if (i != (unsigned short)-1) + return &Glyphs.Data[i]; + } + return FallbackGlyph; +} + +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +{ + // Simple word-wrapping for English, not full-featured. Please submit failing cases! + // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) + + // For references, possible wrap point marked with ^ + // "aaa bbb, ccc,ddd. eee fff. ggg!" + // ^ ^ ^ ^ ^__ ^ ^ + + // List of hardcoded separators: .,;!?'" + + // Skip extra blanks after a line returns (that includes not counting them in width computation) + // e.g. "Hello world" --> "Hello" "World" + + // Cut words that cannot possibly fit within one line. + // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + float line_width = 0.0f; + float word_width = 0.0f; + float blank_width = 0.0f; + wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters + + const char* word_end = text; + const char* prev_word_end = NULL; + bool inside_word = true; + + const char* s = text; + while (s < text_end) + { + unsigned int c = (unsigned int)*s; + const char* next_s; + if (c < 0x80) + next_s = s + 1; + else + next_s = s + ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) + break; + + if (c < 32) + { + if (c == '\n') + { + line_width = word_width = blank_width = 0.0f; + inside_word = true; + s = next_s; + continue; + } + if (c == '\r') + { + s = next_s; + continue; + } + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX); + if (ImCharIsSpace(c)) + { + if (inside_word) + { + line_width += blank_width; + blank_width = 0.0f; + word_end = s; + } + blank_width += char_width; + inside_word = false; + } + else + { + word_width += char_width; + if (inside_word) + { + word_end = next_s; + } + else + { + prev_word_end = word_end; + line_width += word_width + blank_width; + word_width = blank_width = 0.0f; + } + + // Allow wrapping after punctuation. + inside_word = !(c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"'); + } + + // We ignore blank width at the end of the line (they can be skipped) + if (line_width + word_width >= wrap_width) + { + // Words that cannot possibly fit within an entire line will be cut anywhere. + if (word_width < wrap_width) + s = prev_word_end ? prev_word_end : word_end; + break; + } + + s = next_s; + } + + return s; +} + +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + + const float line_height = size; + const float scale = size / FontSize; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + const char* s = text_begin; + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + if (text_size.x < line_width) + text_size.x = line_width; + text_size.y += line_height; + line_width = 0.0f; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + const char* prev_s = s; + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + continue; + } + if (c == '\r') + continue; + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX) * scale; + if (line_width + char_width >= max_width) + { + s = prev_s; + break; + } + + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (line_width > 0 || text_size.y == 0.0f) + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const +{ + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded. + return; + if (const ImFontGlyph* glyph = FindGlyph(c)) + { + float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + draw_list->PrimReserve(6, 4); + draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); + } +} + +void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // ImGui functions generally already provides a valid text_end, so this is merely to handle direct calls. + + // Align to be pixel perfect + pos.x = (float)(int)pos.x + DisplayOffset.x; + pos.y = (float)(int)pos.y + DisplayOffset.y; + float x = pos.x; + float y = pos.y; + if (y > clip_rect.w) + return; + + const float scale = size / FontSize; + const float line_height = FontSize * scale; + const bool word_wrap_enabled = (wrap_width > 0.0f); + const char* word_wrap_eol = NULL; + + // Skip non-visible lines + const char* s = text_begin; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + + // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) + const int vtx_count_max = (int)(text_end - s) * 4; + const int idx_count_max = (int)(text_end - s) * 6; + const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; + draw_list->PrimReserve(idx_count_max, vtx_count_max); + + ImDrawVert* vtx_write = draw_list->_VtxWritePtr; + ImDrawIdx* idx_write = draw_list->_IdxWritePtr; + unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; + + while (s < text_end) + { + if (word_wrap_enabled) + { + // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. + if (!word_wrap_eol) + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x)); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } + + if (s >= word_wrap_eol) + { + x = pos.x; + y += line_height; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) + { + const char c = *s; + if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } + } + continue; + } + } + + // Decode and advance source + unsigned int c = (unsigned int)*s; + if (c < 0x80) + { + s += 1; + } + else + { + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == 0) // Malformed UTF-8? + break; + } + + if (c < 32) + { + if (c == '\n') + { + x = pos.x; + y += line_height; + + if (y > clip_rect.w) + break; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + continue; + } + if (c == '\r') + continue; + } + + float char_width = 0.0f; + if (const ImFontGlyph* glyph = FindGlyph((unsigned short)c)) + { + char_width = glyph->AdvanceX * scale; + + // Arbitrarily assume that both space and tabs are empty glyphs as an optimization + if (c != ' ' && c != '\t') + { + // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + if (x1 <= clip_rect.z && x2 >= clip_rect.x) + { + // Render a character + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. + if (cpu_fine_clip) + { + if (x1 < clip_rect.x) + { + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; + } + if (y1 < clip_rect.y) + { + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) + { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) + { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; + } + if (y1 >= y2) + { + x += char_width; + continue; + } + } + + // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: + { + idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); + idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); + vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; + vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; + vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; + vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; + vtx_write += 4; + vtx_current_idx += 4; + idx_write += 6; + } + } + } + } + + x += char_width; + } + + // Give back unused vertices + draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data)); + draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data)); + draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); + draw_list->_VtxWritePtr = vtx_write; + draw_list->_IdxWritePtr = idx_write; + draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; +} + +//----------------------------------------------------------------------------- +// Internals Drawing Helpers +//----------------------------------------------------------------------------- + +static inline float ImAcos01(float x) +{ + if (x <= 0.0f) return IM_PI * 0.5f; + if (x >= 1.0f) return 0.0f; + return acosf(x); + //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. +} + +// FIXME: Cleanup and move code to ImDrawList. +void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +{ + if (x_end_norm == x_start_norm) + return; + if (x_start_norm > x_end_norm) + ImSwap(x_start_norm, x_end_norm); + + ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); + ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + if (rounding == 0.0f) + { + draw_list->AddRectFilled(p0, p1, col, 0.0f); + return; + } + + rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); + const float inv_rounding = 1.0f / rounding; + const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); + const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); + const float x0 = ImMax(p0.x, rect.Min.x + rounding); + if (arc0_b == arc0_e) + { + draw_list->PathLineTo(ImVec2(x0, p1.y)); + draw_list->PathLineTo(ImVec2(x0, p0.y)); + } + else if (arc0_b == 0.0f && arc0_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL + draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR + } + else + { + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + } + if (p1.x > rect.Min.x + rounding) + { + const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); + const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); + const float x1 = ImMin(p1.x, rect.Max.x - rounding); + if (arc1_b == arc1_e) + { + draw_list->PathLineTo(ImVec2(x1, p0.y)); + draw_list->PathLineTo(ImVec2(x1, p1.y)); + } + else if (arc1_b == 0.0f && arc1_e == IM_PI*0.5f) + { + draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR + draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR + } + else + { + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + } + } + draw_list->PathFillConvex(col); +} + +//----------------------------------------------------------------------------- +// DEFAULT FONT DATA +//----------------------------------------------------------------------------- +// Compressed with stb_compress() then converted to a C array. +// Use the program in extra_fonts/binary_to_compressed_c.cpp to create the array from a TTF file. +// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h +//----------------------------------------------------------------------------- + +static unsigned int stb_decompress_length(unsigned char *input) +{ + return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; +} + +static unsigned char *stb__barrier, *stb__barrier2, *stb__barrier3, *stb__barrier4; +static unsigned char *stb__dout; +static void stb__match(unsigned char *data, unsigned int length) +{ + // INVERSE of memmove... write each byte before copying the next... + IM_ASSERT (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier4) { stb__dout = stb__barrier+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(unsigned char *data, unsigned int length) +{ + IM_ASSERT (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) +#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) +#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + +static unsigned char *stb_decompress_token(unsigned char *i) +{ + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + } + return i; +} + +static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (unsigned int)(s2 << 16) + (unsigned int)s1; +} + +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length) +{ + unsigned int olen; + if (stb__in4(0) != 0x57bC0000) return 0; + if (stb__in4(4) != 0) return 0; // error! stream is > 4GB + olen = stb_decompress_length(i); + stb__barrier2 = i; + stb__barrier3 = i+length; + stb__barrier = output + olen; + stb__barrier4 = output; + i += 16; + + stb__dout = output; + for (;;) { + unsigned char *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + IM_ASSERT(stb__dout == output + olen); + if (stb__dout != output + olen) return 0; + if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2)) + return 0; + return olen; + } else { + IM_ASSERT(0); /* NOTREACHED */ + return 0; + } + } + IM_ASSERT(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +//----------------------------------------------------------------------------- +// ProggyClean.ttf +// Copyright (c) 2004, 2005 Tristan Grimmer +// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) +// Download and more information at http://upperbounds.net +//----------------------------------------------------------------------------- +// File: 'ProggyClean.ttf' (41208 bytes) +// Exported using binary_to_compressed_c.cpp +//----------------------------------------------------------------------------- +static const char proggy_clean_ttf_compressed_data_base85[11980+1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +static const char* GetDefaultCompressedFontDataTTFBase85() +{ + return proggy_clean_ttf_compressed_data_base85; +} diff --git a/skeleton/imgui/imgui_impl_rw.cpp b/skeleton/imgui/imgui_impl_rw.cpp new file mode 100644 index 0000000..6ea726f --- /dev/null +++ b/skeleton/imgui/imgui_impl_rw.cpp @@ -0,0 +1,219 @@ +#include +#include +#include + +#include "imgui/imgui.h" +#include "imgui_impl_rw.h" + +using namespace rw::RWDEVICE; + +static rw::Texture *g_FontTexture; +static Im2DVertex *g_vertbuf; +static int g_vertbufSize; + +void +ImGui_ImplRW_RenderDrawLists(ImDrawData* draw_data) +{ + ImGuiIO &io = ImGui::GetIO(); + + // minimized + if (io.DisplaySize.x <= 0.0f || io.DisplaySize.y <= 0.0f) + return; + + if(g_vertbuf == nil || g_vertbufSize < draw_data->TotalVtxCount){ + if(g_vertbuf){ + rwFree(g_vertbuf); + g_vertbuf = nil; + } + g_vertbufSize = draw_data->TotalVtxCount + 5000; + g_vertbuf = rwNewT(Im2DVertex, g_vertbufSize, 0); + } + + float xoff = 0.0f; + float yoff = 0.0f; +#ifdef RWHALFPIXEL + xoff = -0.5; + yoff = 0.5; +#endif + + rw::Camera *cam = (rw::Camera*)rw::engine->currentCamera; + Im2DVertex *vtx_dst = g_vertbuf; + float recipZ = 1.0f/cam->nearPlane; + for(int n = 0; n < draw_data->CmdListsCount; n++){ + const ImDrawList *cmd_list = draw_data->CmdLists[n]; + const ImDrawVert *vtx_src = cmd_list->VtxBuffer.Data; + for(int i = 0; i < cmd_list->VtxBuffer.Size; i++){ + vtx_dst[i].setScreenX(vtx_src[i].pos.x + xoff); + vtx_dst[i].setScreenY(vtx_src[i].pos.y + yoff); + vtx_dst[i].setScreenZ(rw::im2d::GetNearZ()); + vtx_dst[i].setCameraZ(cam->nearPlane); + vtx_dst[i].setRecipCameraZ(recipZ); + vtx_dst[i].setColor(vtx_src[i].col&0xFF, vtx_src[i].col>>8 & 0xFF, vtx_src[i].col>>16 & 0xFF, vtx_src[i].col>>24 & 0xFF); + vtx_dst[i].setU(vtx_src[i].uv.x, recipZ); + vtx_dst[i].setV(vtx_src[i].uv.y, recipZ); + } + vtx_dst += cmd_list->VtxBuffer.Size; + } + + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + rw::SetRenderState(rw::ZTESTENABLE, 0); + + int vtx_offset = 0; + for(int n = 0; n < draw_data->CmdListsCount; n++){ + const ImDrawList *cmd_list = draw_data->CmdLists[n]; + int idx_offset = 0; + for(int i = 0; i < cmd_list->CmdBuffer.Size; i++){ + const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[i]; + if(pcmd->UserCallback) + pcmd->UserCallback(cmd_list, pcmd); + else{ + rw::Texture *tex = (rw::Texture*)pcmd->TextureId; + if(tex && tex->raster){ + rw::SetRenderStatePtr(rw::TEXTURERASTER, tex->raster); + rw::SetRenderState(rw::TEXTUREADDRESSU, tex->getAddressU()); + rw::SetRenderState(rw::TEXTUREADDRESSV, tex->getAddressV()); + rw::SetRenderState(rw::TEXTUREFILTER, tex->getFilter()); + }else + rw::SetRenderStatePtr(rw::TEXTURERASTER, nil); + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, + g_vertbuf+vtx_offset, cmd_list->VtxBuffer.Size, + cmd_list->IdxBuffer.Data+idx_offset, pcmd->ElemCount); + } + idx_offset += pcmd->ElemCount; + } + vtx_offset += cmd_list->VtxBuffer.Size; + } +} + +bool +ImGui_ImplRW_Init(void) +{ + using namespace sk; + ImGuiIO &io = ImGui::GetIO(); + + io.KeyMap[ImGuiKey_Tab] = KEY_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = KEY_PGUP; + io.KeyMap[ImGuiKey_PageDown] = KEY_PGDN; + io.KeyMap[ImGuiKey_Home] = KEY_HOME; + io.KeyMap[ImGuiKey_End] = KEY_END; + io.KeyMap[ImGuiKey_Delete] = KEY_DEL; + io.KeyMap[ImGuiKey_Backspace] = KEY_BACKSP; + io.KeyMap[ImGuiKey_Enter] = KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = KEY_ESC; + io.KeyMap[ImGuiKey_A] = 'A'; + io.KeyMap[ImGuiKey_C] = 'C'; + io.KeyMap[ImGuiKey_V] = 'V'; + io.KeyMap[ImGuiKey_X] = 'X'; + io.KeyMap[ImGuiKey_Y] = 'Y'; + io.KeyMap[ImGuiKey_Z] = 'Z'; + + io.RenderDrawListsFn = ImGui_ImplRW_RenderDrawLists; + + return true; +} + +void +ImGui_ImplRW_Shutdown(void) +{ +} + +static bool +ImGui_ImplRW_CreateFontsTexture() +{ + // Build texture atlas + ImGuiIO &io = ImGui::GetIO(); + unsigned char *pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, nil); + + rw::Image *image; + image = rw::Image::create(width, height, 32); + image->allocate(); + for(int y = 0; y < height; y++) + memcpy(image->pixels + image->stride*y, pixels + width*4* y, width*4); + g_FontTexture = rw::Texture::create(rw::Raster::createFromImage(image)); + g_FontTexture->setFilter(rw::Texture::LINEAR); + image->destroy(); + + // Store our identifier + io.Fonts->TexID = (void*)g_FontTexture; + + return true; +} + +bool +ImGui_ImplRW_CreateDeviceObjects() +{ +// if(!g_pd3dDevice) +// return false; + if(!ImGui_ImplRW_CreateFontsTexture()) + return false; + return true; +} + +void +ImGui_ImplRW_NewFrame(float timeDelta) +{ + if(!g_FontTexture) + ImGui_ImplRW_CreateDeviceObjects(); + + ImGuiIO &io = ImGui::GetIO(); + + io.DisplaySize = ImVec2(sk::globals.width, sk::globals.height); + io.DeltaTime = timeDelta; + + io.KeyCtrl = io.KeysDown[sk::KEY_LCTRL] || io.KeysDown[sk::KEY_RCTRL]; + io.KeyShift = io.KeysDown[sk::KEY_LSHIFT] || io.KeysDown[sk::KEY_RSHIFT]; + io.KeyAlt = io.KeysDown[sk::KEY_LALT] || io.KeysDown[sk::KEY_RALT]; + io.KeySuper = false; + + if(io.WantMoveMouse) + sk::SetMousePosition(io.MousePos.x, io.MousePos.y); + + ImGui::NewFrame(); +} + +sk::EventStatus +ImGuiEventHandler(sk::Event e, void *param) +{ + using namespace sk; + + ImGuiIO &io = ImGui::GetIO(); + MouseState *ms; + uint c; + + switch(e){ + case KEYDOWN: + c = *(int*)param; + if(c < 256) + io.KeysDown[c] = 1; + return EVENTPROCESSED; + case KEYUP: + c = *(int*)param; + if(c < 256) + io.KeysDown[c] = 0; + return EVENTPROCESSED; + case CHARINPUT: + c = (uint)(uintptr)param; + io.AddInputCharacter((unsigned short)c); + return EVENTPROCESSED; + case MOUSEMOVE: + ms = (MouseState*)param; + io.MousePos.x = ms->posx; + io.MousePos.y = ms->posy; + return EVENTPROCESSED; + case MOUSEBTN: + ms = (MouseState*)param; + io.MouseDown[0] = !!(ms->buttons & 1); + io.MouseDown[2] = !!(ms->buttons & 2); + io.MouseDown[1] = !!(ms->buttons & 4); + return EVENTPROCESSED; + } + return EVENTPROCESSED; +} diff --git a/skeleton/imgui/imgui_impl_rw.h b/skeleton/imgui/imgui_impl_rw.h new file mode 100644 index 0000000..965a619 --- /dev/null +++ b/skeleton/imgui/imgui_impl_rw.h @@ -0,0 +1,4 @@ +IMGUI_API bool ImGui_ImplRW_Init(void); +IMGUI_API void ImGui_ImplRW_Shutdown(void); +IMGUI_API void ImGui_ImplRW_NewFrame(float timeDelta); +sk::EventStatus ImGuiEventHandler(sk::Event e, void *param); diff --git a/skeleton/imgui/imgui_internal.h b/skeleton/imgui/imgui_internal.h new file mode 100644 index 0000000..797d66d --- /dev/null +++ b/skeleton/imgui/imgui_internal.h @@ -0,0 +1,985 @@ +// dear imgui, v1.54 WIP +// (internals) + +// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! +// Set: +// #define IMGUI_DEFINE_MATH_OPERATORS +// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with your own math types+operators) + +#pragma once + +#ifndef IMGUI_VERSION +#error Must include imgui.h before imgui_internal.h +#endif + +#include // FILE* +#include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + +//----------------------------------------------------------------------------- +// Forward Declarations +//----------------------------------------------------------------------------- + +struct ImRect; +struct ImGuiColMod; +struct ImGuiStyleMod; +struct ImGuiGroupData; +struct ImGuiSimpleColumns; +struct ImGuiDrawContext; +struct ImGuiTextEditState; +struct ImGuiMouseCursorData; +struct ImGuiPopupRef; +struct ImGuiWindow; +struct ImGuiWindowSettings; + +typedef int ImGuiLayoutType; // enum: horizontal or vertical // enum ImGuiLayoutType_ +typedef int ImGuiButtonFlags; // flags: for ButtonEx(), ButtonBehavior() // enum ImGuiButtonFlags_ +typedef int ImGuiItemFlags; // flags: for PushItemFlag() // enum ImGuiItemFlags_ +typedef int ImGuiSeparatorFlags; // flags: for Separator() - internal // enum ImGuiSeparatorFlags_ +typedef int ImGuiSliderFlags; // flags: for SliderBehavior() // enum ImGuiSliderFlags_ + +//------------------------------------------------------------------------- +// STB libraries +//------------------------------------------------------------------------- + +namespace ImGuiStb +{ + +#undef STB_TEXTEDIT_STRING +#undef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_STRING ImGuiTextEditState +#define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f +#include "stb_textedit.h" + +} // namespace ImGuiStb + +//----------------------------------------------------------------------------- +// Context +//----------------------------------------------------------------------------- + +#ifndef GImGui +extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointer +#endif + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +#define IM_PI 3.14159265358979323846f +#define IM_OFFSETOF(_TYPE,_ELM) ((size_t)&(((_TYPE*)0)->_ELM)) + +// Helpers: UTF-8 <> wchar +IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count +IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // return input UTF-8 bytes count +IMGUI_API int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count +IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) +IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string as UTF-8 code-points + +// Helpers: Misc +IMGUI_API ImU32 ImHash(const void* data, int data_size, ImU32 seed = 0); // Pass data_size==0 for zero-terminated strings +IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size = NULL, int padding_bytes = 0); +IMGUI_API FILE* ImFileOpen(const char* filename, const char* file_open_mode); +static inline bool ImCharIsSpace(int c) { return c == ' ' || c == '\t' || c == 0x3000; } +static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } + +// Helpers: Geometry +IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); +IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); +IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); + +// Helpers: String +IMGUI_API int ImStricmp(const char* str1, const char* str2); +IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); +IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); +IMGUI_API char* ImStrdup(const char* str); +IMGUI_API char* ImStrchrRange(const char* str_begin, const char* str_end, char c); +IMGUI_API int ImStrlenW(const ImWchar* str); +IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line +IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); +IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); +IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); + +// Helpers: Math +// We are keeping those not leaking to the user by default, in the case the user has implicit cast operators between ImVec2 and its own types (when IM_VEC2_CLASS_EXTRA is defined) +#ifdef IMGUI_DEFINE_MATH_OPERATORS +static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x*rhs, lhs.y*rhs); } +static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x/rhs, lhs.y/rhs); } +static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); } +static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); } +static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x*rhs.x, lhs.y*rhs.y); } +static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x/rhs.x, lhs.y/rhs.y); } +static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w); } +static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w); } +static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z, lhs.w*rhs.w); } +#endif + +static inline int ImMin(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; } +static inline int ImMax(int lhs, int rhs) { return lhs >= rhs ? lhs : rhs; } +static inline float ImMin(float lhs, float rhs) { return lhs < rhs ? lhs : rhs; } +static inline float ImMax(float lhs, float rhs) { return lhs >= rhs ? lhs : rhs; } +static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(ImMin(lhs.x,rhs.x), ImMin(lhs.y,rhs.y)); } +static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(ImMax(lhs.x,rhs.x), ImMax(lhs.y,rhs.y)); } +static inline int ImClamp(int v, int mn, int mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +static inline float ImClamp(float v, float mn, float mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +static inline ImVec2 ImClamp(const ImVec2& f, const ImVec2& mn, ImVec2 mx) { return ImVec2(ImClamp(f.x,mn.x,mx.x), ImClamp(f.y,mn.y,mx.y)); } +static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +static inline void ImSwap(int& a, int& b) { int tmp = a; a = b; b = tmp; } +static inline void ImSwap(float& a, float& b) { float tmp = a; a = b; b = tmp; } +static inline int ImLerp(int a, int b, float t) { return (int)(a + (b - a) * t); } +static inline float ImLerp(float a, float b, float t) { return a + (b - a) * t; } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +static inline float ImLengthSqr(const ImVec2& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y; } +static inline float ImLengthSqr(const ImVec4& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y + lhs.z*lhs.z + lhs.w*lhs.w; } +static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = lhs.x*lhs.x + lhs.y*lhs.y; if (d > 0.0f) return 1.0f / sqrtf(d); return fail_value; } +static inline float ImFloor(float f) { return (float)(int)f; } +static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)v.x, (float)(int)v.y); } +static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } + +// We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. +// Defining a custom placement new() with a dummy parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. +struct ImNewPlacementDummy {}; +inline void* operator new(size_t, ImNewPlacementDummy, void* ptr) { return ptr; } +inline void operator delete(void*, ImNewPlacementDummy, void*) {} // This is only required so we can use the symetrical new() +#define IM_PLACEMENT_NEW(_PTR) new(ImNewPlacementDummy(), _PTR) +#define IM_NEW(_TYPE) new(ImNewPlacementDummy(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE +template void IM_DELETE(T*& p) { if (p) { p->~T(); ImGui::MemFree(p); p = NULL; } } + +//----------------------------------------------------------------------------- +// Types +//----------------------------------------------------------------------------- + +// Internal Drag and Drop payload types. String starting with '_' are reserved for Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_DOCKABLE "_IMDOCK" // ImGuiWindow* // [Internal] Docking/tabs + +enum ImGuiButtonFlags_ +{ + ImGuiButtonFlags_Repeat = 1 << 0, // hold to repeat + ImGuiButtonFlags_PressedOnClickRelease = 1 << 1, // return true on click + release on same item [DEFAULT if no PressedOn* flag is set] + ImGuiButtonFlags_PressedOnClick = 1 << 2, // return true on click (default requires click+release) + ImGuiButtonFlags_PressedOnRelease = 1 << 3, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnDoubleClick = 1 << 4, // return true on double-click (default requires click+release) + ImGuiButtonFlags_FlattenChildren = 1 << 5, // allow interactions even if a child window is overlapping + ImGuiButtonFlags_AllowItemOverlap = 1 << 6, // require previous frame HoveredId to either match id or be null before being usable, use along with SetItemAllowOverlap() + ImGuiButtonFlags_DontClosePopups = 1 << 7, // disable automatically closing parent popup on press // [UNUSED] + ImGuiButtonFlags_Disabled = 1 << 8, // disable interactions + ImGuiButtonFlags_AlignTextBaseLine = 1 << 9, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine + ImGuiButtonFlags_NoKeyModifiers = 1 << 10, // disable interaction if a key modifier is held + ImGuiButtonFlags_NoHoldingActiveID = 1 << 11, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) + ImGuiButtonFlags_PressedOnDragDropHold = 1 << 12 // press when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) +}; + +enum ImGuiSliderFlags_ +{ + ImGuiSliderFlags_Vertical = 1 << 0 +}; + +enum ImGuiColumnsFlags_ +{ + // Default: 0 + ImGuiColumnsFlags_NoBorder = 1 << 0, // Disable column dividers + ImGuiColumnsFlags_NoResize = 1 << 1, // Disable resizing columns when clicking on the dividers + ImGuiColumnsFlags_NoPreserveWidths = 1 << 2, // Disable column width preservation when adjusting columns + ImGuiColumnsFlags_NoForceWithinWindow = 1 << 3, // Disable forcing columns to fit within window + ImGuiColumnsFlags_GrowParentContentsSize= 1 << 4 // (WIP) Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove. +}; + +enum ImGuiSelectableFlagsPrivate_ +{ + // NB: need to be in sync with last value of ImGuiSelectableFlags_ + ImGuiSelectableFlags_Menu = 1 << 3, // -> PressedOnClick + ImGuiSelectableFlags_MenuItem = 1 << 4, // -> PressedOnRelease + ImGuiSelectableFlags_Disabled = 1 << 5, + ImGuiSelectableFlags_DrawFillAvailWidth = 1 << 6 +}; + +enum ImGuiSeparatorFlags_ +{ + ImGuiSeparatorFlags_Horizontal = 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar + ImGuiSeparatorFlags_Vertical = 1 << 1 +}; + +// FIXME: this is in development, not exposed/functional as a generic feature yet. +enum ImGuiLayoutType_ +{ + ImGuiLayoutType_Vertical, + ImGuiLayoutType_Horizontal +}; + +enum ImGuiAxis +{ + ImGuiAxis_None = -1, + ImGuiAxis_X = 0, + ImGuiAxis_Y = 1 +}; + +enum ImGuiPlotType +{ + ImGuiPlotType_Lines, + ImGuiPlotType_Histogram +}; + +enum ImGuiDataType +{ + ImGuiDataType_Int, + ImGuiDataType_Float, + ImGuiDataType_Float2 +}; + +enum ImGuiDir +{ + ImGuiDir_None = -1, + ImGuiDir_Left = 0, + ImGuiDir_Right = 1, + ImGuiDir_Up = 2, + ImGuiDir_Down = 3, + ImGuiDir_Count_ +}; + +// 2D axis aligned bounding-box +// NB: we can't rely on ImVec2 math operators being available here +struct IMGUI_API ImRect +{ + ImVec2 Min; // Upper-left + ImVec2 Max; // Lower-right + + ImRect() : Min(FLT_MAX,FLT_MAX), Max(-FLT_MAX,-FLT_MAX) {} + ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} + ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} + ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + + ImVec2 GetCenter() const { return ImVec2((Min.x+Max.x)*0.5f, (Min.y+Max.y)*0.5f); } + ImVec2 GetSize() const { return ImVec2(Max.x-Min.x, Max.y-Min.y); } + float GetWidth() const { return Max.x-Min.x; } + float GetHeight() const { return Max.y-Min.y; } + ImVec2 GetTL() const { return Min; } // Top-left + ImVec2 GetTR() const { return ImVec2(Max.x, Min.y); } // Top-right + ImVec2 GetBL() const { return ImVec2(Min.x, Max.y); } // Bottom-left + ImVec2 GetBR() const { return Max; } // Bottom-right + bool Contains(const ImVec2& p) const { return p.x >= Min.x && p.y >= Min.y && p.x < Max.x && p.y < Max.y; } + bool Contains(const ImRect& r) const { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x < Max.x && r.Max.y < Max.y; } + bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; } + void Add(const ImVec2& rhs) { if (Min.x > rhs.x) Min.x = rhs.x; if (Min.y > rhs.y) Min.y = rhs.y; if (Max.x < rhs.x) Max.x = rhs.x; if (Max.y < rhs.y) Max.y = rhs.y; } + void Add(const ImRect& rhs) { if (Min.x > rhs.Min.x) Min.x = rhs.Min.x; if (Min.y > rhs.Min.y) Min.y = rhs.Min.y; if (Max.x < rhs.Max.x) Max.x = rhs.Max.x; if (Max.y < rhs.Max.y) Max.y = rhs.Max.y; } + void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; } + void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; } + void Translate(const ImVec2& v) { Min.x += v.x; Min.y += v.y; Max.x += v.x; Max.y += v.y; } + void ClipWith(const ImRect& clip) { if (Min.x < clip.Min.x) Min.x = clip.Min.x; if (Min.y < clip.Min.y) Min.y = clip.Min.y; if (Max.x > clip.Max.x) Max.x = clip.Max.x; if (Max.y > clip.Max.y) Max.y = clip.Max.y; } + void Floor() { Min.x = (float)(int)Min.x; Min.y = (float)(int)Min.y; Max.x = (float)(int)Max.x; Max.y = (float)(int)Max.y; } + void FixInverted() { if (Min.x > Max.x) ImSwap(Min.x, Max.x); if (Min.y > Max.y) ImSwap(Min.y, Max.y); } + bool IsFinite() const { return Min.x != FLT_MAX; } + ImVec2 GetClosestPoint(ImVec2 p, bool on_edge) const + { + if (!on_edge && Contains(p)) + return p; + if (p.x > Max.x) p.x = Max.x; + else if (p.x < Min.x) p.x = Min.x; + if (p.y > Max.y) p.y = Max.y; + else if (p.y < Min.y) p.y = Min.y; + return p; + } +}; + +// Stacked color modifier, backup of modified data so we can restore it +struct ImGuiColMod +{ + ImGuiCol Col; + ImVec4 BackupValue; +}; + +// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. +struct ImGuiStyleMod +{ + ImGuiStyleVar VarIdx; + union { int BackupInt[2]; float BackupFloat[2]; }; + ImGuiStyleMod(ImGuiStyleVar idx, int v) { VarIdx = idx; BackupInt[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } +}; + +// Stacked data for BeginGroup()/EndGroup() +struct ImGuiGroupData +{ + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + float BackupIndentX; + float BackupGroupOffsetX; + float BackupCurrentLineHeight; + float BackupCurrentLineTextBaseOffset; + float BackupLogLinePosY; + bool BackupActiveIdIsAlive; + bool AdvanceCursor; +}; + +// Simple column measurement currently used for MenuItem() only. This is very short-sighted/throw-away code and NOT a generic helper. +struct IMGUI_API ImGuiSimpleColumns +{ + int Count; + float Spacing; + float Width, NextWidth; + float Pos[8], NextWidths[8]; + + ImGuiSimpleColumns(); + void Update(int count, float spacing, bool clear); + float DeclColumns(float w0, float w1, float w2); + float CalcExtraSpace(float avail_w); +}; + +// Internal state of the currently focused/edited text input box +struct IMGUI_API ImGuiTextEditState +{ + ImGuiID Id; // widget id owning the text state + ImVector Text; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector TempTextBuffer; + int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. + int BufSizeA; // end-user buffer size + float ScrollX; + ImGuiStb::STB_TexteditState StbState; + float CursorAnim; + bool CursorFollow; + bool SelectedAllMouseLock; + + ImGuiTextEditState() { memset(this, 0, sizeof(*this)); } + void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking + void CursorClamp() { StbState.cursor = ImMin(StbState.cursor, CurLenW); StbState.select_start = ImMin(StbState.select_start, CurLenW); StbState.select_end = ImMin(StbState.select_end, CurLenW); } + bool HasSelection() const { return StbState.select_start != StbState.select_end; } + void ClearSelection() { StbState.select_start = StbState.select_end = StbState.cursor; } + void SelectAll() { StbState.select_start = 0; StbState.select_end = CurLenW; StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } + void OnKeyPressed(int key); +}; + +// Data saved in imgui.ini file +struct ImGuiWindowSettings +{ + char* Name; + ImGuiID Id; + ImVec2 Pos; + ImVec2 Size; + bool Collapsed; + + ImGuiWindowSettings() { Name = NULL; Id = 0; Pos = Size = ImVec2(0,0); Collapsed = false; } +}; + +struct ImGuiSettingsHandler +{ + const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' + ImGuiID TypeHash; // == ImHash(TypeName, 0, 0) + void* (*ReadOpenFn)(ImGuiContext& ctx, const char* name); + void (*ReadLineFn)(ImGuiContext& ctx, void* entry, const char* line); + void (*WriteAllFn)(ImGuiContext& ctx, ImGuiTextBuffer* out_buf); +}; + +// Mouse cursor data (used when io.MouseDrawCursor is set) +struct ImGuiMouseCursorData +{ + ImGuiMouseCursor Type; + ImVec2 HotOffset; + ImVec2 Size; + ImVec2 TexUvMin[2]; + ImVec2 TexUvMax[2]; +}; + +// Storage for current popup stack +struct ImGuiPopupRef +{ + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* ParentWindow; // Set on OpenPopup() + ImGuiID ParentMenuSet; // Set on OpenPopup() + ImVec2 MousePosOnOpen; // Copy of mouse position at the time of opening popup + + ImGuiPopupRef(ImGuiID id, ImGuiWindow* parent_window, ImGuiID parent_menu_set, const ImVec2& mouse_pos) { PopupId = id; Window = NULL; ParentWindow = parent_window; ParentMenuSet = parent_menu_set; MousePosOnOpen = mouse_pos; } +}; + +struct ImGuiColumnData +{ + float OffsetNorm; // Column start offset, normalized 0.0 (far left) -> 1.0 (far right) + float OffsetNormBeforeResize; + ImGuiColumnsFlags Flags; // Not exposed + ImRect ClipRect; + + ImGuiColumnData() { OffsetNorm = OffsetNormBeforeResize = 0.0f; Flags = 0; } +}; + +struct ImGuiColumnsSet +{ + ImGuiID ID; + ImGuiColumnsFlags Flags; + bool IsFirstFrame; + bool IsBeingResized; + int Current; + int Count; + float MinX, MaxX; + float StartPosY; + float StartMaxPosX; // Backup of CursorMaxPos + float CellMinY, CellMaxY; + ImVector Columns; + + ImGuiColumnsSet() { Clear(); } + void Clear() + { + ID = 0; + Flags = 0; + IsFirstFrame = false; + IsBeingResized = false; + Current = 0; + Count = 1; + MinX = MaxX = 0.0f; + StartPosY = 0.0f; + StartMaxPosX = 0.0f; + CellMinY = CellMaxY = 0.0f; + Columns.clear(); + } +}; + +struct ImDrawListSharedData +{ + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas + ImFont* Font; // Current/default font (optional, for simplified AddText overload) + float FontSize; // Current/default font size (optional, for simplified AddText overload) + float CurveTessellationTol; + ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + + // Const data + // FIXME: Bake rounded corners fill/borders in atlas + ImVec2 CircleVtx12[12]; + + ImDrawListSharedData(); +}; + +// Main state for ImGui +struct ImGuiContext +{ + bool Initialized; + ImGuiIO IO; + ImGuiStyle Style; + ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() + float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. + float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + ImDrawListSharedData DrawListSharedData; + + float Time; + int FrameCount; + int FrameCountEnded; + int FrameCountRendered; + ImVector Windows; + ImVector WindowsSortBuffer; + ImVector CurrentWindowStack; + ImGuiStorage WindowsById; + int WindowsActiveCount; + ImGuiWindow* CurrentWindow; // Being drawn into + ImGuiWindow* NavWindow; // Nav/focused window for navigation + ImGuiWindow* HoveredWindow; // Will catch mouse inputs + ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only) + ImGuiID HoveredId; // Hovered widget + bool HoveredIdAllowOverlap; + ImGuiID HoveredIdPreviousFrame; + float HoveredIdTimer; + ImGuiID ActiveId; // Active widget + ImGuiID ActiveIdPreviousFrame; + float ActiveIdTimer; + bool ActiveIdIsAlive; // Active widget has been seen this frame + bool ActiveIdIsJustActivated; // Set at the time of activation for one frame + bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) + ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) + ImGuiWindow* ActiveIdWindow; + ImGuiWindow* MovingWindow; // Track the child window we clicked on to move a window. + ImGuiID MovingWindowMoveId; // == MovingWindow->MoveId + ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() + ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() + ImVector FontStack; // Stack for PushFont()/PopFont() + ImVector OpenPopupStack; // Which popups are open (persistent) + ImVector CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame) + + // Storage for SetNexWindow** and SetNextTreeNode*** functions + ImVec2 SetNextWindowPosVal; + ImVec2 SetNextWindowPosPivot; + ImVec2 SetNextWindowSizeVal; + ImVec2 SetNextWindowContentSizeVal; + bool SetNextWindowCollapsedVal; + ImGuiCond SetNextWindowPosCond; + ImGuiCond SetNextWindowSizeCond; + ImGuiCond SetNextWindowContentSizeCond; + ImGuiCond SetNextWindowCollapsedCond; + ImRect SetNextWindowSizeConstraintRect; // Valid if 'SetNextWindowSizeConstraint' is true + ImGuiSizeConstraintCallback SetNextWindowSizeConstraintCallback; + void* SetNextWindowSizeConstraintCallbackUserData; + bool SetNextWindowSizeConstraint; + bool SetNextWindowFocus; + bool SetNextTreeNodeOpenVal; + ImGuiCond SetNextTreeNodeOpenCond; + + // Render + ImDrawData RenderDrawData; // Main ImDrawData instance to pass render information to the user + ImVector RenderDrawLists[3]; + float ModalWindowDarkeningRatio; + ImDrawList OverlayDrawList; // Optional software render of mouse cursors, if io.MouseDrawCursor is set + a few debug overlays + ImGuiMouseCursor MouseCursor; + ImGuiMouseCursorData MouseCursorData[ImGuiMouseCursor_Count_]; + + // Drag and Drop + bool DragDropActive; + ImGuiDragDropFlags DragDropSourceFlags; + int DragDropMouseButton; + ImGuiPayload DragDropPayload; + ImRect DragDropTargetRect; + ImGuiID DragDropTargetId; + float DragDropAcceptIdCurrRectSurface; + ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) + ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets) + int DragDropAcceptFrameCount; // Last time a target expressed a desire to accept the source + ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly + unsigned char DragDropPayloadBufLocal[8]; + + // Widget state + ImGuiTextEditState InputTextState; + ImFont InputTextPasswordFont; + ImGuiID ScalarAsInputTextId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets + ImVec4 ColorPickerRef; + float DragCurrentValue; // Currently dragged value, always float, not rounded by end-user precision settings + ImVec2 DragLastMouseDelta; + float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio + float DragSpeedScaleSlow; + float DragSpeedScaleFast; + ImVec2 ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? + int TooltipOverrideCount; + ImVector PrivateClipboard; // If no custom clipboard handler is defined + ImVec2 OsImePosRequest, OsImePosSet; // Cursor position request & last passed to the OS Input Method Editor + + // Settings + float SettingsDirtyTimer; // Save .ini Settings on disk when time reaches zero + ImVector SettingsWindows; // .ini settings for ImGuiWindow + ImVector SettingsHandlers; // List of .ini settings handlers + + // Logging + bool LogEnabled; + FILE* LogFile; // If != NULL log to stdout/ file + ImGuiTextBuffer* LogClipboard; // Else log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. + int LogStartDepth; + int LogAutoExpandMaxDepth; + + // Misc + float FramerateSecPerFrame[120]; // calculate estimate of framerate for user + int FramerateSecPerFrameIdx; + float FramerateSecPerFrameAccum; + int WantCaptureMouseNextFrame; // explicit capture via CaptureInputs() sets those flags + int WantCaptureKeyboardNextFrame; + int WantTextInputNextFrame; + char TempBuffer[1024*3+1]; // temporary text buffer + + ImGuiContext() : OverlayDrawList(NULL) + { + Initialized = false; + Font = NULL; + FontSize = FontBaseSize = 0.0f; + + Time = 0.0f; + FrameCount = 0; + FrameCountEnded = FrameCountRendered = -1; + WindowsActiveCount = 0; + CurrentWindow = NULL; + NavWindow = NULL; + HoveredWindow = NULL; + HoveredRootWindow = NULL; + HoveredId = 0; + HoveredIdAllowOverlap = false; + HoveredIdPreviousFrame = 0; + HoveredIdTimer = 0.0f; + ActiveId = 0; + ActiveIdPreviousFrame = 0; + ActiveIdTimer = 0.0f; + ActiveIdIsAlive = false; + ActiveIdIsJustActivated = false; + ActiveIdAllowOverlap = false; + ActiveIdClickOffset = ImVec2(-1,-1); + ActiveIdWindow = NULL; + MovingWindow = NULL; + MovingWindowMoveId = 0; + + SetNextWindowPosVal = ImVec2(0.0f, 0.0f); + SetNextWindowSizeVal = ImVec2(0.0f, 0.0f); + SetNextWindowCollapsedVal = false; + SetNextWindowPosCond = 0; + SetNextWindowSizeCond = 0; + SetNextWindowContentSizeCond = 0; + SetNextWindowCollapsedCond = 0; + SetNextWindowSizeConstraintRect = ImRect(); + SetNextWindowSizeConstraintCallback = NULL; + SetNextWindowSizeConstraintCallbackUserData = NULL; + SetNextWindowSizeConstraint = false; + SetNextWindowFocus = false; + SetNextTreeNodeOpenVal = false; + SetNextTreeNodeOpenCond = 0; + + DragDropActive = false; + DragDropSourceFlags = 0; + DragDropMouseButton = -1; + DragDropTargetId = 0; + DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; + DragDropAcceptFrameCount = -1; + memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + + ScalarAsInputTextId = 0; + ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; + DragCurrentValue = 0.0f; + DragLastMouseDelta = ImVec2(0.0f, 0.0f); + DragSpeedDefaultRatio = 1.0f / 100.0f; + DragSpeedScaleSlow = 1.0f / 100.0f; + DragSpeedScaleFast = 10.0f; + ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f); + TooltipOverrideCount = 0; + OsImePosRequest = OsImePosSet = ImVec2(-1.0f, -1.0f); + + ModalWindowDarkeningRatio = 0.0f; + OverlayDrawList._Data = &DrawListSharedData; + OverlayDrawList._OwnerName = "##Overlay"; // Give it a name for debugging + MouseCursor = ImGuiMouseCursor_Arrow; + memset(MouseCursorData, 0, sizeof(MouseCursorData)); + + SettingsDirtyTimer = 0.0f; + + LogEnabled = false; + LogFile = NULL; + LogClipboard = NULL; + LogStartDepth = 0; + LogAutoExpandMaxDepth = 2; + + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); + FramerateSecPerFrameIdx = 0; + FramerateSecPerFrameAccum = 0.0f; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempBuffer, 0, sizeof(TempBuffer)); + } +}; + +// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // FIXME-WIP: Disable interactions but doesn't affect visuals. Should be: grey out and disable interactions with widgets that affect data + view widgets (WIP) + //ImGuiItemFlags_NoNav = 1 << 3, // false + //ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window + ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus +}; + +// Transient per-window data, reset at the beginning of the frame +// FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiDrawContext is quite tenuous and could be reconsidered. +struct IMGUI_API ImGuiDrawContext +{ + ImVec2 CursorPos; + ImVec2 CursorPosPrevLine; + ImVec2 CursorStartPos; + ImVec2 CursorMaxPos; // Used to implicitly calculate the size of our contents, always growing during the frame. Turned into window->SizeContents at the beginning of next frame + float CurrentLineHeight; + float CurrentLineTextBaseOffset; + float PrevLineHeight; + float PrevLineTextBaseOffset; + float LogLinePosY; + int TreeDepth; + ImGuiID LastItemId; + ImRect LastItemRect; + bool LastItemRectHoveredRect; + bool MenuBarAppending; + float MenuBarOffsetX; + ImVector ChildWindows; + ImGuiStorage* StateStorage; + ImGuiLayoutType LayoutType; + + // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. + ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default] + float ItemWidth; // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window + float TextWrapPos; // == TextWrapPosStack.back() [empty == -1.0f] + ImVectorItemFlagsStack; + ImVector ItemWidthStack; + ImVector TextWrapPosStack; + ImVectorGroupStack; + int StackSizesBackup[6]; // Store size of various stacks for asserting + + float IndentX; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) + float GroupOffsetX; + float ColumnsOffsetX; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. + ImGuiColumnsSet* ColumnsSet; // Current columns set + + ImGuiDrawContext() + { + CursorPos = CursorPosPrevLine = CursorStartPos = CursorMaxPos = ImVec2(0.0f, 0.0f); + CurrentLineHeight = PrevLineHeight = 0.0f; + CurrentLineTextBaseOffset = PrevLineTextBaseOffset = 0.0f; + LogLinePosY = -1.0f; + TreeDepth = 0; + LastItemId = 0; + LastItemRect = ImRect(); + LastItemRectHoveredRect = false; + MenuBarAppending = false; + MenuBarOffsetX = 0.0f; + StateStorage = NULL; + LayoutType = ImGuiLayoutType_Vertical; + ItemWidth = 0.0f; + ItemFlags = ImGuiItemFlags_Default_; + TextWrapPos = -1.0f; + memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); + + IndentX = 0.0f; + GroupOffsetX = 0.0f; + ColumnsOffsetX = 0.0f; + ColumnsSet = NULL; + } +}; + +// Windows data +struct IMGUI_API ImGuiWindow +{ + char* Name; + ImGuiID ID; // == ImHash(Name) + ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImVec2 PosFloat; + ImVec2 Pos; // Position rounded-up to nearest pixel + ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) + ImVec2 SizeFull; // Size when non collapsed + ImVec2 SizeFullAtLastBegin; // Copy of SizeFull at the end of Begin. This is the reference value we'll use on the next frame to decide if we need scrollbars. + ImVec2 SizeContents; // Size of contents (== extents reach of the drawing cursor) from previous frame. Include decoration, window title, border, menu, etc. + ImVec2 SizeContentsExplicit; // Size of contents explicitly set by the user via SetNextWindowContentSize() + ImRect ContentsRegionRect; // Maximum visible content position in window coordinates. ~~ (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, per axis + ImVec2 WindowPadding; // Window padding at the time of begin. + float WindowRounding; // Window rounding at the time of begin. + float WindowBorderSize; // Window border size at the time of begin. + ImGuiID MoveId; // == window->GetID("#MOVE") + ImVec2 Scroll; + ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change) + ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered + bool ScrollbarX, ScrollbarY; + ImVec2 ScrollbarSizes; + bool Active; // Set to true on Begin() + bool WasActive; + bool WriteAccessed; // Set to true when any widget access the current window + bool Collapsed; // Set when collapsing window to become only title-bar + bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool Appearing; // Set during the frame where the window is appearing (or re-appearing) + bool CloseButton; // Set when the window has a close button (p_open != NULL) + int BeginOrderWithinParent; // Order within immediate parent window, if we are a child window. Otherwise 0. + int BeginOrderWithinContext; // Order within entire imgui context. This is mostly used for debugging submission order related issues. + int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) + int AutoFitFramesX, AutoFitFramesY; + bool AutoFitOnlyGrows; + int AutoFitChildAxises; + ImGuiDir AutoPosLastDirection; + int HiddenFrames; + ImGuiCond SetWindowPosAllowFlags; // store condition flags for next SetWindowPos() call. + ImGuiCond SetWindowSizeAllowFlags; // store condition flags for next SetWindowSize() call. + ImGuiCond SetWindowCollapsedAllowFlags; // store condition flags for next SetWindowCollapsed() call. + ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) + ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. + + ImGuiDrawContext DC; // Temporary per-window data, reset at the beginning of the frame + ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack + ImRect ClipRect; // = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. x1, y1, x2, y2. + ImRect WindowRectClipped; // = WindowRect just after setup in Begin(). == window->Rect() for root window. + ImRect InnerRect; + int LastFrameActive; + float ItemWidthDefault; + ImGuiSimpleColumns MenuColumns; // Simplified columns storage for menu items + ImGuiStorage StateStorage; + ImVector ColumnsStorage; + float FontWindowScale; // Scale multiplier per-window + ImDrawList* DrawList; + ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. + ImGuiWindow* RootWindow; // Generally point to ourself. If we are a child window, this is pointing to the first non-child parent window. + ImGuiWindow* RootNonPopupWindow; // Generally point to ourself. Used to display TitleBgActive color and for selecting which window to use for NavWindowing + + // Navigation / Focus + int FocusIdxAllCounter; // Start at -1 and increase as assigned via FocusItemRegister() + int FocusIdxTabCounter; // (same, but only count widgets which you can Tab through) + int FocusIdxAllRequestCurrent; // Item being requested for focus + int FocusIdxTabRequestCurrent; // Tab-able item being requested for focus + int FocusIdxAllRequestNext; // Item being requested for focus, for next update (relies on layout to be stable between the frame pressing TAB and the next frame) + int FocusIdxTabRequestNext; // " + +public: + ImGuiWindow(ImGuiContext* context, const char* name); + ~ImGuiWindow(); + + ImGuiID GetID(const char* str, const char* str_end = NULL); + ImGuiID GetID(const void* ptr); + ImGuiID GetIDNoKeepAlive(const char* str, const char* str_end = NULL); + ImGuiID GetIDFromRectangle(const ImRect& r_abs); + + // We don't use g.FontSize because the window may be != g.CurrentWidow. + ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } + float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale; } + float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; } + ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } + float MenuBarHeight() const { return (Flags & ImGuiWindowFlags_MenuBar) ? CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f : 0.0f; } + ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } +}; + +// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data. +struct ImGuiItemHoveredDataBackup +{ + ImGuiID LastItemId; + ImRect LastItemRect; + bool LastItemRectHoveredRect; + + ImGuiItemHoveredDataBackup() { Backup(); } + void Backup() { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemRect = window->DC.LastItemRect; LastItemRectHoveredRect = window->DC.LastItemRectHoveredRect; } + void Restore() const { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemRect = LastItemRect; window->DC.LastItemRectHoveredRect = LastItemRectHoveredRect; } +}; + +//----------------------------------------------------------------------------- +// Internal API +// No guarantee of forward compatibility here. +//----------------------------------------------------------------------------- + +namespace ImGui +{ + // We should always have a CurrentWindow in the stack (there is an implicit "Debug" window) + // If this ever crash because g.CurrentWindow is NULL it means that either + // - ImGui::NewFrame() has never been called, which is illegal. + // - You are calling ImGui functions after ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } + inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } + IMGUI_API ImGuiWindow* FindWindowByName(const char* name); + IMGUI_API void FocusWindow(ImGuiWindow* window); + IMGUI_API void BringWindowToFront(ImGuiWindow* window); + IMGUI_API void BringWindowToBack(ImGuiWindow* window); + IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); + + IMGUI_API void Initialize(); + + IMGUI_API void MarkIniSettingsDirty(); + IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(ImGuiID type_id); + IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); + + IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); + IMGUI_API void ClearActiveID(); + IMGUI_API void SetHoveredID(ImGuiID id); + IMGUI_API void KeepAliveID(ImGuiID id); + + IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f); + IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f); + IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id); + IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); + IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); + IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true); // Return true if focus is requested + IMGUI_API void FocusableItemUnregister(ImGuiWindow* window); + IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_x, float default_y); + IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); + IMGUI_API void PushMultiItemsWidths(int components, float width_full = 0.0f); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); + IMGUI_API void PopItemFlag(); + + IMGUI_API void OpenPopupEx(ImGuiID id, bool reopen_existing); + IMGUI_API void ClosePopup(ImGuiID id); + IMGUI_API bool IsPopupOpen(ImGuiID id); + IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); + IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true); + + IMGUI_API int CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate); + + IMGUI_API void Scrollbar(ImGuiLayoutType direction); + IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). not exposed because it is misleading what it doesn't have an effect on regular layout. + IMGUI_API bool SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f); + + IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); + IMGUI_API void ClearDragDrop(); + IMGUI_API bool IsDragDropPayloadBeingAccepted(); + + // FIXME-WIP: New Columns API + IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). + IMGUI_API void EndColumns(); // close columns + IMGUI_API void PushColumnClipRect(int column_index = -1); + + // NB: All position are in absolute pixels coordinates (never using window coordinates internally) + // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. + IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); + IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0,0), const ImRect* clip_rect = NULL); + IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); + IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0); + IMGUI_API void RenderTriangle(ImVec2 pos, ImGuiDir dir, float scale = 1.0f); + IMGUI_API void RenderBullet(ImVec2 pos); + IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz); + IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. + + IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); + IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0); + IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos, float radius); + IMGUI_API bool ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags = 0); + + IMGUI_API bool SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags = 0); + IMGUI_API bool SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power); + IMGUI_API bool SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format); + + IMGUI_API bool DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power); + IMGUI_API bool DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power); + IMGUI_API bool DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format); + + IMGUI_API bool InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags); + IMGUI_API bool InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision); + + IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); + IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); + + IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); + IMGUI_API bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging + IMGUI_API void TreePushRawID(ImGuiID id); + + IMGUI_API void PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size); + + IMGUI_API int ParseFormatPrecision(const char* fmt, int default_value); + IMGUI_API float RoundScalar(float value, int decimal_precision); + + // Shade functions + IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); + IMGUI_API void ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x); + IMGUI_API void ShadeVertsLinearUV(ImDrawVert* vert_start, ImDrawVert* vert_end, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); + +} // namespace ImGui + +// ImFontAtlas internals +IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); +IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc); +IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); +IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning (pop) +#endif diff --git a/skeleton/imgui/stb_rect_pack.h b/skeleton/imgui/stb_rect_pack.h new file mode 100644 index 0000000..c75527d --- /dev/null +++ b/skeleton/imgui/stb_rect_pack.h @@ -0,0 +1,583 @@ +// stb_rect_pack.h - v0.10 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// Bugfixes / warning fixes +// Jeremy Jaussaud +// +// Version history: +// +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF void stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + stbrp_node *L1 = NULL, *L2 = NULL; + int count=0; + cur = context->active_head; + while (cur) { + L1 = cur; + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + L2 = cur; + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_width_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->w > q->w) + return -1; + if (p->w < q->w) + return 1; + return (p->h > q->h) ? -1 : (p->h < q->h); +} + +static int rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags + for (i=0; i < num_rects; ++i) + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); +} +#endif diff --git a/skeleton/imgui/stb_textedit.h b/skeleton/imgui/stb_textedit.h new file mode 100644 index 0000000..4b731a0 --- /dev/null +++ b/skeleton/imgui/stb_textedit.h @@ -0,0 +1,1322 @@ +// [ImGui] this is a slightly modified version of stb_truetype.h 1.9. Those changes would need to be pushed into nothings/sb +// [ImGui] - fixed linestart handler when over last character of multi-line buffer + simplified existing code (#588, #815) +// [ImGui] - fixed a state corruption/crash bug in stb_text_redo and stb_textedit_discard_redo (#715) +// [ImGui] - fixed a crash bug in stb_textedit_discard_redo (#681) +// [ImGui] - fixed some minor warnings + +// stb_textedit.h - v1.9 - public domain - Sean Barrett +// Development of this library was sponsored by RAD Game Tools +// +// This C header file implements the guts of a multi-line text-editing +// widget; you implement display, word-wrapping, and low-level string +// insertion/deletion, and stb_textedit will map user inputs into +// insertions & deletions, plus updates to the cursor position, +// selection state, and undo state. +// +// It is intended for use in games and other systems that need to build +// their own custom widgets and which do not have heavy text-editing +// requirements (this library is not recommended for use for editing large +// texts, as its performance does not scale and it has limited undo). +// +// Non-trivial behaviors are modelled after Windows text controls. +// +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// +// DEPENDENCIES +// +// Uses the C runtime function 'memmove', which you can override +// by defining STB_TEXTEDIT_memmove before the implementation. +// Uses no other functions. Performs no runtime allocations. +// +// +// VERSION HISTORY +// +// 1.9 (2016-08-27) customizable move-by-word +// 1.8 (2016-04-02) better keyboard handling when mouse button is down +// 1.7 (2015-09-13) change y range handling in case baseline is non-0 +// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove +// 1.5 (2014-09-10) add support for secondary keys for OS X +// 1.4 (2014-08-17) fix signed/unsigned warnings +// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary +// 1.2 (2014-05-27) fix some RAD types that had crept into the new code +// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) +// 1.0 (2012-07-26) improve documentation, initial public release +// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode +// 0.2 (2011-11-28) fixes to undo/redo +// 0.1 (2010-07-08) initial version +// +// ADDITIONAL CONTRIBUTORS +// +// Ulf Winklemann: move-by-word in 1.1 +// Fabian Giesen: secondary key inputs in 1.5 +// Martins Mozeiko: STB_TEXTEDIT_memmove +// +// Bugfixes: +// Scott Graham +// Daniel Keller +// Omar Cornut +// +// USAGE +// +// This file behaves differently depending on what symbols you define +// before including it. +// +// +// Header-file mode: +// +// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, +// it will operate in "header file" mode. In this mode, it declares a +// single public symbol, STB_TexteditState, which encapsulates the current +// state of a text widget (except for the string, which you will store +// separately). +// +// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a +// primitive type that defines a single character (e.g. char, wchar_t, etc). +// +// To save space or increase undo-ability, you can optionally define the +// following things that are used by the undo system: +// +// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// If you don't define these, they are set to permissive types and +// moderate sizes. The undo system does no memory allocations, so +// it grows STB_TexteditState by the worst-case storage which is (in bytes): +// +// [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT +// +// +// Implementation mode: +// +// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it +// will compile the implementation of the text edit widget, depending +// on a large number of symbols which must be defined before the include. +// +// The implementation is defined only as static functions. You will then +// need to provide your own APIs in the same file which will access the +// static functions. +// +// The basic concept is that you provide a "string" object which +// behaves like an array of characters. stb_textedit uses indices to +// refer to positions in the string, implicitly representing positions +// in the displayed textedit. This is true for both plain text and +// rich text; even with rich text stb_truetype interacts with your +// code as if there was an array of all the displayed characters. +// +// Symbols that must be the same in header-file and implementation mode: +// +// STB_TEXTEDIT_CHARTYPE the character type +// STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// Symbols you must define for implementation mode: +// +// STB_TEXTEDIT_STRING the type of object representing a string being edited, +// typically this is a wrapper object with other data you need +// +// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) +// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters +// starting from character #n (see discussion below) +// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character +// to the xpos of the i+1'th char for a line of characters +// starting at character #n (i.e. accounts for kerning +// with previous char) +// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character +// (return type is int, -1 means not valid to insert) +// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based +// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize +// as manually wordwrapping for end-of-line positioning +// +// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// +// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key +// +// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left +// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right +// STB_TEXTEDIT_K_UP keyboard input to move cursor up +// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME +// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END +// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME +// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END +// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor +// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor +// STB_TEXTEDIT_K_UNDO keyboard input to perform undo +// STB_TEXTEDIT_K_REDO keyboard input to perform redo +// +// Optional: +// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode +// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), +// required for default WORDLEFT/WORDRIGHT handlers +// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to +// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to +// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT +// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT +// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line +// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line +// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text +// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text +// +// Todo: +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page +// +// Keyboard input must be encoded as a single integer value; e.g. a character code +// and some bitflags that represent shift states. to simplify the interface, SHIFT must +// be a bitflag, so we can test the shifted state of cursor movements to allow selection, +// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// +// You can encode other things, such as CONTROL or ALT, in additional bits, and +// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, +// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN +// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, +// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the +// API below. The control keys will only match WM_KEYDOWN events because of the +// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN +// bit so it only decodes WM_CHAR events. +// +// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed +// row of characters assuming they start on the i'th character--the width and +// the height and the number of characters consumed. This allows this library +// to traverse the entire layout incrementally. You need to compute word-wrapping +// here. +// +// Each textfield keeps its own insert mode state, which is not how normal +// applications work. To keep an app-wide insert mode, update/copy the +// "insert_mode" field of STB_TexteditState before/after calling API functions. +// +// API +// +// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +// +// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +// +// Each of these functions potentially updates the string and updates the +// state. +// +// initialize_state: +// set the textedit state to a known good default state when initially +// constructing the textedit. +// +// click: +// call this with the mouse x,y on a mouse down; it will update the cursor +// and reset the selection start/end to the cursor point. the x,y must +// be relative to the text widget, with (0,0) being the top left. +// +// drag: +// call this with the mouse x,y on a mouse drag/up; it will update the +// cursor and the selection end point +// +// cut: +// call this to delete the current selection; returns true if there was +// one. you should FIRST copy the current selection to the system paste buffer. +// (To copy, just copy the current selection out of the string yourself.) +// +// paste: +// call this to paste text at the current cursor point or over the current +// selection if there is one. +// +// key: +// call this for keyboard inputs sent to the textfield. you can use it +// for "key down" events or for "translated" key events. if you need to +// do both (as in Win32), or distinguish Unicode characters from control +// inputs, set a high bit to distinguish the two; then you can define the +// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit +// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is +// clear. +// +// When rendering, you can read the cursor position and selection state from +// the STB_TexteditState. +// +// +// Notes: +// +// This is designed to be usable in IMGUI, so it allows for the possibility of +// running in an IMGUI that has NOT cached the multi-line layout. For this +// reason, it provides an interface that is compatible with computing the +// layout incrementally--we try to make sure we make as few passes through +// as possible. (For example, to locate the mouse pointer in the text, we +// could define functions that return the X and Y positions of characters +// and binary search Y and then X, but if we're doing dynamic layout this +// will run the layout algorithm many times, so instead we manually search +// forward in one pass. Similar logic applies to e.g. up-arrow and +// down-arrow movement.) +// +// If it's run in a widget that *has* cached the layout, then this is less +// efficient, but it's not horrible on modern computers. But you wouldn't +// want to edit million-line files with it. + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Header-file mode +//// +//// + +#ifndef INCLUDE_STB_TEXTEDIT_H +#define INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////// +// +// STB_TexteditState +// +// Definition of STB_TexteditState which you should store +// per-textfield; it includes cursor position, selection state, +// and undo state. +// + +#ifndef STB_TEXTEDIT_UNDOSTATECOUNT +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#endif +#ifndef STB_TEXTEDIT_UNDOCHARCOUNT +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#endif +#ifndef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_CHARTYPE int +#endif +#ifndef STB_TEXTEDIT_POSITIONTYPE +#define STB_TEXTEDIT_POSITIONTYPE int +#endif + +typedef struct +{ + // private data + STB_TEXTEDIT_POSITIONTYPE where; + short insert_length; + short delete_length; + short char_storage; +} StbUndoRecord; + +typedef struct +{ + // private data + StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; + STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point, redo_point; + short undo_char_point, redo_char_point; +} StbUndoState; + +typedef struct +{ + ///////////////////// + // + // public data + // + + int cursor; + // position of the text cursor within the string + + int select_start; // selection start point + int select_end; + // selection start and end point in characters; if equal, no selection. + // note that start may be less than or greater than end (e.g. when + // dragging the mouse, start is where the initial click was, and you + // can drag in either direction) + + unsigned char insert_mode; + // each textfield keeps its own insert mode state. to keep an app-wide + // insert mode, copy this value in/out of the app state + + ///////////////////// + // + // private data + // + unsigned char cursor_at_end_of_line; // not implemented yet + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char padding1, padding2, padding3; + float preferred_x; // this determines where the cursor up/down tries to seek to along x + StbUndoState undostate; +} STB_TexteditState; + + +//////////////////////////////////////////////////////////////////////// +// +// StbTexteditRow +// +// Result of layout query, used by stb_textedit to determine where +// the text in each row is. + +// result of layout query +typedef struct +{ + float x0,x1; // starting x location, end x location (allows for align=right, etc) + float baseline_y_delta; // position of baseline relative to previous row's baseline + float ymin,ymax; // height of row above and below baseline + int num_chars; +} StbTexteditRow; +#endif //INCLUDE_STB_TEXTEDIT_H + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Implementation mode +//// +//// + + +// implementation isn't include-guarded, since it might have indirectly +// included just the "header" portion +#ifdef STB_TEXTEDIT_IMPLEMENTATION + +#ifndef STB_TEXTEDIT_memmove +#include +#define STB_TEXTEDIT_memmove memmove +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// Mouse input handling +// + +// traverse the layout to locate the nearest character to a display position +static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) +{ + StbTexteditRow r; + int n = STB_TEXTEDIT_STRINGLEN(str); + float base_y = 0, prev_x; + int i=0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + // search rows to find one that straddles 'y' + while (i < n) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (r.num_chars <= 0) + return n; + + if (i==0 && y < base_y + r.ymin) + return 0; + + if (y < base_y + r.ymax) + break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + // below all text, return 'after' last character + if (i >= n) + return n; + + // check if it's before the beginning of the line + if (x < r.x0) + return i; + + // check if it's before the end of the line + if (x < r.x1) { + // search characters in row for one that straddles 'x' + prev_x = r.x0; + for (k=0; k < r.num_chars; ++k) { + float w = STB_TEXTEDIT_GETWIDTH(str, i, k); + if (x < prev_x+w) { + if (x < prev_x+w/2) + return k+i; + else + return k+i+1; + } + prev_x += w; + } + // shouldn't happen, but if it does, fall through to end-of-line case + } + + // if the last character is a newline, return that. otherwise return 'after' the last character + if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) + return i+r.num_chars-1; + else + return i+r.num_chars; +} + +// API click: on mouse down, move the cursor to the clicked location, and reset the selection +static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + state->cursor = stb_text_locate_coord(str, x, y); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} + +// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location +static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + int p = stb_text_locate_coord(str, x, y); + if (state->select_start == state->select_end) + state->select_start = state->cursor; + state->cursor = state->select_end = p; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Keyboard input handling +// + +// forward declarations +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); + +typedef struct +{ + float x,y; // position of n'th character + float height; // height of line + int first_char, length; // first char of row, and length + int prev_first; // first char of previous row +} StbFindState; + +// find the x/y location of a character, and remember info about the previous row in +// case we get a move-up event (for page up, we'll have to rescan) +static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) +{ + StbTexteditRow r; + int prev_start = 0; + int z = STB_TEXTEDIT_STRINGLEN(str); + int i=0, first; + + if (n == z) { + // if it's at the end, then find the last line -- simpler than trying to + // explicitly handle this case in the regular code + if (single_line) { + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; + } else { + find->y = 0; + find->x = 0; + find->height = 1; + while (i < z) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + prev_start = i; + i += r.num_chars; + } + find->first_char = i; + find->length = 0; + find->prev_first = prev_start; + } + return; + } + + // search rows to find the one that straddles character n + find->y = 0; + + for(;;) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (n < i + r.num_chars) + break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + // now scan to find xpos + find->x = r.x0; + i = 0; + for (i=0; first+i < n; ++i) + find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); +} + +#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +// make the selection/cursor state valid if client altered the string +static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + int n = STB_TEXTEDIT_STRINGLEN(str); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + // if clamping forced them to be equal, move the cursor to match + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} + +// delete characters while updating undo +static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) +{ + stb_text_makeundo_delete(str, state, where, len); + STB_TEXTEDIT_DELETECHARS(str, where, len); + state->has_preferred_x = 0; +} + +// delete the section +static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + stb_textedit_clamp(str, state); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} + +// canoncialize the selection so start <= end +static void stb_textedit_sortselection(STB_TexteditState *state) +{ + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} + +// move cursor to first character of selection +static void stb_textedit_move_to_first(STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} + +// move cursor to last character of selection +static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} + +#ifdef STB_TEXTEDIT_IS_SPACE +static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) +{ + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; +} + +#ifndef STB_TEXTEDIT_MOVEWORDLEFT +static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) +{ + --c; // always move at least one character + while( c >= 0 && !is_word_boundary( str, c ) ) + --c; + + if( c < 0 ) + c = 0; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous +#endif + +#ifndef STB_TEXTEDIT_MOVEWORDRIGHT +static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) +{ + const int len = STB_TEXTEDIT_STRINGLEN(str); + ++c; // always move at least one character + while( c < len && !is_word_boundary( str, c ) ) + ++c; + + if( c > len ) + c = len; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next +#endif + +#endif + +// update selection and cursor to match each other +static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) +{ + if (!STB_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else + state->cursor = state->select_end; +} + +// API cut: delete selection +static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_delete_selection(str,state); // implicity clamps + state->has_preferred_x = 0; + return 1; + } + return 0; +} + +// API paste: replace existing selection with passed-in text +static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) +{ + STB_TEXTEDIT_CHARTYPE *text = (STB_TEXTEDIT_CHARTYPE *) ctext; + // if there's a selection, the paste should delete it + stb_textedit_clamp(str, state); + stb_textedit_delete_selection(str,state); + // try to insert the characters + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + stb_text_makeundo_insert(state, state->cursor, len); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + // remove the undo since we didn't actually insert the characters + if (state->undostate.undo_point) + --state->undostate.undo_point; + return 0; +} + +// API key: process a keyboard input +static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +{ +retry: + switch (key) { + default: { + int c = STB_TEXTEDIT_KEYTOTEXT(key); + if (c > 0) { + STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; + + // can't add newline in single-line mode + if (c == '\n' && state->single_line) + break; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + stb_textedit_delete_selection(str,state); // implicity clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + stb_text_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + } + break; + } + +#ifdef STB_TEXTEDIT_K_INSERT + case STB_TEXTEDIT_K_INSERT: + state->insert_mode = !state->insert_mode; + break; +#endif + + case STB_TEXTEDIT_K_UNDO: + stb_text_undo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_REDO: + stb_text_redo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT: + // if currently there's a selection, move cursor to start of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else + if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_RIGHT: + // if currently there's a selection, move cursor to end of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else + ++state->cursor; + stb_textedit_clamp(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + // move selection left + if (state->select_end > 0) + --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_MOVEWORDLEFT + case STB_TEXTEDIT_K_WORDLEFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + +#ifdef STB_TEXTEDIT_MOVEWORDRIGHT + case STB_TEXTEDIT_K_WORDRIGHT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + // move selection right + ++state->select_end; + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_DOWN: + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down in single-line behave like left&right + key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str,state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // now find character position down a row + if (find.length) { + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + int start = find.first_char + find.length; + state->cursor = start; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_UP: + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + + if (state->single_line) { + // on windows, up&down become left&right + key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + // can only go up if there's a previous row + if (find.prev_first != find.first_char) { + // now find character position up a row + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + float x; + state->cursor = find.prev_first; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + } + break; + } + + case STB_TEXTEDIT_K_DELETE: + case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->cursor < n) + stb_textedit_delete(str, state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_BACKSPACE: + case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + stb_textedit_clamp(str, state); + if (state->cursor > 0) { + stb_textedit_delete(str, state, state->cursor-1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2: +#endif + case STB_TEXTEDIT_K_TEXTSTART: + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2: +#endif + case STB_TEXTEDIT_K_TEXTEND: + state->cursor = STB_TEXTEDIT_STRINGLEN(str); + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); + state->has_preferred_x = 0; + break; + + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2: +#endif + case STB_TEXTEDIT_K_LINESTART: + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2: +#endif + case STB_TEXTEDIT_K_LINEEND: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->has_preferred_x = 0; + break; + } + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + } + +// @TODO: +// STB_TEXTEDIT_K_PGUP - move cursor up a page +// STB_TEXTEDIT_K_PGDOWN - move cursor down a page + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Undo processing +// +// @OPTIMIZE: the undo/redo buffer should be circular + +static void stb_textedit_flush_redo(StbUndoState *state) +{ + state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; +} + +// discard the oldest entry in the undo list +static void stb_textedit_discard_undo(StbUndoState *state) +{ + if (state->undo_point > 0) { + // if the 0th undo state has characters, clean those up + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + // delete n characters from all other records + state->undo_char_point = state->undo_char_point - (short) n; // vsnet05 + STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) ((size_t)state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=0; i < state->undo_point; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it + } + --state->undo_point; + STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) ((size_t)state->undo_point*sizeof(state->undo_rec[0]))); + } +} + +// discard the oldest entry in the redo list--it's bad if this +// ever happens, but because undo & redo have to store the actual +// characters in different cases, the redo character buffer can +// fill up even though the undo buffer didn't +static void stb_textedit_discard_redo(StbUndoState *state) +{ + int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; + + if (state->redo_point <= k) { + // if the k'th undo state has characters, clean those up + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + // delete n characters from all other records + state->redo_char_point = state->redo_char_point + (short) n; // vsnet05 + STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((size_t)(STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=state->redo_point; i < k; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = state->undo_rec[i].char_storage + (short) n; // vsnet05 + } + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point, state->undo_rec + state->redo_point-1, (size_t) ((size_t)(STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); + ++state->redo_point; + } +} + +static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) +{ + // any time we create a new undo record, we discard redo + stb_textedit_flush_redo(state); + + // if we have no free records, we have to make room, by sliding the + // existing records down + if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + stb_textedit_discard_undo(state); + + // if the characters to store won't possibly fit in the buffer, we can't undo + if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return NULL; + } + + // if we don't have enough free characters in the buffer, we have to make room + while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) + stb_textedit_discard_undo(state); + + return &state->undo_rec[state->undo_point++]; +} + +static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) +{ + StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); + if (r == NULL) + return NULL; + + r->where = pos; + r->insert_length = (short) insert_len; + r->delete_length = (short) delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return NULL; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point = state->undo_char_point + (short) insert_len; + return &state->undo_char[r->char_storage]; + } +} + +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord u, *r; + if (s->undo_point == 0) + return; + + // we need to do two things: apply the undo record, and create a redo record + u = s->undo_rec[s->undo_point-1]; + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) { + // if the undo record says to delete characters, then the redo record will + // need to re-insert the characters that get deleted, so we need to store + // them. + + // there are three cases: + // there's enough room to store the characters + // characters stored for *redoing* don't leave room for redo + // characters stored for *undoing* don't leave room for redo + // if the last is true, we have to bail + + if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { + // the undo records take up too much character space; there's no space to store the redo characters + r->insert_length = 0; + } else { + int i; + + // there's definitely room to store the characters eventually + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); + // should never happen: + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + } + r = &s->undo_rec[s->redo_point-1]; + + r->char_storage = s->redo_char_point - u.delete_length; + s->redo_char_point = s->redo_char_point - (short) u.delete_length; + + // now save the characters + for (i=0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); + } + + // now we can carry out the deletion + STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); + } + + // check type of recorded action: + if (u.insert_length) { + // easy case: was a deletion, so we need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + s->undo_char_point -= u.insert_length; + } + + state->cursor = u.where + u.insert_length; + + s->undo_point--; + s->redo_point--; +} + +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord *u, r; + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + + // we need to do two things: apply the redo record, and create an undo record + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + // we KNOW there must be room for the undo record, because the redo record + // was derived from an undo record + + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + // the redo record requires us to delete characters, so the undo record + // needs to store the characters + + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = s->undo_char_point + u->insert_length; + + // now save the characters + for (i=0; i < u->insert_length; ++i) + s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); + } + + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); + } + + if (r.insert_length) { + // easy case: need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + s->redo_char_point += r.insert_length; + } + + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} + +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) +{ + stb_text_createundo(&state->undostate, where, 0, length); +} + +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); + if (p) { + for (i=0; i < length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); + if (p) { + for (i=0; i < old_length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +// reset the state to default +static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) +{ + state->undostate.undo_point = 0; + state->undostate.undo_char_point = 0; + state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char) is_single_line; + state->insert_mode = 0; +} + +// API initialize +static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +{ + stb_textedit_clear_state(state, is_single_line); +} +#endif//STB_TEXTEDIT_IMPLEMENTATION diff --git a/skeleton/imgui/stb_truetype.h b/skeleton/imgui/stb_truetype.h new file mode 100644 index 0000000..92b9a87 --- /dev/null +++ b/skeleton/imgui/stb_truetype.h @@ -0,0 +1,4018 @@ +// stb_truetype.h - v1.14 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// Sergey Popov +// Giumo X. Clanjor +// Higor Euripedes +// Thomas Fields +// Derek Vinyard +// +// VERSION HISTORY +// +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts, num-fonts-in-TTC function +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) { + *x0 = r ? c.min_x : 0; + *y0 = r ? c.min_y : 0; + *x1 = r ? c.max_x : 0; + *y1 = r ? c.max_y : 0; + } + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clear pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + float y1,y2; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + y1 = (x - x0) / dx + y_top; + y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// diff --git a/skeleton/sdl2.cpp b/skeleton/sdl2.cpp new file mode 100644 index 0000000..4cb7c0f --- /dev/null +++ b/skeleton/sdl2.cpp @@ -0,0 +1,325 @@ +#ifdef LIBRW_SDL2 + +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +#ifdef RW_OPENGL + +SDL_Window *window; + +static int keyCodeToSkKey(SDL_Keycode keycode) { + switch (keycode) { + case SDLK_SPACE: return ' '; + case SDLK_QUOTE: return '\''; + case SDLK_COMMA: return ','; + case SDLK_MINUS: return '-'; + case SDLK_PERIOD: return '.'; + case SDLK_SLASH: return '/'; + + case SDLK_0: return '0'; + case SDLK_1: return '1'; + case SDLK_2: return '2'; + case SDLK_3: return '3'; + case SDLK_4: return '4'; + case SDLK_5: return '5'; + case SDLK_6: return '6'; + case SDLK_7: return '7'; + case SDLK_8: return '8'; + case SDLK_9: return '9'; + + case SDLK_SEMICOLON: return ';'; + case SDLK_EQUALS: return '='; + + case SDLK_a: return 'A'; + case SDLK_b: return 'B'; + case SDLK_c: return 'C'; + case SDLK_d: return 'D'; + case SDLK_e: return 'E'; + case SDLK_f: return 'F'; + case SDLK_g: return 'G'; + case SDLK_h: return 'H'; + case SDLK_i: return 'I'; + case SDLK_j: return 'J'; + case SDLK_k: return 'K'; + case SDLK_l: return 'L'; + case SDLK_m: return 'M'; + case SDLK_n: return 'N'; + case SDLK_o: return 'O'; + case SDLK_p: return 'P'; + case SDLK_q: return 'Q'; + case SDLK_r: return 'R'; + case SDLK_s: return 'S'; + case SDLK_t: return 'T'; + case SDLK_u: return 'U'; + case SDLK_v: return 'V'; + case SDLK_w: return 'W'; + case SDLK_x: return 'X'; + case SDLK_y: return 'Y'; + case SDLK_z: return 'Z'; + + case SDLK_LEFTBRACKET: return '['; + case SDLK_BACKSLASH: return '\\'; + case SDLK_RIGHTBRACKET: return ']'; + case SDLK_BACKQUOTE: return '`'; + case SDLK_ESCAPE: return KEY_ESC; + case SDLK_RETURN: return KEY_ENTER; + case SDLK_TAB: return KEY_TAB; + case SDLK_BACKSPACE: return KEY_BACKSP; + case SDLK_INSERT: return KEY_INS; + case SDLK_DELETE: return KEY_DEL; + case SDLK_RIGHT: return KEY_RIGHT; + case SDLK_DOWN: return KEY_DOWN; + case SDLK_UP: return KEY_UP; + case SDLK_PAGEUP: return KEY_PGUP; + case SDLK_PAGEDOWN: return KEY_PGDN; + case SDLK_HOME: return KEY_HOME; + case SDLK_END: return KEY_END; + case SDLK_CAPSLOCK: return KEY_CAPSLK; + case SDLK_SCROLLLOCK: return KEY_NULL; + case SDLK_NUMLOCKCLEAR: return KEY_NULL; + case SDLK_PRINTSCREEN: return KEY_NULL; + case SDLK_PAUSE: return KEY_NULL; + + case SDLK_F1: return KEY_F1; + case SDLK_F2: return KEY_F2; + case SDLK_F3: return KEY_F3; + case SDLK_F4: return KEY_F4; + case SDLK_F5: return KEY_F5; + case SDLK_F6: return KEY_F6; + case SDLK_F7: return KEY_F7; + case SDLK_F8: return KEY_F8; + case SDLK_F9: return KEY_F9; + case SDLK_F10: return KEY_F10; + case SDLK_F11: return KEY_F11; + case SDLK_F12: return KEY_F12; + case SDLK_F13: return KEY_NULL; + case SDLK_F14: return KEY_NULL; + case SDLK_F15: return KEY_NULL; + case SDLK_F16: return KEY_NULL; + case SDLK_F17: return KEY_NULL; + case SDLK_F18: return KEY_NULL; + case SDLK_F19: return KEY_NULL; + case SDLK_F20: return KEY_NULL; + case SDLK_F21: return KEY_NULL; + case SDLK_F22: return KEY_NULL; + case SDLK_F23: return KEY_NULL; + case SDLK_F24: return KEY_NULL; + + case SDLK_KP_0: return KEY_NULL; + case SDLK_KP_1: return KEY_NULL; + case SDLK_KP_2: return KEY_NULL; + case SDLK_KP_3: return KEY_NULL; + case SDLK_KP_4: return KEY_NULL; + case SDLK_KP_5: return KEY_NULL; + case SDLK_KP_6: return KEY_NULL; + case SDLK_KP_7: return KEY_NULL; + case SDLK_KP_8: return KEY_NULL; + case SDLK_KP_9: return KEY_NULL; + case SDLK_KP_DECIMAL: return KEY_NULL; + case SDLK_KP_DIVIDE: return KEY_NULL; + case SDLK_KP_MULTIPLY: return KEY_NULL; + case SDLK_KP_MINUS: return KEY_NULL; + case SDLK_KP_PLUS: return KEY_NULL; + case SDLK_KP_ENTER: return KEY_NULL; + case SDLK_KP_EQUALS: return KEY_NULL; + + case SDLK_LSHIFT: return KEY_LSHIFT; + case SDLK_LCTRL: return KEY_LCTRL; + case SDLK_LALT: return KEY_LALT; + case SDLK_LGUI: return KEY_NULL; + case SDLK_RSHIFT: return KEY_RSHIFT; + case SDLK_RCTRL: return KEY_RCTRL; + case SDLK_RALT: return KEY_RALT; + case SDLK_RGUI: return KEY_NULL; + case SDLK_MENU: return KEY_NULL; + } + return KEY_NULL; +} + +#if 0 +static void +keypress(SDL_Window *window, int key, int scancode, int action, int mods) +{ + if(key >= 0 && key <= GLFW_KEY_LAST){ + if(action == GLFW_RELEASE) KeyUp(keymap[key]); + else if(action == GLFW_PRESS) KeyDown(keymap[key]); + else if(action == GLFW_REPEAT) KeyDown(keymap[key]); + } +} + +static void +charinput(GLFWwindow *window, unsigned int c) +{ + EventHandler(CHARINPUT, (void*)(uintptr)c); +} + +static void +resize(GLFWwindow *window, int w, int h) +{ + rw::Rect r; + r.x = 0; + r.y = 0; + r.w = w; + r.h = h; + EventHandler(RESIZE, &r); +} + +static void +mousebtn(GLFWwindow *window, int button, int action, int mods) +{ + static int buttons = 0; + sk::MouseState ms; + + switch(button){ + case GLFW_MOUSE_BUTTON_LEFT: + if(action == GLFW_PRESS) + buttons |= 1; + else + buttons &= ~1; + break; + case GLFW_MOUSE_BUTTON_MIDDLE: + if(action == GLFW_PRESS) + buttons |= 2; + else + buttons &= ~2; + break; + case GLFW_MOUSE_BUTTON_RIGHT: + if(action == GLFW_PRESS) + buttons |= 4; + else + buttons &= ~4; + break; + } + + sk::MouseState ms; + ms.buttons = buttons; + EventHandler(MOUSEBTN, &ms); +} +#endif + +enum mousebutton { +BUTTON_LEFT = 0x1, +BUTTON_MIDDLE = 0x2, +BUTTON_RIGHT = 0x4, +}; + +int +main(int argc, char *argv[]) +{ + args.argc = argc; + args.argv = argv; + + if(EventHandler(INITIALIZE, nil) == EVENTERROR) + return 0; + + engineOpenParams.width = sk::globals.width; + engineOpenParams.height = sk::globals.height; + engineOpenParams.windowtitle = sk::globals.windowtitle; + engineOpenParams.window = &window; + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + float lastTime = SDL_GetTicks(); + SDL_Event event; + int mouseButtons = 0; + + SDL_StartTextInput(); + + while(!sk::globals.quit){ + while(SDL_PollEvent(&event)){ + switch(event.type){ + case SDL_QUIT: + sk::globals.quit = true; + break; + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_RESIZED: { + rw::Rect r; + SDL_GetWindowPosition(window, &r.x, &r.y); + r.w = event.window.data1; + r.h = event.window.data2; + EventHandler(RESIZE, &r); + break; + } + } + break; + case SDL_KEYUP: { + int c = keyCodeToSkKey(event.key.keysym.sym); + EventHandler(KEYUP, &c); + break; + } + case SDL_KEYDOWN: { + int c = keyCodeToSkKey(event.key.keysym.sym); + EventHandler(KEYDOWN, &c); + break; + } + case SDL_TEXTINPUT: { + char *c = event.text.text; + while (int ci = *c) { + EventHandler(CHARINPUT, (void*)(uintptr)ci); + ++c; + } + break; + } + case SDL_MOUSEMOTION: { + sk::MouseState ms; + ms.posx = event.motion.x; + ms.posy = event.motion.y; + EventHandler(MOUSEMOVE, &ms); + break; + } + case SDL_MOUSEBUTTONDOWN: { + switch (event.button.button) { + case SDL_BUTTON_LEFT: mouseButtons |= BUTTON_LEFT; break; + case SDL_BUTTON_MIDDLE: mouseButtons |= BUTTON_MIDDLE; break; + case SDL_BUTTON_RIGHT: mouseButtons |= BUTTON_RIGHT; break; + } + sk::MouseState ms; + ms.buttons = mouseButtons; + EventHandler(MOUSEBTN, &ms); + break; + } + case SDL_MOUSEBUTTONUP: { + switch (event.button.button) { + case SDL_BUTTON_LEFT: mouseButtons &= ~BUTTON_LEFT; break; + case SDL_BUTTON_MIDDLE: mouseButtons &= ~BUTTON_MIDDLE; break; + case SDL_BUTTON_RIGHT: mouseButtons &= ~BUTTON_RIGHT; break; + } + sk::MouseState ms; + ms.buttons = mouseButtons; + EventHandler(MOUSEBTN, &ms); + break; + } + } + } + float currTime = SDL_GetTicks(); + float timeDelta = (currTime - lastTime) * 0.001f; + + EventHandler(IDLE, &timeDelta); + + lastTime = currTime; + } + + SDL_StopTextInput(); + + EventHandler(RWTERMINATE, nil); + + return 0; +} + +namespace sk { + +void +SetMousePosition(int x, int y) +{ + SDL_WarpMouseInWindow(*engineOpenParams.window, x, y); +} + +} + +#endif +#endif diff --git a/skeleton/skeleton.cpp b/skeleton/skeleton.cpp new file mode 100644 index 0000000..ad84d1a --- /dev/null +++ b/skeleton/skeleton.cpp @@ -0,0 +1,111 @@ +#include +#include "skeleton.h" + + +namespace sk { + +Globals globals; +Args args; + + +bool +InitRW(void) +{ + if(!rw::Engine::init()) + return false; + if(AppEventHandler(sk::PLUGINATTACH, nil) == EVENTERROR) + return false; + if(!rw::Engine::open(&engineOpenParams)) + return false; + + SubSystemInfo info; + int i, n; + n = Engine::getNumSubSystems(); + for(i = 0; i < n; i++) + if(Engine::getSubSystemInfo(&info, i)) + printf("subsystem: %s\n", info.name); + Engine::setSubSystem(n-1); + + int want = -1; + VideoMode mode; + n = Engine::getNumVideoModes(); + for(i = 0; i < n; i++) + if(Engine::getVideoModeInfo(&mode, i)){ +// if(mode.width == 640 && mode.height == 480 && mode.depth == 32) + if(mode.width == 1920 && mode.height == 1080 && mode.depth == 32) + want = i; + printf("mode: %dx%dx%d %d\n", mode.width, mode.height, mode.depth, mode.flags); + } +// if(want >= 0) Engine::setVideoMode(want); + Engine::getVideoModeInfo(&mode, Engine::getCurrentVideoMode()); + + if(mode.flags & VIDEOMODEEXCLUSIVE){ + globals.width = mode.width; + globals.height = mode.height; + } + + if(!rw::Engine::start()) + return false; + + rw::Image::setSearchPath("./"); + return true; +} + +void +TerminateRW(void) +{ + // TODO: delete all tex dicts + rw::Engine::stop(); + rw::Engine::close(); + rw::Engine::term(); +} + +Camera* +CameraCreate(int32 width, int32 height, bool32 z) +{ + Camera *cam; + cam = Camera::create(); + cam->setFrame(Frame::create()); + cam->frameBuffer = Raster::create(width, height, 0, Raster::CAMERA); + cam->zBuffer = Raster::create(width, height, 0, Raster::ZBUFFER); + return cam; +} + +void +CameraSize(Camera *cam, Rect *r) +{ + if(cam->frameBuffer){ + cam->frameBuffer->destroy(); + cam->frameBuffer = nil; + } + if(cam->zBuffer){ + cam->zBuffer->destroy(); + cam->zBuffer = nil; + } + cam->frameBuffer = Raster::create(r->w, r->h, 0, Raster::CAMERA); + cam->zBuffer = Raster::create(r->w, r->h, 0, Raster::ZBUFFER); +} + +EventStatus +EventHandler(Event e, void *param) +{ + EventStatus s; + s = AppEventHandler(e, param); + if(e == QUIT){ + globals.quit = 1; + return EVENTPROCESSED; + } + if(s == EVENTNOTPROCESSED) + switch(e){ + case RWINITIALIZE: + return InitRW() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + TerminateRW(); + return EVENTPROCESSED; + default: + break; + } + return s; +} + +} diff --git a/skeleton/skeleton.h b/skeleton/skeleton.h new file mode 100644 index 0000000..9604602 --- /dev/null +++ b/skeleton/skeleton.h @@ -0,0 +1,115 @@ +extern rw::EngineOpenParams engineOpenParams; + +namespace sk { + +using namespace rw; + +// same as RW skeleton +enum Key +{ + // ascii... + + KEY_ESC = 128, + + KEY_F1 = 129, + KEY_F2 = 130, + KEY_F3 = 131, + KEY_F4 = 132, + KEY_F5 = 133, + KEY_F6 = 134, + KEY_F7 = 135, + KEY_F8 = 136, + KEY_F9 = 137, + KEY_F10 = 138, + KEY_F11 = 139, + KEY_F12 = 140, + + KEY_INS = 141, + KEY_DEL = 142, + KEY_HOME = 143, + KEY_END = 144, + KEY_PGUP = 145, + KEY_PGDN = 146, + + KEY_UP = 147, + KEY_DOWN = 148, + KEY_LEFT = 149, + KEY_RIGHT = 150, + + // some stuff ommitted + + KEY_BACKSP = 168, + KEY_TAB = 169, + KEY_CAPSLK = 170, + KEY_ENTER = 171, + KEY_LSHIFT = 172, + KEY_RSHIFT = 173, + KEY_LCTRL = 174, + KEY_RCTRL = 175, + KEY_LALT = 176, + KEY_RALT = 177, + + KEY_NULL, // unused + KEY_NUMKEYS, +}; + +enum EventStatus +{ + EVENTERROR, + EVENTPROCESSED, + EVENTNOTPROCESSED +}; + +enum Event +{ + INITIALIZE, + RWINITIALIZE, + RWTERMINATE, + SELECTDEVICE, + PLUGINATTACH, + KEYDOWN, + KEYUP, + CHARINPUT, + MOUSEMOVE, + MOUSEBTN, + RESIZE, + IDLE, + QUIT +}; + +struct Globals +{ + const char *windowtitle; + int32 width; + int32 height; + bool32 quit; +}; +extern Globals globals; + +// Argument to mouse events +struct MouseState +{ + int posx, posy; + int buttons; // bits 0-2 are left, middle, right button down +}; + +struct Args +{ + int argc; + char **argv; +}; +extern Args args; + +bool InitRW(void); +void TerminateRW(void); +Camera *CameraCreate(int32 width, int32 height, bool32 z); +void CameraSize(Camera *cam, Rect *r); +void SetMousePosition(int x, int y); +EventStatus EventHandler(Event e, void *param); + +} + +sk::EventStatus AppEventHandler(sk::Event e, void *param); + +#include "imgui/imgui.h" +#include "imgui/imgui_impl_rw.h" diff --git a/skeleton/win.cpp b/skeleton/win.cpp new file mode 100644 index 0000000..9f1ee5a --- /dev/null +++ b/skeleton/win.cpp @@ -0,0 +1,315 @@ +#ifdef _WIN32 +#include +#include +#include "skeleton.h" + +using namespace sk; +using namespace rw; + +#ifdef RW_D3D9 + +#ifndef VK_OEM_NEC_EQUAL +#define VK_OEM_NEC_EQUAL 0x92 +#endif + +static int keymap[256]; +static void +initkeymap(void) +{ + int i; + for(i = 0; i < 256; i++) + keymap[i] = KEY_NULL; + keymap[VK_SPACE] = ' '; + keymap[VK_OEM_7] = '\''; + keymap[VK_OEM_COMMA] = ','; + keymap[VK_OEM_MINUS] = '-'; + keymap[VK_OEM_PERIOD] = '.'; + keymap[VK_OEM_2] = '/'; + for(i = '0'; i <= '9'; i++) + keymap[i] = i; + keymap[VK_OEM_1] = ';'; + keymap[VK_OEM_NEC_EQUAL] = '='; + for(i = 'A'; i <= 'Z'; i++) + keymap[i] = i; + keymap[VK_OEM_4] = '['; + keymap[VK_OEM_5] = '\\'; + keymap[VK_OEM_6] = ']'; + keymap[VK_OEM_3] = '`'; + keymap[VK_ESCAPE] = KEY_ESC; + keymap[VK_RETURN] = KEY_ENTER; + keymap[VK_TAB] = KEY_TAB; + keymap[VK_BACK] = KEY_BACKSP; + keymap[VK_INSERT] = KEY_INS; + keymap[VK_DELETE] = KEY_DEL; + keymap[VK_RIGHT] = KEY_RIGHT; + keymap[VK_LEFT] = KEY_LEFT; + keymap[VK_DOWN] = KEY_DOWN; + keymap[VK_UP] = KEY_UP; + keymap[VK_PRIOR] = KEY_PGUP; + keymap[VK_NEXT] = KEY_PGDN; + keymap[VK_HOME] = KEY_HOME; + keymap[VK_END] = KEY_END; + keymap[VK_MODECHANGE] = KEY_CAPSLK; + for(i = VK_F1; i <= VK_F24; i++) + keymap[i] = i-VK_F1+KEY_F1; + keymap[VK_LSHIFT] = KEY_LSHIFT; + keymap[VK_LCONTROL] = KEY_LCTRL; + keymap[VK_LMENU] = KEY_LALT; + keymap[VK_RSHIFT] = KEY_RSHIFT; + keymap[VK_RCONTROL] = KEY_RCTRL; + keymap[VK_RMENU] = KEY_RALT; +} +bool running; + +static void KeyUp(int key) { EventHandler(KEYUP, &key); } +static void KeyDown(int key) { EventHandler(KEYDOWN, &key); } + +LRESULT CALLBACK +WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static int resizing = 0; + static int buttons = 0; + POINTS p; + + MouseState ms; + switch(msg){ + case WM_DESTROY: + PostQuitMessage(0); + break; + + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + if(wParam == VK_MENU){ + if(GetKeyState(VK_LMENU) & 0x8000) KeyDown(keymap[VK_LMENU]); + if(GetKeyState(VK_RMENU) & 0x8000) KeyDown(keymap[VK_RMENU]); + }else if(wParam == VK_CONTROL){ + if(GetKeyState(VK_LCONTROL) & 0x8000) KeyDown(keymap[VK_LCONTROL]); + if(GetKeyState(VK_RCONTROL) & 0x8000) KeyDown(keymap[VK_RCONTROL]); + }else if(wParam == VK_SHIFT){ + if(GetKeyState(VK_LSHIFT) & 0x8000) KeyDown(keymap[VK_LSHIFT]); + if(GetKeyState(VK_RSHIFT) & 0x8000) KeyDown(keymap[VK_RSHIFT]); + }else + KeyDown(keymap[wParam]); + break; + + case WM_SYSKEYUP: + case WM_KEYUP: + if(wParam == VK_MENU){ + if((GetKeyState(VK_LMENU) & 0x8000) == 0) KeyUp(keymap[VK_LMENU]); + if((GetKeyState(VK_RMENU) & 0x8000) == 0) KeyUp(keymap[VK_RMENU]); + }else if(wParam == VK_CONTROL){ + if((GetKeyState(VK_LCONTROL) & 0x8000) == 0) KeyUp(keymap[VK_LCONTROL]); + if((GetKeyState(VK_RCONTROL) & 0x8000) == 0) KeyUp(keymap[VK_RCONTROL]); + }else if(wParam == VK_SHIFT){ + if((GetKeyState(VK_LSHIFT) & 0x8000) == 0) KeyUp(keymap[VK_LSHIFT]); + if((GetKeyState(VK_RSHIFT) & 0x8000) == 0) KeyUp(keymap[VK_RSHIFT]); + }else + KeyUp(keymap[wParam]); + break; + + case WM_CHAR: + if(wParam > 0 && wParam < 0x10000) + EventHandler(CHARINPUT, (void*)wParam); + break; + + case WM_MOUSEMOVE: + p = MAKEPOINTS(lParam); + ms.posx = p.x; + ms.posy = p.y; + EventHandler(MOUSEMOVE, &ms); + break; + + case WM_LBUTTONDOWN: + buttons |= 1; goto mbtn; + case WM_LBUTTONUP: + buttons &= ~1; goto mbtn; + case WM_MBUTTONDOWN: + buttons |= 2; goto mbtn; + case WM_MBUTTONUP: + buttons &= ~2; goto mbtn; + case WM_RBUTTONDOWN: + buttons |= 4; goto mbtn; + case WM_RBUTTONUP: + buttons &= ~4; + mbtn: + ms.buttons = buttons; + EventHandler(MOUSEBTN, &ms); + break; + + case WM_SIZE: + rw::Rect r; + r.x = 0; + r.y = 0; + r.w = LOWORD(lParam); + r.h = HIWORD(lParam); + EventHandler(RESIZE, &r); + break; + + case WM_CLOSE: + DestroyWindow(hwnd); + break; + + case WM_SYSCOMMAND: + if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu + return 0; + break; + + case WM_QUIT: + running = false; + break; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +HWND +MakeWindow(HINSTANCE instance, int width, int height, const char *title) +{ + WNDCLASS wc; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = instance; + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wc.lpszMenuName = 0; + wc.lpszClassName = "librwD3D9"; + if(!RegisterClass(&wc)){ + MessageBox(0, "RegisterClass() - FAILED", 0, 0); + return 0; + } + + int offx = 100; + int offy = 100; + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + DWORD style = WS_OVERLAPPEDWINDOW; + AdjustWindowRect(&rect, style, FALSE); + rect.right += -rect.left; + rect.bottom += -rect.top; + HWND win; + win = CreateWindow("librwD3D9", title, style, + offx, offy, rect.right, rect.bottom, 0, 0, instance, 0); + if(!win){ + MessageBox(0, "CreateWindow() - FAILED", 0, 0); + return 0; + } + ShowWindow(win, SW_SHOW); + UpdateWindow(win); + return win; +} + +void +pollEvents(void) +{ + MSG msg; + while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){ + if(msg.message == WM_QUIT){ + running = false; + break; + }else{ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, + PSTR cmdLine, int showCmd) +{ +/* + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); +*/ + + INT64 ticks; + INT64 ticksPerSecond; + if(!QueryPerformanceFrequency((LARGE_INTEGER*)&ticksPerSecond)) + return 0; + if(!QueryPerformanceCounter((LARGE_INTEGER*)&ticks)) + return 0; + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + args.argc = _argc; + args.argv = _argv; +#else + args.argc = __argc; + args.argv = __argv; +#endif + + if(EventHandler(INITIALIZE, nil) == EVENTERROR) + return 0; + + HWND win = MakeWindow(instance, + sk::globals.width, sk::globals.height, + sk::globals.windowtitle); + if(win == 0){ + MessageBox(0, "MakeWindow() - FAILED", 0, 0); + return 0; + } + engineOpenParams.window = win; + initkeymap(); + + if(EventHandler(RWINITIALIZE, nil) == EVENTERROR) + return 0; + + INT64 lastTicks; + QueryPerformanceCounter((LARGE_INTEGER *)&lastTicks); + running = true; + while((pollEvents(), running) && !globals.quit){ + QueryPerformanceCounter((LARGE_INTEGER *)&ticks); + float timeDelta = (float)(ticks - lastTicks)/ticksPerSecond; + + EventHandler(IDLE, &timeDelta); + + lastTicks = ticks; + } + + EventHandler(RWTERMINATE, nil); + + return 0; +} + +namespace sk { + +void +SetMousePosition(int x, int y) +{ + POINT pos = { x, y }; + ClientToScreen(engineOpenParams.window, &pos); + SetCursorPos(pos.x, pos.y); +} + +} + +#endif + +#ifdef RW_OPENGL +int main(int argc, char *argv[]); + +int WINAPI +WinMain(HINSTANCE instance, HINSTANCE, + PSTR cmdLine, int showCmd) +{ +/* + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); +*/ + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + return main(_argc, _argv); +#else + return main(__argc, __argv); +#endif +} +#endif +#endif diff --git a/src/anim.cpp b/src/anim.cpp new file mode 100644 index 0000000..2003f1b --- /dev/null +++ b/src/anim.cpp @@ -0,0 +1,293 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" + +#define PLUGIN_ID ID_ANIMANIMATION + +namespace rw { + +// +// AnimInterpolatorInfo +// + +#define MAXINTERPINFO 10 + +static AnimInterpolatorInfo *interpInfoList[MAXINTERPINFO]; + +void +AnimInterpolatorInfo::registerInterp(AnimInterpolatorInfo *interpInfo) +{ + for(int32 i = 0; i < MAXINTERPINFO; i++) + if(interpInfoList[i] == nil){ + interpInfoList[i] = interpInfo; + return; + } + assert(0 && "no room for interpolatorInfo"); +} + +void +AnimInterpolatorInfo::unregisterInterp(AnimInterpolatorInfo *interpInfo) +{ + for(int32 i = 0; i < MAXINTERPINFO; i++) + if(interpInfoList[i] == interpInfo){ + rwFree(interpInfoList[i]); + interpInfoList[i] = nil; + return; + } +} + +AnimInterpolatorInfo* +AnimInterpolatorInfo::find(int32 id) +{ + for(int32 i = 0; i < MAXINTERPINFO; i++){ + if(interpInfoList[i] && interpInfoList[i]->id == id) + return interpInfoList[i]; + } + return nil; +} + +// +// Animation +// + +Animation* +Animation::create(AnimInterpolatorInfo *interpInfo, int32 numFrames, + int32 flags, float duration) +{ + int32 sz = sizeof(Animation) + + numFrames*interpInfo->animKeyFrameSize + + interpInfo->customDataSize; + uint8 *data = (uint8*)rwMalloc(sz, MEMDUR_EVENT | ID_ANIMANIMATION); + if(data == nil){ + RWERROR((ERR_ALLOC, sz)); + return nil; + } + Animation *anim = (Animation*)data; + data += sizeof(Animation); + anim->interpInfo = interpInfo; + anim->numFrames = numFrames; + anim->flags = flags; + anim->duration = duration; + anim->keyframes = data; + data += anim->numFrames*interpInfo->animKeyFrameSize; + anim->customData = data; + return anim; +} + +void +Animation::destroy(void) +{ + rwFree(this); +} + +int32 +Animation::getNumNodes(void) +{ + int32 sz = this->interpInfo->animKeyFrameSize; + KeyFrameHeader *first = (KeyFrameHeader*)this->keyframes; + int32 n = 0; + for(KeyFrameHeader *f = first; f->prev != first; f = f->next(sz)) + n++; + return n; +} + +Animation* +Animation::streamRead(Stream *stream) +{ + Animation *anim; + if(stream->readI32() != 0x100) + return nil; + int32 typeID = stream->readI32(); + AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(typeID); + int32 numFrames = stream->readI32(); + int32 flags = stream->readI32(); + float duration = stream->readF32(); + anim = Animation::create(interpInfo, numFrames, flags, duration); + interpInfo->streamRead(stream, anim); + return anim; +} + +Animation* +Animation::streamReadLegacy(Stream *stream) +{ + Animation *anim; + AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(1); + int32 numFrames = stream->readI32(); + int32 flags = stream->readI32(); + float duration = stream->readF32(); + anim = Animation::create(interpInfo, numFrames, flags, duration); + HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes; + for(int32 i = 0; i < anim->numFrames; i++){ + stream->read32(&frames[i].q, 4*4); + frames[i].q = conj(frames[i].q); + stream->read32(&frames[i].t, 3*4); + frames[i].time = stream->readF32(); + int32 prev = stream->readI32()/0x24; + frames[i].prev = &frames[prev]; + } + return anim; +} + +bool +Animation::streamWrite(Stream *stream) +{ + writeChunkHeader(stream, ID_ANIMANIMATION, this->streamGetSize()); + stream->writeI32(0x100); + stream->writeI32(this->interpInfo->id); + stream->writeI32(this->numFrames); + stream->writeI32(this->flags); + stream->writeF32(this->duration); + this->interpInfo->streamWrite(stream, this); + return true; +} + +bool +Animation::streamWriteLegacy(Stream *stream) +{ + stream->writeI32(this->numFrames); + stream->writeI32(this->flags); + stream->writeF32(this->duration); + assert(interpInfo->id == 1); + HAnimKeyFrame *frames = (HAnimKeyFrame*)this->keyframes; + for(int32 i = 0; i < this->numFrames; i++){ + frames[i].q = conj(frames[i].q); + stream->write32(&frames[i].q, 4*4); + frames[i].q = conj(frames[i].q); + stream->write32(&frames[i].t, 3*4); + stream->writeF32(frames[i].time); + stream->writeI32((frames[i].prev - frames)*0x24); + } + return true; +} + +uint32 +Animation::streamGetSize(void) +{ + uint32 size = 4 + 4 + 4 + 4 + 4; + size += this->interpInfo->streamGetSize(this); + return size; +} + +// +// AnimInterpolator +// + +AnimInterpolator* +AnimInterpolator::create(int32 numNodes, int32 maxFrameSize) +{ + AnimInterpolator *interp; + int32 sz; + int32 realsz = maxFrameSize; + + // Add some space for pointers and padding, hopefully this will be + // enough. Don't change maxFrameSize not to mess up streaming. + if(sizeof(void*) > 4) + realsz += 16; + sz = sizeof(AnimInterpolator) + numNodes*realsz; + interp = (AnimInterpolator*)rwMalloc(sz, MEMDUR_EVENT | ID_ANIMANIMATION); + if(interp == nil){ + RWERROR((ERR_ALLOC, sz)); + return nil; + } + interp->currentAnim = nil; + interp->currentTime = 0.0f; + interp->nextFrame = nil; + interp->maxInterpKeyFrameSize = maxFrameSize; + interp->currentInterpKeyFrameSize = maxFrameSize; + interp->currentAnimKeyFrameSize = -1; + interp->numNodes = numNodes;; + + return interp; +} + +void +AnimInterpolator::destroy(void) +{ + rwFree(this); +} + +bool32 +AnimInterpolator::setCurrentAnim(Animation *anim) +{ + int32 i; + AnimInterpolatorInfo *interpInfo = anim->interpInfo; + this->currentAnim = anim; + this->currentTime = 0.0f; + int32 maxkf = this->maxInterpKeyFrameSize; + if(sizeof(void*) > 4) // see above in create() + maxkf += 16; + if(interpInfo->interpKeyFrameSize > maxkf){ + RWERROR((ERR_GENERAL, "interpolation frame too big")); + return 0; + } + this->currentInterpKeyFrameSize = interpInfo->interpKeyFrameSize; + this->currentAnimKeyFrameSize = interpInfo->animKeyFrameSize; + this->applyCB = interpInfo->applyCB; + this->blendCB = interpInfo->blendCB; + this->interpCB = interpInfo->interpCB; + this->addCB = interpInfo->addCB; + for(i = 0; i < numNodes; i++){ + InterpFrameHeader *intf; + KeyFrameHeader *kf1, *kf2; + intf = this->getInterpFrame(i); + kf1 = this->getAnimFrame(i); + kf2 = this->getAnimFrame(i+numNodes); + intf->keyFrame1 = kf1; + intf->keyFrame2 = kf2; + // TODO: perhaps just implement all interpolator infos? + if(this->interpCB) + this->interpCB(intf, kf1, kf2, 0.0f, anim->customData); + } + this->nextFrame = this->getAnimFrame(numNodes*2); + return 1; +} + +void +AnimInterpolator::addTime(float32 t) +{ + int32 i; + if(t <= 0.0f) + return; + this->currentTime += t; + // reset animation + if(this->currentTime > this->currentAnim->duration){ + this->setCurrentAnim(this->currentAnim); + return; + } + KeyFrameHeader *last = this->getAnimFrame(this->currentAnim->numFrames); + KeyFrameHeader *next = (KeyFrameHeader*)this->nextFrame; + InterpFrameHeader *ifrm = nil; + while(next < last && next->prev->time <= this->currentTime){ + // find next interpolation frame to expire + for(i = 0; i < this->numNodes; i++){ + ifrm = this->getInterpFrame(i); + if(ifrm->keyFrame2 == next->prev) + break; + } + // advance interpolation frame + ifrm->keyFrame1 = ifrm->keyFrame2; + ifrm->keyFrame2 = next; + // ... and next frame + next = (KeyFrameHeader*)((uint8*)this->nextFrame + + currentAnimKeyFrameSize); + this->nextFrame = next; + } + for(i = 0; i < this->numNodes; i++){ + ifrm = this->getInterpFrame(i); + this->interpCB(ifrm, ifrm->keyFrame1, ifrm->keyFrame2, + this->currentTime, + this->currentAnim->customData); + } +} + +} diff --git a/src/base.cpp b/src/base.cpp new file mode 100644 index 0000000..de3706b --- /dev/null +++ b/src/base.cpp @@ -0,0 +1,1120 @@ +#include +#include +#include +#include +#include +#include + +#define PSEP_C '/' +#define PSEP_S "/" +#ifdef __unix__ +#include +#include +#endif + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +namespace rw { + +#define PLUGIN_ID 0 + +int32 version = 0x36003; +int32 build = 0xFFFF; +#ifdef RW_PS2 + int32 platform = PLATFORM_PS2; +#elif RW_WDGL + int32 platform = PLATFORM_WDGL; +#elif RW_GL3 + int32 platform = PLATFORM_GL3; +#elif RW_D3D9 + int32 platform = PLATFORM_D3D9; +#else + int32 platform = PLATFORM_NULL; +#endif +bool32 streamAppendFrames = 0; +char *debugFile = nil; + +static Matrix identMat = { + { 1.0f, 0.0f, 0.0f }, Matrix::IDENTITY|Matrix::TYPEORTHONORMAL, + { 0.0f, 1.0f, 0.0f }, 0, + { 0.0f, 0.0f, 1.0f }, 0, + { 0.0f, 0.0f, 0.0f }, 0 +}; + +// lazy implementation +int +strcmp_ci(const char *s1, const char *s2) +{ + char c1, c2; + for(;;){ + c1 = tolower(*s1); + c2 = tolower(*s2); + if(c1 != c2) + return c1 - c2; + if(c1 == '\0') + return 0; + s1++; + s2++; + } +} + +int +strncmp_ci(const char *s1, const char *s2, int n) +{ + char c1, c2; + while(n--){ + c1 = tolower(*s1); + c2 = tolower(*s2); + if(c1 != c2) + return c1 - c2; + if(c1 == '\0') + return 0; + s1++; + s2++; + } + return 0; +} + +Quat +mult(const Quat &q, const Quat &p) +{ + return makeQuat(q.w*p.w - q.x*p.x - q.y*p.y - q.z*p.z, + q.w*p.x + q.x*p.w + q.y*p.z - q.z*p.y, + q.w*p.y + q.y*p.w + q.z*p.x - q.x*p.z, + q.w*p.z + q.z*p.w + q.x*p.y - q.y*p.x); +} + + +Quat* +Quat::rotate(const V3d *axis, float32 angle, CombineOp op) +{ + Quat rot = rotation(angle, *axis); + switch(op){ + case COMBINEREPLACE: + *this = rot; + break; + case COMBINEPRECONCAT: + *this = mult(*this, rot); + break; + case COMBINEPOSTCONCAT: + *this = mult(rot, *this); + break; + } + return this; +} + +Quat +lerp(const Quat &q, const Quat &p, float32 r) +{ + float32 c; + Quat q1 = q; + c = dot(q1, p); + if(c < 0.0f){ + c = -c; + q1 = negate(q1); + } + return makeQuat(q1.w + r*(p.w - q1.w), + q1.x + r*(p.x - q1.x), + q1.y + r*(p.y - q1.y), + q1.z + r*(p.z - q1.z)); +}; + +Quat +slerp(const Quat &q, const Quat &p, float32 a) +{ + float32 c; + Quat q1 = q; + c = dot(q1, p); + if(c < 0.0f){ + c = -c; + q1 = negate(q1); + } + float32 phi = acosf(c); + if(phi > 0.00001f){ + float32 s = sinf(phi); + return add(scale(q1, sinf((1.0f-a)*phi)/s), + scale(p, sinf(a*phi)/s)); + } + return q1; +} + +// +// V3d +// + +V3d +cross(const V3d &a, const V3d &b) +{ + return makeV3d(a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x); +} + +void +V3d::transformPoints(V3d *out, const V3d *in, int32 n, const Matrix *m) +{ + int32 i; + V3d tmp; + for(i = 0; i < n; i++){ + tmp.x = in[i].x*m->right.x + in[i].y*m->up.x + in[i].z*m->at.x + m->pos.x; + tmp.y = in[i].x*m->right.y + in[i].y*m->up.y + in[i].z*m->at.y + m->pos.y; + tmp.z = in[i].x*m->right.z + in[i].y*m->up.z + in[i].z*m->at.z + m->pos.z; + out[i] = tmp; + } +} + +void +V3d::transformVectors(V3d *out, const V3d *in, int32 n, const Matrix *m) +{ + int32 i; + V3d tmp; + for(i = 0; i < n; i++){ + tmp.x = in[i].x*m->right.x + in[i].y*m->up.x + in[i].z*m->at.x; + tmp.y = in[i].x*m->right.y + in[i].y*m->up.y + in[i].z*m->at.y; + tmp.z = in[i].x*m->right.z + in[i].y*m->up.z + in[i].z*m->at.z; + out[i] = tmp; + } +} + +// +// RawMatrix +// + +void +RawMatrix::mult(RawMatrix *dst, RawMatrix *src1, RawMatrix *src2) +{ + dst->right.x = src1->right.x*src2->right.x + src1->right.y*src2->up.x + src1->right.z*src2->at.x + src1->rightw*src2->pos.x; + dst->right.y = src1->right.x*src2->right.y + src1->right.y*src2->up.y + src1->right.z*src2->at.y + src1->rightw*src2->pos.y; + dst->right.z = src1->right.x*src2->right.z + src1->right.y*src2->up.z + src1->right.z*src2->at.z + src1->rightw*src2->pos.z; + dst->rightw = src1->right.x*src2->rightw + src1->right.y*src2->upw + src1->right.z*src2->atw + src1->rightw*src2->posw; + dst->up.x = src1->up.x*src2->right.x + src1->up.y*src2->up.x + src1->up.z*src2->at.x + src1->upw*src2->pos.x; + dst->up.y = src1->up.x*src2->right.y + src1->up.y*src2->up.y + src1->up.z*src2->at.y + src1->upw*src2->pos.y; + dst->up.z = src1->up.x*src2->right.z + src1->up.y*src2->up.z + src1->up.z*src2->at.z + src1->upw*src2->pos.z; + dst->upw = src1->up.x*src2->rightw + src1->up.y*src2->upw + src1->up.z*src2->atw + src1->upw*src2->posw; + dst->at.x = src1->at.x*src2->right.x + src1->at.y*src2->up.x + src1->at.z*src2->at.x + src1->atw*src2->pos.x; + dst->at.y = src1->at.x*src2->right.y + src1->at.y*src2->up.y + src1->at.z*src2->at.y + src1->atw*src2->pos.y; + dst->at.z = src1->at.x*src2->right.z + src1->at.y*src2->up.z + src1->at.z*src2->at.z + src1->atw*src2->pos.z; + dst->atw = src1->at.x*src2->rightw + src1->at.y*src2->upw + src1->at.z*src2->atw + src1->atw*src2->posw; + dst->pos.x = src1->pos.x*src2->right.x + src1->pos.y*src2->up.x + src1->pos.z*src2->at.x + src1->posw*src2->pos.x; + dst->pos.y = src1->pos.x*src2->right.y + src1->pos.y*src2->up.y + src1->pos.z*src2->at.y + src1->posw*src2->pos.y; + dst->pos.z = src1->pos.x*src2->right.z + src1->pos.y*src2->up.z + src1->pos.z*src2->at.z + src1->posw*src2->pos.z; + dst->posw = src1->pos.x*src2->rightw + src1->pos.y*src2->upw + src1->pos.z*src2->atw + src1->posw*src2->posw; +} + +void +RawMatrix::transpose(RawMatrix *dst, RawMatrix *src) +{ + dst->right.x = src->right.x; + dst->up.x = src->right.y; + dst->at.x = src->right.z; + dst->pos.x = src->rightw; + dst->right.y = src->up.x; + dst->up.y = src->up.y; + dst->at.y = src->up.z; + dst->pos.y = src->upw; + dst->right.z = src->at.x; + dst->up.z = src->at.y; + dst->at.z = src->at.z; + dst->pos.z = src->atw; + dst->rightw = src->pos.x; + dst->upw = src->pos.y; + dst->atw = src->pos.z; + dst->posw = src->posw; +} + +void +RawMatrix::setIdentity(RawMatrix *dst) +{ + static RawMatrix identity = { + { 1.0f, 0.0f, 0.0f }, 0.0f, + { 0.0f, 1.0f, 0.0f }, 0.0f, + { 0.0f, 0.0f, 1.0f }, 0.0f, + { 0.0f, 0.0f, 0.0f }, 1.0f + }; + *dst = identity; +} + +// +// Matrix +// + +static Matrix::Tolerance matrixDefaultTolerance = { 0.01f, 0.01f, 0.01f }; + +Matrix* +Matrix::create(void) +{ + Matrix *m = (Matrix*)rwMalloc(sizeof(Matrix), MEMDUR_EVENT | ID_MATRIX); + m->setIdentity(); + return m; +} + +void +Matrix::destroy(void) +{ + rwFree(this); +} + +void +Matrix::setIdentity(void) +{ + *this = identMat; +} + +void +Matrix::optimize(Tolerance *tolerance) +{ + bool32 isnormal, isorthogonal, isidentity; + if(tolerance == nil) + tolerance = &matrixDefaultTolerance; + isnormal = normalError() <= tolerance->normal; + isorthogonal = orthogonalError() <= tolerance->orthogonal; + isidentity = isnormal && isorthogonal && identityError() <= tolerance->identity; + if(isnormal) + flags |= TYPENORMAL; + else + flags &= ~TYPENORMAL; + if(isorthogonal) + flags |= TYPEORTHOGONAL; + else + flags &= ~TYPEORTHOGONAL; + if(isidentity) + flags |= IDENTITY; + else + flags &= ~IDENTITY; +} + +Matrix* +Matrix::mult(Matrix *dst, const Matrix *src1, const Matrix *src2) +{ + if(src1->flags & IDENTITY) + *dst = *src2; + else if(src2->flags & IDENTITY) + *dst = *src1; + else{ + mult_(dst, src1, src2); + dst->flags = src1->flags & src2->flags; + } + return dst; +} + +Matrix* +Matrix::invert(Matrix *dst, const Matrix *src) +{ + if(src->flags & IDENTITY) + *dst = *src; + else if((src->flags & TYPEMASK) == TYPEORTHONORMAL) + invertOrthonormal(dst, src); + else + return invertGeneral(dst, src); + return dst; +} + +// transpose the 3x3 submatrix, pos is set to 0 +Matrix* +Matrix::transpose(Matrix *dst, const Matrix *src) +{ + if(src->flags & IDENTITY) + *dst = *src; + dst->right.x = src->right.x; + dst->up.x = src->right.y; + dst->at.x = src->right.z; + dst->right.y = src->up.x; + dst->up.y = src->up.y; + dst->at.y = src->up.z; + dst->right.z = src->at.x; + dst->up.z = src->at.y; + dst->at.z = src->at.z; + dst->pos.x = 0.0; + dst->pos.y = 0.0; + dst->pos.z = 0.0; + return dst; +} + +Matrix* +Matrix::rotate(const V3d *axis, float32 angle, CombineOp op) +{ + Matrix tmp, rot; + makeRotation(&rot, axis, angle); + switch(op){ + case COMBINEREPLACE: + *this = rot; + break; + case COMBINEPRECONCAT: + mult(&tmp, &rot, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, &rot); + *this = tmp; + break; + } + return this; +} + +Matrix* +Matrix::rotate(const Quat &q, CombineOp op) +{ + Matrix tmp, rot; + makeRotation(&rot, q); + switch(op){ + case COMBINEREPLACE: + *this = rot; + break; + case COMBINEPRECONCAT: + mult(&tmp, &rot, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, &rot); + *this = tmp; + break; + } + return this; +} +Matrix* +Matrix::translate(const V3d *translation, CombineOp op) +{ + Matrix tmp; + Matrix trans = identMat; + trans.pos = *translation; + trans.flags &= ~IDENTITY; + switch(op){ + case COMBINEREPLACE: + *this = trans; + break; + case COMBINEPRECONCAT: + mult(&tmp, &trans, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, &trans); + *this = tmp; + break; + } + return this; +} + +Matrix* +Matrix::scale(const V3d *scale, CombineOp op) +{ + Matrix tmp; + Matrix scl = identMat; + scl.right.x = scale->x; + scl.up.y = scale->y; + scl.at.z = scale->z; + scl.flags &= ~IDENTITY; + switch(op){ + case COMBINEREPLACE: + *this = scl; + break; + case COMBINEPRECONCAT: + mult(&tmp, &scl, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, &scl); + *this = tmp; + break; + } + return this; +} + +Matrix* +Matrix::transform(const Matrix *mat, CombineOp op) +{ + Matrix tmp; + switch(op){ + case COMBINEREPLACE: + *this = *mat; + break; + case COMBINEPRECONCAT: + mult(&tmp, mat, this); + *this = tmp; + break; + case COMBINEPOSTCONCAT: + mult(&tmp, this, mat); + *this = tmp; + break; + } + return this; +} + +Quat +Matrix::getRotation(void) +{ + Quat q = { 0.0f, 0.0f, 0.0f, 1.0f }; + float32 tr = right.x + up.y + at.z; + float s; + if(tr > 0.0f){ + s = sqrtf(1.0f + tr) * 2.0f; + q.w = s / 4.0f; + q.x = (up.z - at.y) / s; + q.y = (at.x - right.z) / s; + q.z = (right.y - up.x) / s; + }else if(right.x > up.y && right.x > at.z){ + s = sqrtf(1.0f + right.x - up.y - at.z) * 2.0f; + q.w = (up.z - at.y) / s; + q.x = s / 4.0f; + q.y = (up.x + right.y) / s; + q.z = (at.x + right.z) / s; + }else if(up.y > at.z){ + s = sqrtf(1.0f + up.y - right.x - at.z) * 2.0f; + q.w = (at.x - right.z) / s; + q.x = (up.x + right.y) / s; + q.y = s / 4.0f; + q.z = (at.y + up.z) / s; + }else{ + s = sqrtf(1.0f + at.z - right.x - up.y) * 2.0f; + q.w = (right.y - up.x) / s; + q.x = (at.x + right.z) / s; + q.y = (at.y + up.z) / s; + q.z = s / 4.0f; + } + return q; +} + +void +Matrix::lookAt(const V3d &dir, const V3d &up) +{ + // this->right is really pointing left + this->at = normalize(dir); + this->right = normalize(cross(up, this->at)); + this->up = cross(this->at, this->right); + this->flags = TYPEORTHONORMAL; +} + +/* For a row-major representation, this calculates src1 * src2. + * For column-major src2 * src1. + * i.e. a vector is first xformed by src1, then by src2 + */ +void +Matrix::mult_(Matrix *dst, const Matrix *src1, const Matrix *src2) +{ + dst->right.x = src1->right.x*src2->right.x + src1->right.y*src2->up.x + src1->right.z*src2->at.x; + dst->right.y = src1->right.x*src2->right.y + src1->right.y*src2->up.y + src1->right.z*src2->at.y; + dst->right.z = src1->right.x*src2->right.z + src1->right.y*src2->up.z + src1->right.z*src2->at.z; + dst->up.x = src1->up.x*src2->right.x + src1->up.y*src2->up.x + src1->up.z*src2->at.x; + dst->up.y = src1->up.x*src2->right.y + src1->up.y*src2->up.y + src1->up.z*src2->at.y; + dst->up.z = src1->up.x*src2->right.z + src1->up.y*src2->up.z + src1->up.z*src2->at.z; + dst->at.x = src1->at.x*src2->right.x + src1->at.y*src2->up.x + src1->at.z*src2->at.x; + dst->at.y = src1->at.x*src2->right.y + src1->at.y*src2->up.y + src1->at.z*src2->at.y; + dst->at.z = src1->at.x*src2->right.z + src1->at.y*src2->up.z + src1->at.z*src2->at.z; + dst->pos.x = src1->pos.x*src2->right.x + src1->pos.y*src2->up.x + src1->pos.z*src2->at.x + src2->pos.x; + dst->pos.y = src1->pos.x*src2->right.y + src1->pos.y*src2->up.y + src1->pos.z*src2->at.y + src2->pos.y; + dst->pos.z = src1->pos.x*src2->right.z + src1->pos.y*src2->up.z + src1->pos.z*src2->at.z + src2->pos.z; +} + +void +Matrix::invertOrthonormal(Matrix *dst, const Matrix *src) +{ + dst->right.x = src->right.x; + dst->right.y = src->up.x; + dst->right.z = src->at.x; + dst->up.x = src->right.y; + dst->up.y = src->up.y; + dst->up.z = src->at.y; + dst->at.x = src->right.z; + dst->at.y = src->up.z; + dst->at.z = src->at.z; + dst->pos.x = -(src->pos.x*src->right.x + + src->pos.y*src->right.y + + src->pos.z*src->right.z); + dst->pos.y = -(src->pos.x*src->up.x + + src->pos.y*src->up.y + + src->pos.z*src->up.z); + dst->pos.z = -(src->pos.x*src->at.x + + src->pos.y*src->at.y + + src->pos.z*src->at.z); + dst->flags = TYPEORTHONORMAL; +} + +Matrix* +Matrix::invertGeneral(Matrix *dst, const Matrix *src) +{ + float32 det, invdet; + // calculate a few cofactors + dst->right.x = src->up.y*src->at.z - src->up.z*src->at.y; + dst->right.y = src->at.y*src->right.z - src->at.z*src->right.y; + dst->right.z = src->right.y*src->up.z - src->right.z*src->up.y; + // get the determinant from that + det = src->up.x * dst->right.y + src->at.x * dst->right.z + dst->right.x * src->right.x; + invdet = 1.0; + if(det != 0.0f) + invdet = 1.0f/det; + dst->right.x *= invdet; + dst->right.y *= invdet; + dst->right.z *= invdet; + dst->up.x = invdet * (src->up.z*src->at.x - src->up.x*src->at.z); + dst->up.y = invdet * (src->at.z*src->right.x - src->at.x*src->right.z); + dst->up.z = invdet * (src->right.z*src->up.x - src->right.x*src->up.z); + dst->at.x = invdet * (src->up.x*src->at.y - src->up.y*src->at.x); + dst->at.y = invdet * (src->at.x*src->right.y - src->at.y*src->right.x); + dst->at.z = invdet * (src->right.x*src->up.y - src->right.y*src->up.x); + dst->pos.x = -(src->pos.x*dst->right.x + src->pos.y*dst->up.x + src->pos.z*dst->at.x); + dst->pos.y = -(src->pos.x*dst->right.y + src->pos.y*dst->up.y + src->pos.z*dst->at.y); + dst->pos.z = -(src->pos.x*dst->right.z + src->pos.y*dst->up.z + src->pos.z*dst->at.z); + dst->flags &= ~IDENTITY; + return dst; +} + +void +Matrix::makeRotation(Matrix *dst, const V3d *axis, float32 angle) +{ +// V3d v = normalize(*axis); + float32 len = dot(*axis, *axis); + if(len != 0.0f) len = 1.0f/sqrtf(len); + V3d v = rw::scale(*axis, len); + angle = angle*(float)M_PI/180.0f; + float32 s = sinf(angle); + float32 c = cosf(angle); + float32 t = 1.0f - c; + + dst->right.x = c + v.x*v.x*t; + dst->right.y = v.x*v.y*t + v.z*s; + dst->right.z = v.z*v.x*t - v.y*s; + dst->up.x = v.x*v.y*t - v.z*s; + dst->up.y = c + v.y*v.y*t; + dst->up.z = v.y*v.z*t + v.x*s; + dst->at.x = v.z*v.x*t + v.y*s; + dst->at.y = v.y*v.z*t - v.x*s; + dst->at.z = c + v.z*v.z*t; + dst->pos.x = 0.0; + dst->pos.y = 0.0; + dst->pos.z = 0.0; + dst->flags = TYPEORTHONORMAL; +} + +/* q must be normalized */ +void +Matrix::makeRotation(Matrix *dst, const Quat &q) +{ + float xx = q.x*q.x; + float yy = q.y*q.y; + float zz = q.z*q.z; + float yz = q.y*q.z; + float zx = q.z*q.x; + float xy = q.x*q.y; + float wx = q.w*q.x; + float wy = q.w*q.y; + float wz = q.w*q.z; + + dst->right.x = 1.0f - 2.0f*(yy + zz); + dst->right.y = 2.0f*(xy + wz); + dst->right.z = 2.0f*(zx - wy); + dst->up.x = 2.0f*(xy - wz); + dst->up.y = 1.0f - 2.0f*(xx + zz); + dst->up.z = 2.0f*(yz + wx); + dst->at.x = 2.0f*(zx + wy); + dst->at.y = 2.0f*(yz - wx); + dst->at.z = 1.0f - 2.0f*(xx + yy); + dst->pos.x = 0.0; + dst->pos.y = 0.0; + dst->pos.z = 0.0; + dst->flags = TYPEORTHONORMAL; +} + +float32 +Matrix::normalError(void) +{ + float32 x, y, z; + x = dot(right, right) - 1.0f; + y = dot(up, up) - 1.0f; + z = dot(at, at) - 1.0f; + return x*x + y*y + z*z; +} + +float32 +Matrix::orthogonalError(void) +{ + float32 x, y, z; + x = dot(at, up); + y = dot(at, right); + z = dot(up, right); + return x*x + y*y + z*z; +} + +float32 +Matrix::identityError(void) +{ + V3d r = { right.x-1.0f, right.y, right.z }; + V3d u = { up.x, up.y-1.0f, up.z }; + V3d a = { at.x, at.y, at.z-1.0f }; + return dot(r,r) + dot(u,u) + dot(a,a) + dot(pos,pos); +} + +void +correctPathCase(char *filename) +{ +#ifdef __unix__ + DIR *direct; + struct dirent *dirent; + + char *dir, *arg; + char copy[1024], sofar[1024] = "."; + strncpy(copy, filename, 1024); + arg = copy; + // hack for absolute paths + if(filename[0] == '/'){ + sofar[0] = '/'; + sofar[1] = '/'; + sofar[2] = '\0'; + arg++; + } + while(dir = strtok(arg, PSEP_S)){ + arg = nil; + if(direct = opendir(sofar), dir == nil) + return; + while(dirent = readdir(direct), dirent != nil) + if(strncmp_ci(dirent->d_name, dir, 1024) == 0){ + strncat(sofar, PSEP_S, 1024); + strncat(sofar, dirent->d_name, 1024); + break; + } + closedir(direct); + if(dirent == nil) + return; + } + strcpy(filename, sofar+2); +#endif +} + +void +makePath(char *filename) +{ + size_t len = strlen(filename); + for(size_t i = 0; i < len; i++) + if(filename[i] == '/' || filename[i] == '\\') + filename[i] = PSEP_C; +#ifdef __unix__ + correctPathCase(filename); +#endif +} + +void +memNative32_func(void *data, uint32 size) +{ + uint8 *bytes = (uint8*)data; + uint32 *words = (uint32*)data; + size >>= 2; + while(size--){ + *words++ = (uint32)bytes[0] | (uint32)bytes[1]<<8 | + (uint32)bytes[2]<<16 | (uint32)bytes[3]<<24; + bytes += 4; + } +} + +void +memNative16_func(void *data, uint32 size) +{ + uint8 *bytes = (uint8*)data; + uint16 *words = (uint16*)data; + size >>= 1; + while(size--){ + *words++ = (uint16)bytes[0] | (uint16)bytes[1]<<8; + bytes += 2; + } +} + +void +memLittle32_func(void *data, uint32 size) +{ + uint32 w; + uint8 *bytes = (uint8*)data; + uint32 *words = (uint32*)data; + size >>= 2; + while(size--){ + w = *words++; + *bytes++ = w; + *bytes++ = w >> 8; + *bytes++ = w >> 16; + *bytes++ = w >> 24; + } +} + +void +memLittle16_func(void *data, uint32 size) +{ + uint16 w; + uint8 *bytes = (uint8*)data; + uint16 *words = (uint16*)data; + size >>= 1; + while(size--){ + w = *words++; + *bytes++ = (uint8)w; + *bytes++ = w >> 8; + } +} + +uint32 +Stream::write32(const void *data, uint32 length) +{ +#ifdef BIGENDIAN + uint8 *src = (uint8*)data; + uint32 buf[256]; + int32 n, len; + for(len = length >>= 2; len > 0; len -= 256){ + n = len < 256 ? len : 256; + memcpy(buf, src, n*4); + memLittle16(buf, n*4); + write8(buf, n*4); + src += n*4; + } + return length; +#else + return write8(data, length); +#endif +} + +uint32 +Stream::write16(const void *data, uint32 length) +{ +#ifdef BIGENDIAN + uint8 *src = (uint8*)data; + uint16 buf[256]; + int32 n, len; + for(len = length >>= 1; len > 0; len -= 256){ + n = len < 256 ? len : 256; + memcpy(buf, src, n*2); + memLittle16(buf, n*2); + write8(buf, n*2); + src += n*2; + } + return length; +#else + return write8(data, length); +#endif +} + +uint32 +Stream::read32(void *data, uint32 length) +{ + uint32 ret; + ret = read8(data, length); + memNative32(data, length); + return ret; +} + +uint32 +Stream::read16(void *data, uint32 length) +{ + uint32 ret; + ret = read8(data, length); + memNative16(data, length); + return ret; +} + +int32 +Stream::writeI8(int8 val) +{ + return write8(&val, sizeof(int8)); +} + +int32 +Stream::writeU8(uint8 val) +{ + return write8(&val, sizeof(uint8)); +} + +int32 +Stream::writeI16(int16 val) +{ + return write16(&val, sizeof(int16)); +} + +int32 +Stream::writeU16(uint16 val) +{ + return write16(&val, sizeof(uint16)); +} + +int32 +Stream::writeI32(int32 val) +{ + return write32(&val, sizeof(int32)); +} + +int32 +Stream::writeU32(uint32 val) +{ + return write32(&val, sizeof(uint32)); +} + +int32 +Stream::writeF32(float32 val) +{ + return write32(&val, sizeof(float32)); +} + +int8 +Stream::readI8(void) +{ + int8 tmp; + read8(&tmp, sizeof(int8)); + return tmp; +} + +uint8 +Stream::readU8(void) +{ + uint8 tmp; + read8(&tmp, sizeof(uint8)); + return tmp; +} + +int16 +Stream::readI16(void) +{ + int16 tmp; + read16(&tmp, sizeof(int16)); + return tmp; +} + +uint16 +Stream::readU16(void) +{ + uint16 tmp; + read16(&tmp, sizeof(uint16)); + return tmp; +} + +int32 +Stream::readI32(void) +{ + int32 tmp; + read32(&tmp, sizeof(int32)); + return tmp; +} + +uint32 +Stream::readU32(void) +{ + uint32 tmp; + read32(&tmp, sizeof(uint32)); + return tmp; +} + +float32 +Stream::readF32(void) +{ + float32 tmp; + read32(&tmp, sizeof(float32)); + return tmp; +} + + + +void +StreamMemory::close(void) +{ +} + +uint32 +StreamMemory::write8(const void *data, uint32 len) +{ + if(this->eof()) + return 0; + uint32 l = len; + if(this->position+l > this->length){ + if(this->position+l > this->capacity) + l = this->capacity-this->position; + this->length = this->position+l; + } + memcpy(&this->data[this->position], data, l); + this->position += l; + if(len != l) + this->position = S_EOF; + return l; +} + +uint32 +StreamMemory::read8(void *data, uint32 len) +{ + if(this->eof()) + return 0; + uint32 l = len; + if(this->position+l > this->length) + l = this->length-this->position; + memcpy(data, &this->data[this->position], l); + this->position += l; + if(len != l) + this->position = S_EOF; + return l; +} + +void +StreamMemory::seek(int32 offset, int32 whence) +{ + if(whence == 0) + this->position = offset; + else if(whence == 1) + this->position += offset; + else + this->position = this->length-offset; + if(this->position > this->length){ + // TODO: ideally this would depend on the mode + if(this->position > this->capacity) + this->position = S_EOF; + else + this->length = this->position; + } +} + +uint32 +StreamMemory::tell(void) +{ + return this->position; +} + +bool +StreamMemory::eof(void) +{ + return this->position == S_EOF; +} + +StreamMemory* +StreamMemory::open(uint8 *data, uint32 length, uint32 capacity) +{ + this->data = data; + this->capacity = capacity; + this->length = length; + if(this->capacity < this->length) + this->capacity = this->length; + this->position = 0; + return this; +} + +uint32 +StreamMemory::getLength(void) +{ + return this->length; +} + + +StreamFile* +StreamFile::open(const char *path, const char *mode) +{ + assert(this->file == nil); + this->file = fopen(path, mode); + if(this->file == nil){ + RWERROR((ERR_FILE, path)); + return nil; + } + return this; +} + +void +StreamFile::close(void) +{ + assert(this->file); + fclose(this->file); + this->file = nil; +} + +uint32 +StreamFile::write8(const void *data, uint32 length) +{ + return (uint32)fwrite(data, 1, length, this->file); +} + +uint32 +StreamFile::read8(void *data, uint32 length) +{ + return (uint32)fread(data, 1, length, this->file); +} + +void +StreamFile::seek(int32 offset, int32 whence) +{ + fseek(this->file, offset, whence); +} + +uint32 +StreamFile::tell(void) +{ + return ftell(this->file); +} + +bool +StreamFile::eof(void) +{ + return ( feof(this->file) != 0 ); +} + +bool +writeChunkHeader(Stream *s, int32 type, int32 size) +{ + struct { + int32 type, size; + uint32 id; + } buf = { type, size, libraryIDPack(version, build) }; + s->write32(&buf, 12); + return true; +} + +bool +readChunkHeaderInfo(Stream *s, ChunkHeaderInfo *header) +{ + struct { + int32 type, size; + uint32 id; + } buf; + s->read32(&buf, 12); + if(s->eof()) + return false; + assert(header != nil); + header->type = buf.type; + header->length = buf.size; + header->version = libraryIDUnpackVersion(buf.id); + header->build = libraryIDUnpackBuild(buf.id); + return true; +} + +bool +findChunk(Stream *s, uint32 type, uint32 *length, uint32 *version) +{ + ChunkHeaderInfo header; + while(readChunkHeaderInfo(s, &header)){ + if(header.type == ID_NAOBJECT) + return false; + if(header.type == type){ + if(length) + *length = header.length; + if(version) + *version = header.version; + return true; + } + s->seek(header.length); + } + return false; +} + +int32 +findPointer(void *p, void **list, int32 num) +{ + int i; + for(i = 0; i < num; i++) + if(list[i] == p) + return i; + return -1; +} + +uint8* +getFileContents(const char *name, uint32 *len) +{ + FILE *cf = fopen(name, "rb"); + assert(cf != nil); + fseek(cf, 0, SEEK_END); + *len = ftell(cf); + fseek(cf, 0, SEEK_SET); + uint8 *data = rwNewT(uint8, *len, MEMDUR_EVENT); + fread(data, 1, *len, cf); + fclose(cf); + return data; +} + +} diff --git a/src/base.err b/src/base.err new file mode 100644 index 0000000..507b26c --- /dev/null +++ b/src/base.err @@ -0,0 +1,22 @@ +ECODE(ERR_GENERAL, + "Error: %s"), +ECODE(ERR_ALLOC, + "Couldn't allocate 0x%X bytes"), +ECODE(ERR_FILE, + "Couldn't open file %s"), +ECODE(ERR_CHUNK, + "Couldn't find chunk %s"), +ECODE(ERR_VERSION, + "Unsupported version %X"), +ECODE(ERR_PLATFORM, + "Unsupported platform %d"), +ECODE(ERR_ENGINEINIT, + "Engine could not be initialized"), +ECODE(ERR_ENGINEOPEN, + "Engine could not be opened"), +ECODE(ERR_ENGINESTART, + "Engine could not be started"), +ECODE(ERR_INVRASTER, + "Invalid raster format"), +ECODE(ERR_NOTEXTURE, + "Could not create texture") diff --git a/src/bmp.cpp b/src/bmp.cpp new file mode 100644 index 0000000..6f9f8c7 --- /dev/null +++ b/src/bmp.cpp @@ -0,0 +1,284 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 0 + +namespace rw { + +// NB: this has padding and cannot be streamed directly! +struct BMPheader +{ + uint16 magic; + uint32 size; + uint16 reserved[2]; + uint32 offset; + + bool32 read(Stream *stream); + void write(Stream *stream); +}; + +// This one is aligned and can be streamed directly +struct DIBheader +{ + uint32 headerSize; + int32 width; + int32 height; + int16 numPlanes; + int16 depth; + uint32 compression; + uint32 imgSize; + int32 hres; + int32 vres; + int32 paletteLen; + int32 numImportant; + // end of 40 btyes + + uint32 rmask, gmask, bmask, amask; +}; + +bool32 +BMPheader::read(Stream *stream) +{ + magic = stream->readU16(); + size = stream->readU32(); + reserved[0] = stream->readU16(); + reserved[1] = stream->readU16(); + offset = stream->readU32(); + return magic == 0x4D42; +} + +void +BMPheader::write(Stream *stream) +{ + + stream->writeU16(magic); + stream->writeU32(size); + stream->writeU16(reserved[0]); + stream->writeU16(reserved[1]); + stream->writeU32(offset); +} + +Image* +readBMP(const char *filename) +{ + ASSERTLITTLE; + Image *image; + uint32 length; + uint8 *data; + StreamMemory file; + int i, x, y; + + bool32 noalpha; + int pad; + + data = getFileContents(filename, &length); + if(data == nil) + return nil; + file.open(data, length); + + /* read headers */ + BMPheader bmp; + DIBheader dib; + if(!bmp.read(&file)) + goto lose; + file.read8(&dib, sizeof(dib)); + file.seek(dib.headerSize-sizeof(dib)); // skip the part of the header we're ignoring + if(dib.headerSize <= 16){ + dib.compression = 0; + dib.paletteLen = 0; + } + + noalpha = true; + + // Recognize 32 bit formats + if(dib.compression == 3){ + if(dib.rmask != 0xFF0000 || + dib.gmask != 0x00FF00 || + dib.bmask != 0x0000FF) + goto lose; + dib.compression = 0; + if(dib.headerSize > 52 && dib.amask == 0xFF000000) + noalpha = false; + } + + if(dib.compression != 0) + goto lose; + + image = Image::create(dib.width, dib.height, dib.depth); + image->allocate(); + + + if(image->palette){ + int len = 1<palette; + for(i = 0; i < len; i++){ + color[i][2] = file.readU8(); // blue + color[i][1] = file.readU8(); // green + color[i][0] = file.readU8(); // red + color[i][3] = file.readU8(); // alpha + if(noalpha) + color[i][3] = 0xFF; + } + } + + file.seek(bmp.offset, 0); + + pad = image->width*image->bpp % 4; + + uint8 *px, *line; + line = image->pixels + (image->height-1)*image->stride; + for(y = 0; y < image->height; y++){ + px = line; + for(x = 0; x < image->width; x++){ + switch(image->depth){ + case 4: + i = file.readU8();; + px[x+0] = (i>>4)&0xF; + px[x+1] = i&0xF; + x++; + break; + + case 8: + px[x] = file.readU8(); + break; + + case 16: + // TODO: what format is this even? and what does Image expect? + px[x*2 + 0] = file.readU8(); + px[x*2 + 1] = file.readU8(); + break; + + case 24: + px[x*3 + 2] = file.readU8(); + px[x*3 + 1] = file.readU8(); + px[x*3 + 0] = file.readU8(); + break; + + case 32: + px[x*4 + 2] = file.readU8(); + px[x*4 + 1] = file.readU8(); + px[x*4 + 0] = file.readU8(); + px[x*4 + 3] = file.readU8(); + if(noalpha) + px[x*4 + 3] = 0xFF; + break; + + default: + goto lose; + } + } + + line -= image->stride; + file.seek(pad); + } + + + file.close(); + rwFree(data); + return image; + +lose: + file.close(); + rwFree(data); + return nil; +} + +/* can't write alpha */ +void +writeBMP(Image *image, const char *filename) +{ + ASSERTLITTLE; + uint8 *p; + StreamFile file; + if(!file.open(filename, "wb")){ + RWERROR((ERR_FILE, filename)); + return; + } + + int32 pallen = image->depth > 8 ? 0 : + image->depth == 4 ? 16 : 256; + int32 stride = image->width*image->depth/8; + int32 depth = image->depth == 32 ? 24 : image->depth; + stride = stride+3 & ~3; + + // File headers + BMPheader bmp; + bmp.magic = 0x4D42; // BM + bmp.size = 0x36 + 4*pallen + image->height*stride; + bmp.reserved[0] = 0; + bmp.reserved[1] = 0; + bmp.offset = 0x36 + 4*pallen; + bmp.write(&file); + + DIBheader dib; + dib.headerSize = 0x28; + dib.width = image->width; + dib.height = image->height; + dib.numPlanes = 1; + dib.depth = depth; + dib.compression = 0; + dib.imgSize = 0; + dib.hres = 2835; // 72dpi + dib.vres = 2835; // 72dpi + dib.paletteLen = 0; + dib.numImportant = 0; + file.write8(&dib, dib.headerSize); + + for(int i = 0; i < pallen; i++){ + file.writeU8(image->palette[i*4+2]); + file.writeU8(image->palette[i*4+1]); + file.writeU8(image->palette[i*4+0]); + file.writeU8(0xFF); + } + + uint8 *line = image->pixels + (image->height-1)*image->stride; + int32 n; + for(int y = 0; y < image->height; y++){ + p = line; + for(int x = 0; x < image->width; x++){ + switch(image->depth){ + case 4: + file.writeU8((p[0]&0xF)<<4 | p[1]&0xF); + p += 2; + x++; + break; + case 8: + file.writeU8(*p++); + break; + case 16: + file.writeU8(p[0]); + file.writeU8(p[1]); + p += 2; + break; + case 24: + file.writeU8(p[2]); + file.writeU8(p[1]); + file.writeU8(p[0]); + p += 3; + break; + case 32: + file.writeU8(p[2]); + file.writeU8(p[1]); + file.writeU8(p[0]); + p += 4; + } + } + n = (p-line) % 4; + while(n--) + file.writeU8(0); + line -= image->stride; + } + + file.close(); +} + +} diff --git a/src/camera.cpp b/src/camera.cpp new file mode 100644 index 0000000..4a87ed3 --- /dev/null +++ b/src/camera.cpp @@ -0,0 +1,524 @@ +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_CAMERA + +namespace rw { + +int32 Camera::numAllocated; + +PluginList Camera::s_plglist(sizeof(Camera)); + +void +defaultBeginUpdateCB(Camera *cam) +{ + engine->currentCamera = cam; + Frame::syncDirty(); + engine->device.beginUpdate(cam); +} + +void +defaultEndUpdateCB(Camera *cam) +{ + engine->device.endUpdate(cam); +} + +static void +buildPlanes(Camera *cam) +{ + V3d *c = cam->frustumCorners; + FrustumPlane *p = cam->frustumPlanes; + V3d v51 = sub(c[1], c[5]); + V3d v73 = sub(c[3], c[7]); + + /* Far plane */ + p[0].plane.normal = cam->getFrame()->getLTM()->at; + p[0].plane.distance = dot(p[0].plane.normal, c[4]); + p[0].closestX = p[0].plane.normal.x < 0.0f ? 0 : 1; + p[0].closestY = p[0].plane.normal.y < 0.0f ? 0 : 1; + p[0].closestZ = p[0].plane.normal.z < 0.0f ? 0 : 1; + + /* Near plane */ + p[1].plane.normal = neg(p[0].plane.normal); + p[1].plane.distance = dot(p[1].plane.normal, c[0]); + p[1].closestX = p[1].plane.normal.x < 0.0f ? 0 : 1; + p[1].closestY = p[1].plane.normal.y < 0.0f ? 0 : 1; + p[1].closestZ = p[1].plane.normal.z < 0.0f ? 0 : 1; + + /* Right plane */ + p[2].plane.normal = normalize(cross(v51, + sub(c[6], c[5]))); + p[2].plane.distance = dot(p[2].plane.normal, c[1]); + p[2].closestX = p[2].plane.normal.x < 0.0f ? 0 : 1; + p[2].closestY = p[2].plane.normal.y < 0.0f ? 0 : 1; + p[2].closestZ = p[2].plane.normal.z < 0.0f ? 0 : 1; + + /* Top plane */ + p[3].plane.normal = normalize(cross(sub(c[4], c[5]), + v51)); + p[3].plane.distance = dot(p[3].plane.normal, c[1]); + p[3].closestX = p[3].plane.normal.x < 0.0f ? 0 : 1; + p[3].closestY = p[3].plane.normal.y < 0.0f ? 0 : 1; + p[3].closestZ = p[3].plane.normal.z < 0.0f ? 0 : 1; + + /* Left plane */ + p[4].plane.normal = normalize(cross(v73, + sub(c[4], c[7]))); + p[4].plane.distance = dot(p[4].plane.normal, c[3]); + p[4].closestX = p[4].plane.normal.x < 0.0f ? 0 : 1; + p[4].closestY = p[4].plane.normal.y < 0.0f ? 0 : 1; + p[4].closestZ = p[4].plane.normal.z < 0.0f ? 0 : 1; + + /* Bottom plane */ + p[5].plane.normal = normalize(cross(sub(c[6], c[7]), + v73)); + p[5].plane.distance = dot(p[5].plane.normal, c[3]); + p[5].closestX = p[5].plane.normal.x < 0.0f ? 0 : 1; + p[5].closestY = p[5].plane.normal.y < 0.0f ? 0 : 1; + p[5].closestZ = p[5].plane.normal.z < 0.0f ? 0 : 1; +} + +static void +buildClipPersp(Camera *cam) +{ + Matrix *ltm = cam->getFrame()->getLTM(); + + /* First we calculate the 4 points on the view window. */ + V3d up = scale(ltm->up, cam->viewWindow.y); + V3d left = scale(ltm->right, cam->viewWindow.x); + V3d *c = cam->frustumCorners; + c[0] = add(add(ltm->at, up), left); // top left + c[1] = sub(add(ltm->at, up), left); // top right + c[2] = sub(sub(ltm->at, up), left); // bottom right + c[3] = add(sub(ltm->at, up), left); // bottom left + + /* Now Calculate near and far corners. */ + V3d off = sub(scale(ltm->up, cam->viewOffset.y), + scale(ltm->right, cam->viewOffset.x)); + for(int32 i = 0; i < 4; i++){ + V3d corner = sub(cam->frustumCorners[i], off); + V3d pos = add(ltm->pos, off); + c[i] = add(scale(corner, cam->nearPlane), pos); + c[i+4] = add(scale(corner, cam->farPlane), pos); + } + + buildPlanes(cam); +} + +static void +buildClipParallel(Camera *cam) +{ + Matrix *ltm = cam->getFrame()->getLTM(); + float32 nearoffx = -(1.0f - cam->nearPlane)*cam->viewOffset.x; + float32 nearoffy = (1.0f - cam->nearPlane)*cam->viewOffset.y; + float32 faroffx = -(1.0f - cam->farPlane)*cam->viewOffset.x; + float32 faroffy = (1.0f - cam->farPlane)*cam->viewOffset.y; + + V3d *c = cam->frustumCorners; + c[0].x = nearoffx + cam->viewWindow.x; + c[0].y = nearoffy + cam->viewWindow.y; + c[0].z = cam->nearPlane; + + c[1].x = nearoffx - cam->viewWindow.x; + c[1].y = nearoffy + cam->viewWindow.y; + c[1].z = cam->nearPlane; + + c[2].x = nearoffx - cam->viewWindow.x; + c[2].y = nearoffy - cam->viewWindow.y; + c[2].z = cam->nearPlane; + + c[3].x = nearoffx + cam->viewWindow.x; + c[3].y = nearoffy - cam->viewWindow.y; + c[3].z = cam->nearPlane; + + c[4].x = faroffx + cam->viewWindow.x; + c[4].y = faroffy + cam->viewWindow.y; + c[4].z = cam->farPlane; + + c[5].x = faroffx - cam->viewWindow.x; + c[5].y = faroffy + cam->viewWindow.y; + c[5].z = cam->farPlane; + + c[6].x = faroffx - cam->viewWindow.x; + c[6].y = faroffy - cam->viewWindow.y; + c[6].z = cam->farPlane; + + c[7].x = faroffx + cam->viewWindow.x; + c[7].y = faroffy - cam->viewWindow.y; + c[7].z = cam->farPlane; + + V3d::transformPoints(c, c, 8, ltm); + + buildPlanes(cam); +} + +static void +cameraSync(ObjectWithFrame *obj) +{ + /* + * RW projection matrix looks like this: + * (cf. Camera View Matrix white paper) + * w = viewWindow width + * h = viewWindow height + * o = view offset + * + * perspective: + * 1/2w 0 ox/2w + 1/2 -ox/2w + * 0 -1/2h -oy/2h + 1/2 oy/2h + * 0 0 1 0 + * 0 0 1 0 + * + * parallel: + * 1/2w 0 ox/2w -ox/2w + 1/2 + * 0 -1/2h -oy/2h oy/2h + 1/2 + * 0 0 1 0 + * 0 0 0 1 + * + * The view matrix transforms from world to clip space, it is however + * not used for OpenGL or D3D since transformation to camera space + * and to clip space are handled by separate matrices there. + * On these platforms the two matrices are built in the platform's + * beginUpdate function. + * On the PS2 the z- and w-rows are the same and the + * 1/2 translation/shear is removed again on the VU1 by + * subtracting the w-row/2 from the x- and y-rows. + * + * perspective: + * 1/2w 0 ox/2w -ox/2w + * 0 -1/2h -oy/2h oy/2h + * 0 0 1 0 + * 0 0 1 0 + * + * parallel: + * 1/2w 0 ox/2w -ox/2w + * 0 -1/2h -oy/2h oy/2h + * 0 0 1 0 + * 0 0 0 1 + * + * RW builds this matrix directly without using explicit + * inversion and matrix multiplication. + */ + + Camera *cam = (Camera*)obj; + Matrix inv, proj; + Matrix::invertOrthonormal(&inv, cam->getFrame()->getLTM()); + + inv.right.x = -inv.right.x; + inv.up.x = -inv.up.x; + inv.at.x = -inv.at.x; + inv.pos.x = -inv.pos.x; + + float32 xscl = 1.0f/(2.0f*cam->viewWindow.x); + float32 yscl = 1.0f/(2.0f*cam->viewWindow.y); + + proj.flags = 0; + proj.right.x = xscl; + proj.right.y = 0.0f; + proj.right.z = 0.0f; + + proj.up.x = 0.0f; + proj.up.y = -yscl; + proj.up.z = 0.0f; + + if(cam->projection == Camera::PERSPECTIVE){ + proj.pos.x = -cam->viewOffset.x*xscl; + proj.pos.y = cam->viewOffset.y*yscl; + proj.pos.z = 0.0f; + + proj.at.x = -proj.pos.x + 0.5f; + proj.at.y = -proj.pos.y + 0.5f; + proj.at.z = 1.0f; + proj.optimize(); + Matrix::mult(&cam->viewMatrix, &inv, &proj); + buildClipPersp(cam); + }else{ + proj.at.x = cam->viewOffset.x*xscl; + proj.at.y = -cam->viewOffset.y*yscl; + proj.at.z = 1.0f; + + proj.pos.x = -proj.at.x + 0.5f; + proj.pos.y = -proj.at.y + 0.5f; + proj.pos.z = 0.0f; + proj.optimize(); + Matrix::mult(&cam->viewMatrix, &inv, &proj); + buildClipParallel(cam); + } + cam->frustumBoundBox.calculate(cam->frustumCorners, 8); +} + +void +worldBeginUpdateCB(Camera *cam) +{ + engine->currentWorld = cam->world; + cam->originalBeginUpdate(cam); +} + +void +worldEndUpdateCB(Camera *cam) +{ + cam->originalEndUpdate(cam); +} + +static void +worldCameraSync(ObjectWithFrame *obj) +{ + Camera *camera = (Camera*)obj; + camera->originalSync(obj); +} + +Camera* +Camera::create(void) +{ + Camera *cam = (Camera*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_CAMERA); + if(cam == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + cam->object.object.init(Camera::ID, 0); + cam->object.syncCB = cameraSync; + cam->beginUpdateCB = defaultBeginUpdateCB; + cam->endUpdateCB = defaultEndUpdateCB; + cam->viewWindow.set(1.0f, 1.0f); + cam->viewOffset.set(0.0f, 0.0f); + cam->nearPlane = 0.05f; + cam->farPlane = 10.0f; + cam->fogPlane = 5.0f; + cam->projection = Camera::PERSPECTIVE; + + cam->frameBuffer = nil; + cam->zBuffer = nil; + + // clump extension + cam->clump = nil; + cam->inClump.init(); + + // world extension + cam->world = nil; + cam->originalSync = cam->object.syncCB; + cam->originalBeginUpdate = cam->beginUpdateCB; + cam->originalEndUpdate = cam->endUpdateCB; + cam->object.syncCB = worldCameraSync; + cam->beginUpdateCB = worldBeginUpdateCB; + cam->endUpdateCB = worldEndUpdateCB; + + s_plglist.construct(cam); + return cam; +} + +Camera* +Camera::clone(void) +{ + Camera *cam = Camera::create(); + if(cam == nil) + return nil; + cam->object.object.copy(&this->object.object); + cam->setFrame(this->getFrame()); + cam->viewWindow = this->viewWindow; + cam->viewOffset = this->viewOffset; + cam->nearPlane = this->nearPlane; + cam->farPlane = this->farPlane; + cam->fogPlane = this->fogPlane; + cam->projection = this->projection; + + cam->frameBuffer = this->frameBuffer; + cam->zBuffer = this->zBuffer; + + if(this->world) + this->world->addCamera(cam); + + s_plglist.copy(cam, this); + return cam; +} + +void +Camera::destroy(void) +{ + s_plglist.destruct(this); + assert(this->clump == nil); + assert(this->world == nil); + this->setFrame(nil); + rwFree(this); + numAllocated--; +} + +void +Camera::clear(RGBA *col, uint32 mode) +{ + engine->device.clearCamera(this, col, mode); +} + +void +Camera::showRaster(uint32 flags) +{ + this->frameBuffer->show(flags); +} + +void +calczShiftScale(Camera *cam) +{ + float32 n = cam->nearPlane; + float32 f = cam->farPlane; + float32 N = engine->device.zNear; + float32 F = engine->device.zFar; + // RW does this + N += (F - N)/10000.0f; + F -= (F - N)/10000.0f; + if(cam->projection == Camera::PERSPECTIVE){ + cam->zScale = (N - F)*n*f/(f - n); + cam->zShift = (F*f - N*n)/(f - n); + }else{ + cam->zScale = (F - N)/(f -n); + cam->zShift = (N*f - F*n)/(f - n); + } +} + +void +Camera::setNearPlane(float32 near) +{ + this->nearPlane = near; + calczShiftScale(this); + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +void +Camera::setFarPlane(float32 far) +{ + this->farPlane = far; + calczShiftScale(this); + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +void +Camera::setViewWindow(const V2d *window) +{ + this->viewWindow = *window; + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +void +Camera::setViewOffset(const V2d *offset) +{ + this->viewOffset = *offset; + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +void +Camera::setProjection(int32 proj) +{ + this->projection = proj; + if(this->getFrame()) + this->getFrame()->updateObjects(); +} + +int32 +Camera::frustumTestSphere(const Sphere *s) const +{ + int32 res = SPHEREINSIDE; + const FrustumPlane *p = this->frustumPlanes; + for(int32 i = 0; i < 6; i++){ + float32 dist = dot(p->plane.normal, s->center) - p->plane.distance; + if(s->radius < dist) + return SPHEREOUTSIDE; + if(s->radius > -dist) + res = SPHEREBOUNDARY; + p++; + } + return res; +} + +struct CameraChunkData +{ + V2d viewWindow; + V2d viewOffset; + float32 nearPlane, farPlane; + float32 fogPlane; + int32 projection; +}; + +Camera* +Camera::streamRead(Stream *stream) +{ + CameraChunkData buf; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(&buf, sizeof(CameraChunkData)); + Camera *cam = Camera::create(); + cam->viewWindow = buf.viewWindow; + cam->viewOffset = buf.viewOffset; + cam->nearPlane = buf.nearPlane; + cam->farPlane = buf.farPlane; + cam->fogPlane = buf.fogPlane; + cam->projection = buf.projection; + if(s_plglist.streamRead(stream, cam)) + return cam; + cam->destroy(); + return nil; +} + +bool +Camera::streamWrite(Stream *stream) +{ + CameraChunkData buf; + writeChunkHeader(stream, ID_CAMERA, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, sizeof(CameraChunkData)); + buf.viewWindow = this->viewWindow; + buf.viewOffset = this->viewOffset; + buf.nearPlane = this->nearPlane; + buf.farPlane = this->farPlane; + buf.fogPlane = this->fogPlane; + buf.projection = this->projection; + stream->write32(&buf, sizeof(CameraChunkData)); + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Camera::streamGetSize(void) +{ + return 12 + sizeof(CameraChunkData) + 12 + + s_plglist.streamGetSize(this); +} + +// Assumes horizontal FOV for 4:3, but we convert to vertical FOV +void +Camera::setFOV(float32 hfov, float32 ratio) +{ + V2d v; + float w, h; + + w = (float)this->frameBuffer->width; + h = (float)this->frameBuffer->height; + if(w < 1 || h < 1){ + w = 1; + h = 1; + } + hfov = hfov*3.14159f/360.0f; // deg to rad and halved + + float ar1 = 4.0f/3.0f; + float ar2 = w/h; + float vfov = atanf(tanf(hfov/2) / ar1) *2; + hfov = atanf(tanf(vfov/2) * ar2) *2; + + float32 a = tanf(hfov); + v.set(a, a/ratio); + this->setViewWindow(&v); + v.set(0.0f, 0.0f); + this->setViewOffset(&v); +} + +} diff --git a/src/charset.cpp b/src/charset.cpp new file mode 100644 index 0000000..0d94be8 --- /dev/null +++ b/src/charset.cpp @@ -0,0 +1,236 @@ +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwrender.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwcharset.h" + +#include "ps2/rwps2.h" +#include "d3d/rwd3d.h" +#include "gl/rwgl3.h" + + +#define PLUGIN_ID 1000 // TODO: find a better ID + +#ifndef RW_NULL + +#ifdef RWHALFPIXEL +#define HALFPX (0.5f) +#else +#define HALFPX (0.0f) +#endif + +namespace rw { + +const uint8 fontbits[256*256] = { +#include "vgafont.inc" +}; + +#define NUMCHARS 100 +static uint16 *indices; +static RWDEVICE::Im2DVertex *vertices; +static int32 numChars; +static Raster *lastRaster; + +bool32 +Charset::open(void) +{ + if(indices || vertices) + return 0; + numChars = 0; + lastRaster = nil; + indices = rwNewT(uint16, NUMCHARS*6, MEMDUR_EVENT); + vertices = rwNewT(RWDEVICE::Im2DVertex, NUMCHARS*4, MEMDUR_EVENT); + if(indices == nil || vertices == nil){ + close(); + return 0; + } + return 1; +} + +void +Charset::close(void) +{ + rwFree(indices); + indices = nil; + rwFree(vertices); + vertices = nil; +} + +Charset* +Charset::create(const RGBA *foreground, const RGBA *background) +{ + Charset *charset = (Charset*)rwMalloc(sizeof(Charset), MEMDUR_EVENT); + if(charset == nil){ + RWERROR((ERR_ALLOC, sizeof(Charset))); + return nil; + } + charset->raster = nil; + if(charset->setColors(foreground, background) == nil){ + charset->destroy(); + return nil; + } + charset->desc.count = 256; + charset->desc.tileWidth = 28; + charset->desc.tileHeight = 10; + charset->desc.width = 9; + charset->desc.height = 16; + charset->desc.width_internal = 9; + charset->desc.height_internal = 16; + return charset; +} + +void +Charset::destroy(void) +{ + if(raster) + raster->destroy(); + rwFree(this); +} + +Charset* +Charset::setColors(const RGBA *foreground, const RGBA *background) +{ + Image *img = Image::create(256, 256, 8); + if(img == nil) + return nil; + img->pixels = (uint8*)fontbits; + img->stride = 256; + img->allocate(); + img->palette[0] = background->red; + img->palette[1] = background->green; + img->palette[2] = background->blue; + img->palette[3] = background->alpha; + img->palette[4] = foreground->red; + img->palette[5] = foreground->green; + img->palette[6] = foreground->blue; + img->palette[7] = foreground->alpha; + + Raster *newRaster = Raster::createFromImage(img); + img->destroy(); + if(newRaster == nil) + return nil; + if(this->raster) + this->raster->destroy(); + this->raster = newRaster; + return this; +} + +void +Charset::flushBuffer(void) +{ + if(numChars){ + rw::SetRenderStatePtr(rw::TEXTURERASTER, lastRaster); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + + im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, + vertices, numChars*4, indices, numChars*6); + } + + numChars = 0; + lastRaster = nil; +} + +void +Charset::printChar(int32 c, int32 x, int32 y) +{ + Camera *cam; + float recipZ; + float u, v, du, dv; + RWDEVICE::Im2DVertex *vert; + uint16 *ix; + + if(c >= this->desc.count) + return; + + if(this->raster != lastRaster || numChars >= NUMCHARS) + flushBuffer(); + lastRaster = this->raster; + + cam = (Camera*)engine->currentCamera; + vert = &vertices[numChars*4]; + ix = &indices[numChars*6]; + recipZ = 1.0f/cam->nearPlane; + + u = ((c % this->desc.tileWidth)*this->desc.width_internal + HALFPX) / (float32)this->raster->width; + v = ((c / this->desc.tileWidth)*this->desc.height_internal + HALFPX) / (float32)this->raster->height; + du = this->desc.width_internal/(float32)this->raster->width; + dv = this->desc.height_internal/(float32)this->raster->height; + + vert->setScreenX((float)x); + vert->setScreenY((float)y); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u, recipZ); + vert->setV(v, recipZ); + vert++; + + vert->setScreenX(float(x+this->desc.width_internal)); + vert->setScreenY((float)y); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u+du, recipZ); + vert->setV(v, recipZ); + vert++; + + vert->setScreenX((float)x); + vert->setScreenY(float(y+this->desc.height_internal)); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u, recipZ); + vert->setV(v+dv, recipZ); + vert++; + + vert->setScreenX(float(x+this->desc.width_internal)); + vert->setScreenY(float(y+this->desc.height_internal)); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u+du, recipZ); + vert->setV(v+dv, recipZ); + vert++; + + *ix++ = numChars*4; + *ix++ = numChars*4+1; + *ix++ = numChars*4+2; + *ix++ = numChars*4+2; + *ix++ = numChars*4+1; + *ix++ = numChars*4+3; + + numChars++; +} + +void +Charset::print(const char *str, int32 x, int32 y, bool32 hideSpaces) +{ + this->printBuffered(str, x, y, hideSpaces); + flushBuffer(); +} + +void +Charset::printBuffered(const char *str, int32 x, int32 y, bool32 hideSpaces) +{ + while(*str){ + if(!hideSpaces || *str != ' ') + printChar((uint8)*str, x, y); + x += this->desc.width; + str++; + } +} + +} + +#endif + diff --git a/src/clump.cpp b/src/clump.cpp new file mode 100644 index 0000000..f228298 --- /dev/null +++ b/src/clump.cpp @@ -0,0 +1,669 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 2 + +namespace rw { + +int32 Clump::numAllocated; +int32 Atomic::numAllocated; + +PluginList Clump::s_plglist(sizeof(Clump)); +PluginList Atomic::s_plglist(sizeof(Atomic)); + +// +// Clump +// + +Clump* +Clump::create(void) +{ + Clump *clump = (Clump*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_CLUMP); + if(clump == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + clump->object.init(Clump::ID, 0); + clump->atomics.init(); + clump->lights.init(); + clump->cameras.init(); + + // World extension + clump->world = nil; + clump->inWorld.init(); + + s_plglist.construct(clump); + return clump; +} + +Clump* +Clump::clone(void) +{ + Clump *clump = Clump::create(); + Frame *root = this->getFrame()->cloneAndLink(); + clump->setFrame(root); + FORLIST(lnk, this->atomics){ + Atomic *a = Atomic::fromClump(lnk); + Atomic *atomic = a->clone(); + atomic->setFrame(a->getFrame()->root); + clump->addAtomic(atomic); + } + this->getFrame()->purgeClone(); + + // World extension + if(this->world) + this->world->addClump(clump); + + s_plglist.copy(clump, this); + return clump; +} + +void +Clump::destroy(void) +{ + Frame *f; + s_plglist.destruct(this); + FORLIST(lnk, this->atomics){ + Atomic *a = Atomic::fromClump(lnk); + this->removeAtomic(a); + a->destroy(); + } + FORLIST(lnk, this->lights){ + Light *l = Light::fromClump(lnk); + this->removeLight(l); + l->destroy(); + } + FORLIST(lnk, this->cameras){ + Camera *c = Camera::fromClump(lnk); + this->removeCamera(c); + c->destroy(); + } + if(f = this->getFrame(), f) + f->destroyHierarchy(); + assert(this->world == nil); + rwFree(this); + numAllocated--; +} + +void +Clump::addAtomic(Atomic *a) +{ + assert(a->clump == nil); + a->clump = this; + this->atomics.append(&a->inClump); +} + +void +Clump::removeAtomic(Atomic *a) +{ + assert(a->clump == this); + a->inClump.remove(); + a->clump = nil; +} + +void +Clump::addLight(Light *l) +{ + assert(l->clump == nil); + l->clump = this; + this->lights.append(&l->inClump); +} + +void +Clump::removeLight(Light *l) +{ + assert(l->clump == this); + l->inClump.remove(); + l->clump = nil; +} + +void +Clump::addCamera(Camera *c) +{ + assert(c->clump == nil); + c->clump = this; + this->cameras.append(&c->inClump); +} + +void +Clump::removeCamera(Camera *c) +{ + assert(c->clump == this); + c->inClump.remove(); + c->clump = nil; +} + + +Clump* +Clump::streamRead(Stream *stream) +{ + uint32 length, version; + int32 buf[3]; + Clump *clump; + int32 numGeometries; + Geometry **geometryList; + + if(!findChunk(stream, ID_STRUCT, &length, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(buf, length); + int32 numAtomics = buf[0]; + int32 numLights = 0; + int32 numCameras = 0; + if(version > 0x33000){ + numLights = buf[1]; + numCameras = buf[2]; + } + clump = Clump::create(); + if(clump == nil) + return nil; + + // Frame list + FrameList_ frmlst; + frmlst.frames = nil; + if(!findChunk(stream, ID_FRAMELIST, nil, nil)){ + RWERROR((ERR_CHUNK, "FRAMELIST")); + goto fail; + } + if(frmlst.streamRead(stream) == nil) + goto fail; + clump->setFrame(frmlst.frames[0]); + + // Geometry list + numGeometries = 0; + geometryList = nil; + if(version >= 0x30400){ + if(!findChunk(stream, ID_GEOMETRYLIST, nil, nil)){ + RWERROR((ERR_CHUNK, "GEOMETRYLIST")); + goto fail; + } + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + numGeometries = stream->readI32(); + if(numGeometries){ + size_t sz = numGeometries*sizeof(Geometry*); + geometryList = (Geometry**)rwMalloc(sz, MEMDUR_FUNCTION | ID_CLUMP); + if(geometryList == nil){ + RWERROR((ERR_ALLOC, sz)); + goto fail; + } + memset(geometryList, 0, sz); + } + for(int32 i = 0; i < numGeometries; i++){ + if(!findChunk(stream, ID_GEOMETRY, nil, nil)){ + RWERROR((ERR_CHUNK, "GEOMETRY")); + goto failgeo; + } + geometryList[i] = Geometry::streamRead(stream); + if(geometryList[i] == nil) + goto failgeo; + } + } + + // Atomics + Atomic *a; + for(int32 i = 0; i < numAtomics; i++){ + if(!findChunk(stream, ID_ATOMIC, nil, nil)){ + RWERROR((ERR_CHUNK, "ATOMIC")); + goto failgeo; + } + a = Atomic::streamReadClump(stream, &frmlst, geometryList); + if(a == nil) + goto failgeo; + clump->addAtomic(a); + } + + // Lights + int32 frm; + Light *l; + for(int32 i = 0; i < numLights; i++){ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto failgeo; + } + frm = stream->readI32(); + if(!findChunk(stream, ID_LIGHT, nil, nil)){ + RWERROR((ERR_CHUNK, "LIGHT")); + goto failgeo; + } + l = Light::streamRead(stream); + if(l == nil) + goto failgeo; + l->setFrame(frmlst.frames[frm]); + clump->addLight(l); + } + + // Cameras + Camera *cam; + for(int32 i = 0; i < numCameras; i++){ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto failgeo; + } + frm = stream->readI32(); + if(!findChunk(stream, ID_CAMERA, nil, nil)){ + RWERROR((ERR_CHUNK, "CAMERA")); + goto failgeo; + } + cam = Camera::streamRead(stream); + if(cam == nil) + goto failgeo; + cam->setFrame(frmlst.frames[frm]); + clump->addCamera(cam); + } + + for(int32 i = 0; i < numGeometries; i++) + if(geometryList[i]) + geometryList[i]->destroy(); + rwFree(geometryList); + rwFree(frmlst.frames); + if(s_plglist.streamRead(stream, clump)) + return clump; + +failgeo: + for(int32 i = 0; i < numGeometries; i++) + if(geometryList[i]) + geometryList[i]->destroy(); + rwFree(geometryList); +fail: + rwFree(frmlst.frames); + clump->destroy(); + return nil; +} + +bool +Clump::streamWrite(Stream *stream) +{ + int size = this->streamGetSize(); + writeChunkHeader(stream, ID_CLUMP, size); + int32 numAtomics = this->countAtomics(); + int32 numLights = this->countLights(); + int32 numCameras = this->countCameras(); + int32 buf[3] = { numAtomics, numLights, numCameras }; + size = version > 0x33000 ? 12 : 4; + writeChunkHeader(stream, ID_STRUCT, size); + stream->write32(buf, size); + + FrameList_ frmlst; + frmlst.numFrames = this->getFrame()->count(); + frmlst.frames = (Frame**)rwMalloc(frmlst.numFrames*sizeof(Frame*), MEMDUR_FUNCTION | ID_CLUMP); + makeFrameList(this->getFrame(), frmlst.frames); + frmlst.streamWrite(stream); + + if(rw::version >= 0x30400){ + size = 12+4; + FORLIST(lnk, this->atomics) + size += 12 + Atomic::fromClump(lnk)->geometry->streamGetSize(); + writeChunkHeader(stream, ID_GEOMETRYLIST, size); + writeChunkHeader(stream, ID_STRUCT, 4); + stream->writeI32(numAtomics); // same as numGeometries + FORLIST(lnk, this->atomics) + Atomic::fromClump(lnk)->geometry->streamWrite(stream); + } + + FORLIST(lnk, this->atomics) + Atomic::fromClump(lnk)->streamWriteClump(stream, &frmlst); + + FORLIST(lnk, this->lights){ + Light *l = Light::fromClump(lnk); + int frm = findPointer(l->getFrame(), (void**)frmlst.frames, frmlst.numFrames); + if(frm < 0) + return false; + writeChunkHeader(stream, ID_STRUCT, 4); + stream->writeI32(frm); + l->streamWrite(stream); + } + + FORLIST(lnk, this->cameras){ + Camera *c = Camera::fromClump(lnk); + int frm = findPointer(c->getFrame(), (void**)frmlst.frames, frmlst.numFrames); + if(frm < 0) + return false; + writeChunkHeader(stream, ID_STRUCT, 4); + stream->writeI32(frm); + c->streamWrite(stream); + } + + rwFree(frmlst.frames); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Clump::streamGetSize(void) +{ + uint32 size = 0; + size += 12; // Struct + size += 4; // numAtomics + if(version > 0x33000) + size += 8; // numLights, numCameras + + // Frame list + size += FrameList_::streamGetSize(this->getFrame()); + + if(rw::version >= 0x30400){ + // Geometry list + size += 12 + 12 + 4; + FORLIST(lnk, this->atomics) + size += 12 + Atomic::fromClump(lnk)->geometry->streamGetSize(); + } + + // Atomics + FORLIST(lnk, this->atomics) + size += 12 + Atomic::fromClump(lnk)->streamGetSize(); + + // Lights + FORLIST(lnk, this->lights) + size += 16 + 12 + Light::fromClump(lnk)->streamGetSize(); + + // Cameras + FORLIST(lnk, this->cameras) + size += 16 + 12 + Camera::fromClump(lnk)->streamGetSize(); + + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +void +Clump::render(void) +{ + Atomic *a; + FORLIST(lnk, this->atomics){ + a = Atomic::fromClump(lnk); + if(a->object.object.flags & Atomic::RENDER) + a->render(); + } +} + +// +// Atomic +// + +static void +atomicSync(ObjectWithFrame *obj) +{ + // TODO: interpolate + obj->object.privateFlags |= Atomic::WORLDBOUNDDIRTY; +} + + +static void +worldAtomicSync(ObjectWithFrame *obj) +{ + Atomic *atomic = (Atomic*)obj; + atomic->originalSync(obj); +} + +Atomic* +Atomic::create(void) +{ + Atomic *atomic = (Atomic*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_ATOMIC); + if(atomic == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + atomic->object.object.init(Atomic::ID, 0); + atomic->object.syncCB = atomicSync; + atomic->geometry = nil; + atomic->boundingSphere.center.set(0.0f, 0.0f, 0.0f); + atomic->boundingSphere.radius = 0.0f; + atomic->worldBoundingSphere.center.set(0.0f, 0.0f, 0.0f); + atomic->worldBoundingSphere.radius = 0.0f; + atomic->setFrame(nil); + atomic->object.object.privateFlags |= WORLDBOUNDDIRTY; + atomic->clump = nil; + atomic->inClump.init(); + atomic->pipeline = nil; + atomic->renderCB = Atomic::defaultRenderCB; + atomic->object.object.flags = Atomic::COLLISIONTEST | Atomic::RENDER; + // TODO: interpolator + + // World extension + atomic->world = nil; + atomic->originalSync = atomic->object.syncCB; + atomic->object.syncCB = worldAtomicSync; + + s_plglist.construct(atomic); + return atomic; +} + +Atomic* +Atomic::clone() +{ + Atomic *atomic = Atomic::create(); + if(atomic == nil) + return nil; + atomic->object.object.copy(&this->object.object); + atomic->object.object.privateFlags |= WORLDBOUNDDIRTY; + if(this->geometry) + atomic->setGeometry(this->geometry, 0); + atomic->renderCB = this->renderCB; + atomic->pipeline = this->pipeline; + + // World extension doesn't add to world + + s_plglist.copy(atomic, this); + return atomic; +} + +void +Atomic::destroy(void) +{ + s_plglist.destruct(this); + if(this->geometry) + this->geometry->destroy(); + assert(this->clump == nil); + assert(this->world == nil); + this->setFrame(nil); + rwFree(this); + numAllocated--; +} + +void +Atomic::setGeometry(Geometry *geo, uint32 flags) +{ + if(this->geometry) + this->geometry->destroy(); + if(geo) + geo->addRef(); + this->geometry = geo; + if(flags & SAMEBOUNDINGSPHERE) + return; + if(geo){ + this->boundingSphere = geo->morphTargets[0].boundingSphere; + if(this->getFrame()) // TODO: && getWorld??? + this->getFrame()->updateObjects(); + } +} + +Sphere* +Atomic::getWorldBoundingSphere(void) +{ + Sphere *s = &this->worldBoundingSphere; + // TODO: if we ever support morphing, check interpolation + if(!this->getFrame()->dirty() && + (this->object.object.privateFlags & WORLDBOUNDDIRTY) == 0) + return s; + Matrix *ltm = this->getFrame()->getLTM(); + // TODO: support scaling + V3d::transformPoints(&s->center, &this->boundingSphere.center, 1, ltm); + s->radius = this->boundingSphere.radius; + this->object.object.privateFlags &= ~WORLDBOUNDDIRTY; + return s; +} + +static uint32 atomicRights[2]; + +Atomic* +Atomic::streamReadClump(Stream *stream, + FrameList_ *frameList, Geometry **geometryList) +{ + int32 buf[4]; + uint32 version; + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(buf, version < 0x30400 ? 12 : 16); + Atomic *atomic = Atomic::create(); + if(atomic == nil) + return nil; + atomic->setFrame(frameList->frames[buf[0]]); + Geometry *g; + if(version < 0x30400){ + if(!findChunk(stream, ID_GEOMETRY, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + g = Geometry::streamRead(stream); + if(g == nil) + goto fail; + atomic->setGeometry(g, 0); + g->destroy(); + }else + atomic->setGeometry(geometryList[buf[1]], 0); + atomic->object.object.flags = buf[2]; + + atomicRights[0] = 0; + if(!s_plglist.streamRead(stream, atomic)) + goto fail; + if(atomicRights[0]) + s_plglist.assertRights(atomic, atomicRights[0], atomicRights[1]); + return atomic; + +fail: + atomic->destroy(); + return nil; +} + +bool +Atomic::streamWriteClump(Stream *stream, FrameList_ *frmlst) +{ + int32 buf[4] = { 0, 0, 0, 0 }; + Clump *c = this->clump; + if(c == nil) + return false; + writeChunkHeader(stream, ID_ATOMIC, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, rw::version < 0x30400 ? 12 : 16); + buf[0] = findPointer(this->getFrame(), (void**)frmlst->frames, frmlst->numFrames); + + if(version < 0x30400){ + buf[1] = this->object.object.flags; + stream->write32(buf, sizeof(int32[3])); + this->geometry->streamWrite(stream); + }else{ + buf[1] = 0; + FORLIST(lnk, c->atomics){ + if(Atomic::fromClump(lnk)->geometry == this->geometry) + goto foundgeo; + buf[1]++; + } + return false; + foundgeo: + buf[2] = this->object.object.flags; + stream->write32(buf, sizeof(buf)); + } + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Atomic::streamGetSize(void) +{ + uint32 size = 12 + 12 + 12 + s_plglist.streamGetSize(this); + if(rw::version < 0x30400) + size += 12 + this->geometry->streamGetSize(); + else + size += 4; + return size; +} + +ObjPipeline* +Atomic::getPipeline(void) +{ + return this->pipeline ? + this->pipeline : + engine->driver[platform]->defaultPipeline; +} + +void +Atomic::instance(void) +{ + if(this->geometry->flags & Geometry::NATIVE) + return; + this->getPipeline()->instance(this); + this->geometry->flags |= Geometry::NATIVE; +} + +void +Atomic::uninstance(void) +{ + if(!(this->geometry->flags & Geometry::NATIVE)) + return; + this->getPipeline()->uninstance(this); + // this should be done by the CB already, just make sure + this->geometry->flags &= ~Geometry::NATIVE; +} + +void +Atomic::defaultRenderCB(Atomic *atomic) +{ + atomic->getPipeline()->render(atomic); +} + +// Atomic Rights plugin + +static Stream* +readAtomicRights(Stream *stream, int32, void *, int32, int32) +{ + stream->read32(atomicRights, 8); + return stream; +} + +static Stream* +writeAtomicRights(Stream *stream, int32, void *object, int32, int32) +{ + Atomic *atomic = (Atomic*)object; + uint32 buffer[2]; + buffer[0] = atomic->pipeline->pluginID; + buffer[1] = atomic->pipeline->pluginData; + stream->write32(buffer, 8); + return stream; +} + +static int32 +getSizeAtomicRights(void *object, int32, int32) +{ + Atomic *atomic = (Atomic*)object; + if(atomic->pipeline == nil || atomic->pipeline->pluginID == 0) + return 0; + return 8; +} + +void +registerAtomicRightsPlugin(void) +{ + Atomic::registerPlugin(0, ID_RIGHTTORENDER, nil, nil, nil); + Atomic::registerPluginStream(ID_RIGHTTORENDER, + readAtomicRights, + writeAtomicRights, + getSizeAtomicRights); +} + +} diff --git a/src/d3d/d3d.cpp b/src/d3d/d3d.cpp new file mode 100644 index 0000000..1545934 --- /dev/null +++ b/src/d3d/d3d.cpp @@ -0,0 +1,1108 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3dimpl.h" + +#define PLUGIN_ID ID_DRIVER + +namespace rw { +namespace d3d { + +bool32 isP8supported = 1; + +#ifndef RW_D3D9 +#define MAKEFOURCC(ch0, ch1, ch2, ch3) \ + ((uint32)(uint8)(ch0) | ((uint32)(uint8)(ch1) << 8) | \ + ((uint32)(uint8)(ch2) << 16) | ((uint32)(uint8)(ch3) << 24 )) +enum { + D3DFMT_UNKNOWN = 0, + + D3DFMT_R8G8B8 = 20, + D3DFMT_A8R8G8B8 = 21, + D3DFMT_X8R8G8B8 = 22, + D3DFMT_R5G6B5 = 23, + D3DFMT_X1R5G5B5 = 24, + D3DFMT_A1R5G5B5 = 25, + D3DFMT_A4R4G4B4 = 26, + D3DFMT_R3G3B2 = 27, + D3DFMT_A8 = 28, + D3DFMT_A8R3G3B2 = 29, + D3DFMT_X4R4G4B4 = 30, + D3DFMT_A2B10G10R10 = 31, + D3DFMT_A8B8G8R8 = 32, + D3DFMT_X8B8G8R8 = 33, + D3DFMT_G16R16 = 34, + D3DFMT_A2R10G10B10 = 35, + D3DFMT_A16B16G16R16 = 36, + + D3DFMT_A8P8 = 40, + D3DFMT_P8 = 41, + + D3DFMT_L8 = 50, + D3DFMT_A8L8 = 51, + D3DFMT_A4L4 = 52, + + D3DFMT_V8U8 = 60, + D3DFMT_L6V5U5 = 61, + D3DFMT_X8L8V8U8 = 62, + D3DFMT_Q8W8V8U8 = 63, + D3DFMT_V16U16 = 64, + D3DFMT_A2W10V10U10 = 67, + + D3DFMT_UYVY = MAKEFOURCC('U', 'Y', 'V', 'Y'), + D3DFMT_R8G8_B8G8 = MAKEFOURCC('R', 'G', 'B', 'G'), + D3DFMT_YUY2 = MAKEFOURCC('Y', 'U', 'Y', '2'), + D3DFMT_G8R8_G8B8 = MAKEFOURCC('G', 'R', 'G', 'B'), + D3DFMT_DXT1 = MAKEFOURCC('D', 'X', 'T', '1'), + D3DFMT_DXT2 = MAKEFOURCC('D', 'X', 'T', '2'), + D3DFMT_DXT3 = MAKEFOURCC('D', 'X', 'T', '3'), + D3DFMT_DXT4 = MAKEFOURCC('D', 'X', 'T', '4'), + D3DFMT_DXT5 = MAKEFOURCC('D', 'X', 'T', '5'), + + D3DFMT_D16_LOCKABLE = 70, + D3DFMT_D32 = 71, + D3DFMT_D15S1 = 73, + D3DFMT_D24S8 = 75, + D3DFMT_D24X8 = 77, + D3DFMT_D24X4S4 = 79, + D3DFMT_D16 = 80, + + D3DFMT_D32F_LOCKABLE = 82, + D3DFMT_D24FS8 = 83, + + // d3d9ex only + /* Z-Stencil formats valid for CPU access */ + D3DFMT_D32_LOCKABLE = 84, + D3DFMT_S8_LOCKABLE = 85, + + D3DFMT_L16 = 81, + + D3DFMT_VERTEXDATA =100, + D3DFMT_INDEX16 =101, + D3DFMT_INDEX32 =102, + + D3DFMT_Q16W16V16U16 =110, + + D3DFMT_MULTI2_ARGB8 = MAKEFOURCC('M','E','T','1'), + + // Floating point surface formats + + // s10e5 formats (16-bits per channel) + D3DFMT_R16F = 111, + D3DFMT_G16R16F = 112, + D3DFMT_A16B16G16R16F = 113, + + // IEEE s23e8 formats (32-bits per channel) + D3DFMT_R32F = 114, + D3DFMT_G32R32F = 115, + D3DFMT_A32B32G32R32F = 116, + + D3DFMT_CxV8U8 = 117, + + // d3d9ex only + // Monochrome 1 bit per pixel format + D3DFMT_A1 = 118, + // 2.8 biased fixed point + D3DFMT_A2B10G10R10_XR_BIAS = 119, + // Binary format indicating that the data has no inherent type + D3DFMT_BINARYBUFFER = 199 +}; +#endif + +// stolen from d3d8to9 +static uint32 +calculateTextureSize(uint32 width, uint32 height, uint32 depth, uint32 format) +{ +#define D3DFMT_W11V11U10 65 + switch(format){ + default: + case D3DFMT_UNKNOWN: + return 0; + case D3DFMT_R3G3B2: + case D3DFMT_A8: + case D3DFMT_P8: + case D3DFMT_L8: + case D3DFMT_A4L4: + return width * height * depth; + case D3DFMT_R5G6B5: + case D3DFMT_X1R5G5B5: + case D3DFMT_A1R5G5B5: + case D3DFMT_A4R4G4B4: + case D3DFMT_A8R3G3B2: + case D3DFMT_X4R4G4B4: + case D3DFMT_A8P8: + case D3DFMT_A8L8: + case D3DFMT_V8U8: + case D3DFMT_L6V5U5: + case D3DFMT_D16_LOCKABLE: + case D3DFMT_D15S1: + case D3DFMT_D16: + case D3DFMT_UYVY: + case D3DFMT_YUY2: + return width * 2 * height * depth; + case D3DFMT_R8G8B8: + return width * 3 * height * depth; + case D3DFMT_A8R8G8B8: + case D3DFMT_X8R8G8B8: + case D3DFMT_A2B10G10R10: + case D3DFMT_A8B8G8R8: + case D3DFMT_X8B8G8R8: + case D3DFMT_G16R16: + case D3DFMT_X8L8V8U8: + case D3DFMT_Q8W8V8U8: + case D3DFMT_V16U16: + case D3DFMT_W11V11U10: + case D3DFMT_A2W10V10U10: + case D3DFMT_D32: + case D3DFMT_D24S8: + case D3DFMT_D24X8: + case D3DFMT_D24X4S4: + return width * 4 * height * depth; + case D3DFMT_DXT1: + assert(depth <= 1); + return ((width + 3) >> 2) * ((height + 3) >> 2) * 8; + case D3DFMT_DXT2: + case D3DFMT_DXT3: + case D3DFMT_DXT4: + case D3DFMT_DXT5: + assert(depth <= 1); + return ((width + 3) >> 2) * ((height + 3) >> 2) * 16; + } +} + +int vertFormatMap[] = { + -1, VERT_FLOAT2, VERT_FLOAT3, VERT_FLOAT4, VERT_ARGB, VERT_RGBA /* blend indices */ +}; + +void* +createIndexBuffer(uint32 length, bool dynamic) +{ +#ifdef RW_D3D9 + IDirect3DIndexBuffer9 *ibuf; + if(dynamic) + d3ddevice->CreateIndexBuffer(length, D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &ibuf, 0); + else + d3ddevice->CreateIndexBuffer(length, D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &ibuf, 0); + if(ibuf) + d3d9Globals.numIndexBuffers++; + return ibuf; +#else + return rwNewT(uint8, length, MEMDUR_EVENT | ID_DRIVER); +#endif +} + +void +destroyIndexBuffer(void *indexBuffer) +{ +#ifdef RW_D3D9 + if(indexBuffer){ + if(((IUnknown*)indexBuffer)->Release() != 0) + printf("indexBuffer wasn't destroyed\n"); + d3d9Globals.numIndexBuffers--; + } +#else + rwFree(indexBuffer); +#endif +} + +uint16* +lockIndices(void *indexBuffer, uint32 offset, uint32 size, uint32 flags) +{ + if(indexBuffer == nil) + return nil; +#ifdef RW_D3D9 + uint16 *indices; + IDirect3DIndexBuffer9 *ibuf = (IDirect3DIndexBuffer9*)indexBuffer; + ibuf->Lock(offset, size, (void**)&indices, flags); + return indices; +#else + (void)offset; + (void)size; + (void)flags; + return (uint16*)indexBuffer; +#endif +} + +void +unlockIndices(void *indexBuffer) +{ + if(indexBuffer == nil) + return; +#ifdef RW_D3D9 + IDirect3DIndexBuffer9 *ibuf = (IDirect3DIndexBuffer9*)indexBuffer; + ibuf->Unlock(); +#endif +} + +void* +createVertexBuffer(uint32 length, uint32 fvf, bool dynamic) +{ +#ifdef RW_D3D9 + IDirect3DVertexBuffer9 *vbuf; + if(dynamic) + d3ddevice->CreateVertexBuffer(length, D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, fvf, D3DPOOL_DEFAULT, &vbuf, 0); + else + d3ddevice->CreateVertexBuffer(length, D3DUSAGE_WRITEONLY, fvf, D3DPOOL_MANAGED, &vbuf, 0); + if(vbuf) + d3d9Globals.numVertexBuffers++; + return vbuf; +#else + (void)fvf; + return rwNewT(uint8, length, MEMDUR_EVENT | ID_DRIVER); +#endif +} + +void +destroyVertexBuffer(void *vertexBuffer) +{ +#ifdef RW_D3D9 + if(vertexBuffer){ + if(((IUnknown*)vertexBuffer)->Release() != 0) + printf("vertexBuffer wasn't destroyed\n"); + d3d9Globals.numVertexBuffers--; + } +#else + rwFree(vertexBuffer); +#endif +} + +uint8* +lockVertices(void *vertexBuffer, uint32 offset, uint32 size, uint32 flags) +{ + if(vertexBuffer == nil) + return nil; +#ifdef RW_D3D9 + uint8 *verts; + IDirect3DVertexBuffer9 *vertbuf = (IDirect3DVertexBuffer9*)vertexBuffer; + vertbuf->Lock(offset, size, (void**)&verts, flags); + return verts; +#else + (void)offset; + (void)size; + (void)flags; + return (uint8*)vertexBuffer; +#endif +} + +void +unlockVertices(void *vertexBuffer) +{ + if(vertexBuffer == nil) + return; +#ifdef RW_D3D9 + IDirect3DVertexBuffer9 *vertbuf = (IDirect3DVertexBuffer9*)vertexBuffer; + vertbuf->Unlock(); +#endif +} + +void* +createTexture(int32 width, int32 height, int32 numlevels, uint32 format) +{ +#ifdef RW_D3D9 + IDirect3DTexture9 *tex; + d3ddevice->CreateTexture(width, height, numlevels, 0, + (D3DFORMAT)format, D3DPOOL_MANAGED, &tex, nil); + if(tex) + d3d9Globals.numTextures++; + return tex; +#else + int32 w = width; + int32 h = height; + int32 size = 0; + for(int32 i = 0; i < numlevels; i++){ + size += calculateTextureSize(w, h, 1, format); + w /= 2; + if(w == 0) w = 1; + h /= 2; + if(h == 0) h = 1; + } + uint8 *data = (uint8*)rwNew(sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(numlevels-1)+size, + MEMDUR_EVENT | ID_DRIVER); + RasterLevels *levels = (RasterLevels*)data; + data += sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(numlevels-1); + levels->numlevels = numlevels; + levels->format = format; + w = width; + h = height; + for(int32 i = 0; i < numlevels; i++){ + levels->levels[i].width = w; + levels->levels[i].height = h; + levels->levels[i].data = data; + levels->levels[i].size = calculateTextureSize(w, h, 1, format); + data += levels->levels[i].size; + w /= 2; + if(w == 0) w = 1; + h /= 2; + if(h == 0) h = 1; + } + return levels; +#endif +} + +void +destroyTexture(void *texture) +{ +#ifdef RW_D3D9 + if(texture){ + if(((IUnknown*)texture)->Release() != 0) + printf("texture wasn't destroyed\n"); + d3d9Globals.numTextures--; + } +#else + rwFree(texture); +#endif +} + +// Native Raster + +int32 nativeRasterOffset; + +struct RasterFormatInfo +{ + uint32 d3dformat; + int32 depth; + bool32 hasAlpha; + uint32 rwFormat; +}; + +// indexed directly by RW format +static RasterFormatInfo formatInfoRW[16] = { + { 0, 0, 0}, + { D3DFMT_A1R5G5B5, 16, 1, Raster::C1555 }, + { D3DFMT_R5G6B5, 16, 0, Raster::C565 }, + { D3DFMT_A4R4G4B4, 16, 1, Raster::C4444 }, + { D3DFMT_L8, 8, 0, Raster::LUM8 }, + { D3DFMT_A8R8G8B8, 32, 1, Raster::C8888 }, + { D3DFMT_X8R8G8B8, 32, 0, Raster::C888 }, + { D3DFMT_D16, 16, 0, Raster::D16 }, + { D3DFMT_D24X8, 32, 0, Raster::D24 }, + { D3DFMT_D32, 32, 0, Raster::D32 }, + { D3DFMT_X1R5G5B5, 16, 0, Raster::C555 }, +}; + +static RasterFormatInfo formatInfoFull[] = { + { D3DFMT_R8G8B8, 0, 24, 0 }, + { D3DFMT_A8R8G8B8, 1, 32, Raster::C8888 }, + { D3DFMT_X8R8G8B8, 0, 32, Raster::C888 }, + { D3DFMT_R5G6B5, 0, 16, Raster::C565 }, + { D3DFMT_X1R5G5B5, 0, 16, Raster::C555 }, + { D3DFMT_A1R5G5B5, 1, 16, Raster::C1555 }, + { D3DFMT_A4R4G4B4, 1, 16, Raster::C4444 }, + { D3DFMT_R3G3B2, 0, 8, 0 }, + { D3DFMT_A8, 1, 8, 0 }, + { D3DFMT_A8R3G3B2, 1, 16, 0 }, + { D3DFMT_X4R4G4B4, 0, 16, 0 }, + { D3DFMT_A2B10G10R10, 1, 32, 0 }, + { D3DFMT_A8B8G8R8, 1, 32, 0 }, + { D3DFMT_X8B8G8R8, 0, 32, 0 }, + { D3DFMT_G16R16, 0, 32, 0 }, + { D3DFMT_A2R10G10B10, 1, 32, 0 }, + { D3DFMT_A16B16G16R16, 1, 64, 0 }, + { D3DFMT_A8P8, 1, 16, 0 }, +// { D3DFMT_P8, 0, 8, ... }, +// { D3DFMT_L8, 0, 8, ... }, + { D3DFMT_A8L8, 1, 16, 0 }, + { D3DFMT_A4L4, 1, 8, 0 }, + { D3DFMT_V8U8, 0, 16, 0 }, + { D3DFMT_L6V5U5, 0, 16, 0 }, + { D3DFMT_X8L8V8U8, 0, 32, 0 }, + { D3DFMT_Q8W8V8U8, 0, 32, 0 }, + { D3DFMT_V16U16, 0, 32, 0 }, + { D3DFMT_A2W10V10U10, 1, 32, 0 }, + { D3DFMT_D16_LOCKABLE, 0, 16, Raster::D16 }, + { D3DFMT_D32, 0, 32, Raster::D32 }, + { D3DFMT_D15S1, 0, 16, Raster::D16 }, + { D3DFMT_D24S8, 0, 32, Raster::D32 }, + { D3DFMT_D24X8, 0, 32, Raster::D32 }, + { D3DFMT_D24X4S4, 0, 32, Raster::D32 }, + { D3DFMT_D16, 0, 16, Raster::D16 }, + { D3DFMT_D32F_LOCKABLE, 0, 32, Raster::D32 }, + { D3DFMT_D24FS8, 0, 32, Raster::D32 }, + { D3DFMT_L16, 0, 16, 0 }, + { D3DFMT_Q16W16V16U16, 0, 64, 0 }, + { D3DFMT_R16F, 0, 16, 0 }, + { D3DFMT_G16R16F, 0, 32, 0 }, + { D3DFMT_A16B16G16R16F, 1, 64, 0 }, + { D3DFMT_R32F, 0, 32, 0 }, + { D3DFMT_G32R32F, 0, 64, 0 }, + { D3DFMT_A32B32G32R32F, 1, 128, 0 }, + { D3DFMT_CxV8U8, 0, 16, 0 }, +}; + +RasterFormatInfo* +findFormatInfoD3D(uint32 d3dformat) +{ + static RasterFormatInfo fake = { 0, 0, 0, 0 }; + int i; + for(i = 0; i < nelem(formatInfoFull); i++) + if(formatInfoFull[i].d3dformat == d3dformat) + return &formatInfoFull[i]; + return &fake; +} + + +static void +rasterSetFormat(Raster *raster) +{ + if(raster->format == 0){ + // have to find a format first + // this could perhaps be a bit more intelligent + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + raster->format = Raster::C8888; + break; + +#ifdef RW_D3D9 + case Raster::ZBUFFER: + // TODO: allow other formats + raster->format = findFormatInfoD3D(d3d9Globals.present.AutoDepthStencilFormat)->rwFormat; + // can this even happen? just do something... + if(raster->format == 0) + raster->format = Raster::D32; + break; + + case Raster::CAMERATEXTURE: +// let's not use this because we apparently don't want alpha +// raster->format = findFormatInfoD3D(d3d9Globals.present.BackBufferFormat)->rwFormat; + raster->format = findFormatInfoD3D(d3d9Globals.startMode.mode.Format)->rwFormat; + // can this even happen? just do something... + if(raster->format == 0) + raster->format = Raster::C888; + break; + case Raster::CAMERA: + raster->format = findFormatInfoD3D(d3d9Globals.present.BackBufferFormat)->rwFormat; + // can this even happen? just do something... + if(raster->format == 0) + raster->format = Raster::C8888; + break; +#endif + } + } + + + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + // TODO: do we even allow PAL4? + natras->format = D3DFMT_P8; + raster->depth = 8; + }else{ + natras->format = formatInfoRW[(raster->format >> 8) & 0xF].d3dformat; + raster->depth = formatInfoRW[(raster->format >> 8) & 0xF].depth; + } + natras->bpp = raster->depth/8; + natras->hasAlpha = formatInfoRW[(raster->format >> 8) & 0xF].hasAlpha; + raster->stride = raster->width*natras->bpp; + + raster->pixels = nil; + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->originalStride = raster->stride; + raster->originalPixels = raster->pixels; +} + +static Raster* +rasterCreateTexture(Raster *raster) +{ + int32 levels; + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + + if(natras->format == D3DFMT_P8) + natras->palette = (uint8*)rwNew(4*256, MEMDUR_EVENT | ID_DRIVER); + levels = Raster::calculateNumLevels(raster->width, raster->height); + assert(natras->texture == nil); + natras->texture = createTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? levels : 1, + natras->format); + if(natras->texture == nil){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + return raster; +} + +#ifdef RW_D3D9 + +static Raster* +rasterCreateCameraTexture(Raster *raster) +{ + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + + int32 levels; + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + levels = Raster::calculateNumLevels(raster->width, raster->height); + + IDirect3DTexture9 *tex; + d3ddevice->CreateTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? levels : 1, + D3DUSAGE_RENDERTARGET, + (D3DFORMAT)natras->format, D3DPOOL_DEFAULT, &tex, nil); + assert(natras->texture == nil); + natras->texture = tex; + if(natras->texture == nil){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + d3d9Globals.numTextures++; + addVidmemRaster(raster); + return raster; +} + +static Raster* +rasterCreateCamera(Raster *raster) +{ + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->originalStride = raster->stride = 0; + raster->originalPixels = raster->pixels = nil; + + natras->format = d3d9Globals.present.BackBufferFormat; + raster->depth = findFormatDepth(natras->format); + + natras->texture = nil; // global default render target + return raster; +} + +static Raster* +rasterCreateZbuffer(Raster *raster) +{ + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->originalStride = raster->stride = 0; + raster->originalPixels = raster->pixels = nil; + + // TODO: allow other formats + natras->format = d3d9Globals.present.AutoDepthStencilFormat; + raster->depth = findFormatDepth(natras->format); + + RECT rect; + GetClientRect(d3d9Globals.window, &rect); + // This check is done by RW but it's rather strange... + if(rect.right == raster->width && rect.bottom == raster->height) + natras->texture = d3d9Globals.defaultDepthSurf; + else{ + IDirect3DSurface9 *surf = nil; + d3ddevice->CreateDepthStencilSurface(raster->width, raster->height, (D3DFORMAT)natras->format, + d3d9Globals.present.MultiSampleType, d3d9Globals.present.MultiSampleQuality, + FALSE, &surf, nil); + assert(natras->texture == nil); + natras->texture = surf; + if(natras->texture == nil){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + } + addVidmemRaster(raster); + + return raster; +} +#endif + +Raster* +rasterCreate(Raster *raster) +{ + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + + rasterSetFormat(raster); + + if(raster->width == 0 || raster->height == 0){ + raster->flags |= Raster::DONTALLOCATE; + raster->stride = 0; + return raster; + } + if(raster->flags & Raster::DONTALLOCATE) + return raster; + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + return rasterCreateTexture(raster); + +#ifdef RW_D3D9 + case Raster::CAMERATEXTURE: + return rasterCreateCameraTexture(raster); + case Raster::ZBUFFER: + return rasterCreateZbuffer(raster); + case Raster::CAMERA: + return rasterCreateCamera(raster); +#endif + } + return nil; +} + +uint8* +rasterLock(Raster *raster, int32 level, int32 lockMode) +{ + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + + // check if already locked + if(raster->privateFlags & (Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE)) + return nil; + +#ifdef RW_D3D9 + DWORD flags = D3DLOCK_NOSYSLOCK; + if(lockMode & Raster::LOCKREAD) + flags |= D3DLOCK_READONLY | D3DLOCK_NO_DIRTY_UPDATE; + IDirect3DTexture9 *tex = (IDirect3DTexture9*)natras->texture; + IDirect3DSurface9 *surf; + D3DLOCKED_RECT lr; + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: { + tex->GetSurfaceLevel(level, &surf); + natras->lockedSurf = surf; + surf->LockRect(&lr, 0, flags); + break; + } + + default: + assert(0 && "can't lock this raster type (yet)"); + } + + raster->pixels = (uint8*)lr.pBits; + raster->width >>= level; + raster->height >>= level; + raster->stride = lr.Pitch; + if(raster->width == 0) raster->width = 1; + if(raster->height == 0) raster->height = 1; + + if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ; + if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE; + + return raster->pixels; +#else + RasterLevels *levels = (RasterLevels*)natras->texture; + return levels->levels[level].data; +#endif +} + +void +rasterUnlock(Raster *raster, int32 level) +{ +#if RW_D3D9 + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + IDirect3DSurface9 *surf = (IDirect3DSurface9*)natras->lockedSurf; + surf->UnlockRect(); + surf->Release(); + natras->lockedSurf = nil; +#endif + raster->width = raster->originalWidth; + raster->height = raster->originalHeight; + raster->stride = raster->originalStride; + raster->pixels = raster->originalPixels; + + raster->privateFlags &= ~(Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE); +} + +int32 +rasterNumLevels(Raster *raster) +{ + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); +#ifdef RW_D3D9 + IDirect3DTexture9 *tex = (IDirect3DTexture9*)natras->texture; + return tex->GetLevelCount(); +#else + RasterLevels *levels = (RasterLevels*)natras->texture; + return levels->numlevels; +#endif +} + +// Almost the same as ps2 and gl3 function +bool32 +imageFindRasterFormat(Image *img, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat) +{ + int32 width, height, depth, format; + + assert((type&0xF) == Raster::TEXTURE); + +// for(width = 1; width < img->width; width <<= 1); +// for(height = 1; height < img->height; height <<= 1); + // Perhaps non-power-of-2 textures are acceptable? + width = img->width; + height = img->height; + + depth = img->depth; + + if(depth <= 8 && !isP8supported) + depth = 32; + + switch(depth){ + case 32: + if(img->hasAlpha()) + format = Raster::C8888; + else{ + format = Raster::C888; + depth = 24; + } + break; + case 24: + format = Raster::C888; + break; + case 16: + format = Raster::C1555; + break; + case 8: + format = Raster::PAL8 | Raster::C8888; + break; + case 4: + format = Raster::PAL4 | Raster::C8888; + break; + default: + RWERROR((ERR_INVRASTER)); + return 0; + } + + format |= type; + + *pWidth = width; + *pHeight = height; + *pDepth = depth; + *pFormat = format; + + return 1; +} + +bool32 +rasterFromImage(Raster *raster, Image *image) +{ + if((raster->type&0xF) != Raster::TEXTURE) + return 0; + + void (*conv)(uint8 *out, uint8 *in) = nil; + + // Unpalettize image if necessary but don't change original + Image *truecolimg = nil; + if(image->depth <= 8 && !isP8supported){ + truecolimg = Image::create(image->width, image->height, image->depth); + truecolimg->pixels = image->pixels; + truecolimg->stride = image->stride; + truecolimg->palette = image->palette; + truecolimg->unpalettize(); + image = truecolimg; + } + + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + switch(image->depth){ + case 32: + if(raster->format == Raster::C8888) + conv = conv_BGRA8888_from_RGBA8888; + else if(raster->format == Raster::C888) + conv = conv_BGR888_from_RGB888; + else + goto err; + break; + case 24: + if(raster->format == Raster::C8888) + conv = conv_BGRA8888_from_RGB888; + else if(raster->format == Raster::C888) + conv = conv_BGR888_from_RGB888; + else + goto err; + break; + case 16: + if(raster->format == Raster::C1555) + conv = conv_ARGB1555_from_ARGB1555; + else + goto err; + break; + case 8: + if(raster->format == (Raster::PAL8 | Raster::C8888)) + conv = conv_8_from_8; + else + goto err; + break; + case 4: + if(raster->format == (Raster::PAL4 | Raster::C8888) || + raster->format == (Raster::PAL8 | Raster::C8888)) + conv = conv_8_from_8; + else + goto err; + default: + err: + RWERROR((ERR_INVRASTER)); + return 0; + } + + uint8 *in, *out; + int pallength = 0; + if(raster->format & Raster::PAL4) + pallength = 16; + else if(raster->format & Raster::PAL8) + pallength = 256; + if(pallength){ + in = image->palette; + out = (uint8*)natras->palette; + for(int32 i = 0; i < pallength; i++){ + conv_RGBA8888_from_RGBA8888(out, in); + in += 4; + out += 4; + } + } + + uint8 *pixels = raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + assert(pixels); + uint8 *imgpixels = image->pixels; + + int x, y; + assert(image->width == raster->width); + assert(image->height == raster->height); + for(y = 0; y < image->height; y++){ + uint8 *imgrow = imgpixels; + uint8 *rasrow = pixels; + for(x = 0; x < image->width; x++){ + conv(rasrow, imgrow); + imgrow += image->bpp; + rasrow += natras->bpp; + } + imgpixels += image->stride; + pixels += raster->stride; + } + raster->unlock(0); + + if(truecolimg) + truecolimg->destroy(); + + return 1; +} + +Image* +rasterToImage(Raster *raster) +{ + int32 depth; + Image *image; + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + if(natras->customFormat){ + image = Image::create(raster->width, raster->height, 32); + image->allocate(); + uint8 *pix = raster->lock(0, Raster::LOCKREAD); + switch(natras->format){ + case D3DFMT_DXT1: + image->setPixelsDXT(1, pix); + if((raster->format & 0xF00) == Raster::C565) + image->removeMask(); + break; + case D3DFMT_DXT3: + image->setPixelsDXT(3, pix); + break; + case D3DFMT_DXT5: + image->setPixelsDXT(5, pix); + break; + default: + raster->unlock(0); + image->destroy(); + return nil; + } + raster->unlock(0); + return image; + } + + void (*conv)(uint8 *out, uint8 *in) = nil; + switch(raster->format & 0xF00){ + case Raster::C1555: + depth = 16; + conv = conv_ARGB1555_from_ARGB1555; + break; + case Raster::C8888: + depth = 32; + conv = conv_RGBA8888_from_BGRA8888; + break; + case Raster::C888: + depth = 24; + conv = conv_RGB888_from_BGR888; + break; + case Raster::C555: + depth = 16; + conv = conv_ARGB1555_from_RGB555; + break; + + default: + case Raster::C565: + case Raster::C4444: + case Raster::LUM8: + RWERROR((ERR_INVRASTER)); + return nil; + } + int32 pallength = 0; + if((raster->format & Raster::PAL4) == Raster::PAL4){ + depth = 4; + pallength = 16; + }else if((raster->format & Raster::PAL8) == Raster::PAL8){ + depth = 8; + pallength = 256; + } + + uint8 *in, *out; + image = Image::create(raster->width, raster->height, depth); + image->allocate(); + + if(pallength){ + out = image->palette; + in = (uint8*)natras->palette; + for(int32 i = 0; i < pallength; i++){ + conv_RGBA8888_from_RGBA8888(out, in); + in += 4; + out += 4; + } + } + + uint8 *imgpixels = image->pixels; + uint8 *pixels = raster->lock(0, Raster::LOCKREAD); + + int x, y; + assert(image->width == raster->width); + assert(image->height == raster->height); + for(y = 0; y < image->height; y++){ + uint8 *imgrow = imgpixels; + uint8 *rasrow = pixels; + for(x = 0; x < image->width; x++){ + conv(imgrow, rasrow); + imgrow += image->bpp; + rasrow += natras->bpp; + } + imgpixels += image->stride; + pixels += raster->stride; + } + raster->unlock(0); + + image->compressPalette(); + + return image; +} + +int32 +getLevelSize(Raster *raster, int32 level) +{ + D3dRaster *ras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); +#ifdef RW_D3D9 + IDirect3DTexture9 *tex = (IDirect3DTexture9*)ras->texture; + D3DSURFACE_DESC desc; + tex->GetLevelDesc(level, &desc); + return calculateTextureSize(desc.Width, desc.Height, 1, desc.Format); +#else + RasterLevels *levels = (RasterLevels*)ras->texture; + return levels->levels[level].size; +#endif +} + +void +allocateDXT(Raster *raster, int32 dxt, int32 numLevels, bool32 hasAlpha) +{ + static uint32 dxtMap[] = { + 0x31545844, // DXT1 + 0x32545844, // DXT2 + 0x33545844, // DXT3 + 0x34545844, // DXT4 + 0x35545844, // DXT5 + }; + D3dRaster *ras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + ras->format = dxtMap[dxt-1]; + ras->hasAlpha = hasAlpha; + ras->texture = createTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? numLevels : 1, + ras->format); + raster->flags &= ~Raster::DONTALLOCATE; +} + +void +setPalette(Raster *raster, void *palette, int32 size) +{ + D3dRaster *ras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + memcpy(ras->palette, palette, 4*size); +} + +void +setTexels(Raster *raster, void *texels, int32 level) +{ + uint8 *dst = raster->lock(level, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + memcpy(dst, texels, getLevelSize(raster, level)); + raster->unlock(level); +} + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + D3dRaster *raster = PLUGINOFFSET(D3dRaster, object, offset); + raster->texture = nil; + raster->palette = nil; + raster->lockedSurf = nil; + raster->format = 0; + raster->hasAlpha = 0; + raster->customFormat = 0; + return object; +} + +static void* +destroyNativeRaster(void *object, int32 offset, int32) +{ + Raster *raster = (Raster*)object; + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, offset); +#ifdef RW_D3D9 + removeVidmemRaster(raster); + evictD3D9Raster(raster); +#endif + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + case Raster::CAMERATEXTURE: + destroyTexture(natras->texture); + break; + + case Raster::ZBUFFER: +#ifdef RW_D3D9 + if(raster->flags & Raster::DONTALLOCATE) + break; + if(natras->texture != d3d9Globals.defaultDepthSurf) + ((IDirect3DSurface9*)natras->texture)->Release(); + natras->texture = nil; +#endif + break; + case Raster::CAMERA: + break; + } + rwFree(natras->palette); + return object; +} + +static void* +copyNativeRaster(void *dst, void *, int32 offset, int32) +{ + D3dRaster *raster = PLUGINOFFSET(D3dRaster, dst, offset); + raster->texture = nil; + raster->palette = nil; + raster->lockedSurf = nil; + raster->format = 0; + raster->hasAlpha = 0; + raster->customFormat = 0; + return dst; +} + +void +registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(D3dRaster), + ID_RASTERD3D9, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); +} + +} +} diff --git a/src/d3d/d3d8.cpp b/src/d3d/d3d8.cpp new file mode 100644 index 0000000..8932620 --- /dev/null +++ b/src/d3d/d3d8.cpp @@ -0,0 +1,652 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3d8.h" + +#include "rwd3dimpl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace d3d8 { +using namespace d3d; + +static void* +driverOpen(void *o, int32, int32) +{ + engine->driver[PLATFORM_D3D8]->defaultPipeline = makeDefaultPipeline(); + + engine->driver[PLATFORM_D3D8]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_D3D8]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_D3D8]->rasterLock = rasterLock; + engine->driver[PLATFORM_D3D8]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_D3D8]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_D3D8]->imageFindRasterFormat = imageFindRasterFormat; + engine->driver[PLATFORM_D3D8]->rasterFromImage = rasterFromImage; + engine->driver[PLATFORM_D3D8]->rasterToImage = rasterToImage; + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_D3D8, 0, PLATFORM_D3D8, + driverOpen, driverClose); + // shared between D3D8 and 9 + if(nativeRasterOffset == 0) + registerNativeRaster(); +} + +uint32 +makeFVFDeclaration(uint32 flags, int32 numTex) +{ + uint32 fvf = 0x2; + if(flags & Geometry::NORMALS) + fvf |= 0x10; + if(flags & Geometry::PRELIT) + fvf |= 0x40; + fvf |= numTex << 8; + return fvf; +} + +int32 +getStride(uint32 flags, int32 numTex) +{ + int32 stride = 12; + if(flags & Geometry::NORMALS) + stride += 12;; + if(flags & Geometry::PRELIT) + stride += 4; + stride += numTex*8; + return stride; +} + +void* +destroyNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D8) + return object; + InstanceDataHeader *header = + (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + destroyIndexBuffer(inst->indexBuffer); + destroyVertexBuffer(inst->vertexBuffer); + inst++; + } + rwFree(header->inst); + rwFree(header); + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_D3D8){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_D3D8; + + int32 size = stream->readI32(); + uint8 *data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_GEOMETRY); + stream->read8(data, size); + uint8 *p = data; + header->serialNumber = *(uint16*)p; p += 2; + header->numMeshes = *(uint16*)p; p += 2; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + inst->minVert = *(uint32*)p; p += 4; + inst->stride = *(uint32*)p; p += 4; + inst->numVertices = *(uint32*)p; p += 4; + inst->numIndices = *(uint32*)p; p += 4; + uint32 matid = *(uint32*)p; p += 4; + inst->material = geometry->matList.materials[matid]; + inst->vertexShader = *(uint32*)p; p += 4; + inst->primType = *(uint32*)p; p += 4; + inst->indexBuffer = nil; p += 4; + inst->vertexBuffer = nil; p += 4; + inst->baseIndex = 0; p += 4; + inst->vertexAlpha = *p++; + inst->managed = 0; p++; + inst->remapped = 0; p++; // TODO: really unused? and what's that anyway? + inst++; + } + rwFree(data); + + inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + assert(inst->indexBuffer == nil); + inst->indexBuffer = createIndexBuffer(inst->numIndices*2, false); + uint16 *indices = lockIndices(inst->indexBuffer, 0, 0, 0); + stream->read8(indices, 2*inst->numIndices); + unlockIndices(inst->indexBuffer); + + inst->managed = 1; + assert(inst->vertexBuffer == nil); + inst->vertexBuffer = createVertexBuffer(inst->stride*inst->numVertices, 0, false); + uint8 *verts = lockVertices(inst->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + stream->read8(verts, inst->stride*inst->numVertices); + unlockVertices(inst->vertexBuffer); + + inst++; + } + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_STRUCT, len-12); + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D8) + return stream; + stream->writeU32(PLATFORM_D3D8); + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + + int32 size = 4 + geometry->meshHeader->numMeshes*0x2C; + uint8 *data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_GEOMETRY); + stream->writeI32(size); + uint8 *p = data; + *(uint16*)p = header->serialNumber; p += 2; + *(uint16*)p = header->numMeshes; p += 2; + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + *(uint32*)p = inst->minVert; p += 4; + *(uint32*)p = inst->stride; p += 4; + *(uint32*)p = inst->numVertices; p += 4; + *(uint32*)p = inst->numIndices; p += 4; + int32 matid = geometry->matList.findIndex(inst->material); + *(int32*)p = matid; p += 4; + *(uint32*)p = inst->vertexShader; p += 4; + *(uint32*)p = inst->primType; p += 4; + *(uint32*)p = 0; p += 4; // index buffer + *(uint32*)p = 0; p += 4; // vertex buffer + *(uint32*)p = inst->baseIndex; p += 4; + *p++ = inst->vertexAlpha; + *p++ = inst->managed; + *p++ = inst->remapped; + inst++; + } + stream->write8(data, size); + rwFree(data); + + inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + uint16 *indices = lockIndices(inst->indexBuffer, 0, 0, 0); + stream->write8(indices, 2*inst->numIndices); + unlockIndices(inst->indexBuffer); + + uint8 *verts = lockVertices(inst->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + stream->write8(verts, inst->stride*inst->numVertices); + unlockVertices(inst->vertexBuffer); + inst++; + } + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D8) + return 0; + + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + InstanceData *inst = header->inst; + int32 size = 12 + 4 + 4 + 4 + header->numMeshes*0x2C; + for(int32 i = 0; i < header->numMeshes; i++){ + size += inst->numIndices*2 + inst->numVertices*inst->stride; + inst++; + } + return size; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE + if(geo->instData) + return; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + MeshHeader *meshh = geo->meshHeader; + geo->instData = header; + header->platform = PLATFORM_D3D8; + + header->serialNumber = meshh->serialNum; + header->numMeshes = meshh->numMeshes; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + InstanceData *inst = header->inst; + Mesh *mesh = meshh->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &inst->minVert, &inst->numVertices); + inst->numIndices = mesh->numIndices; + inst->material = mesh->material; + inst->vertexShader = 0; + inst->primType = meshh->flags == 1 ? D3DPT_TRIANGLESTRIP : D3DPT_TRIANGLELIST; + inst->vertexBuffer = nil; + inst->baseIndex = 0; // (maybe) not used by us + inst->vertexAlpha = 0; + inst->managed = 0; + inst->remapped = 0; + + inst->indexBuffer = createIndexBuffer(inst->numIndices*2, false); + uint16 *indices = lockIndices(inst->indexBuffer, 0, 0, 0); + if(inst->minVert == 0) + memcpy(indices, mesh->indices, inst->numIndices*2); + else + for(int32 j = 0; j < inst->numIndices; j++) + indices[j] = mesh->indices[j] - inst->minVert; + unlockIndices(inst->indexBuffer); + + pipe->instanceCB(geo, inst); + mesh++; + inst++; + } +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_D3D8); + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + geo->allocateMeshes(geo->meshHeader->numMeshes, geo->meshHeader->totalIndices, 0); + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + InstanceData *inst = header->inst; + Mesh *mesh = geo->meshHeader->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + uint16 *indices = lockIndices(inst->indexBuffer, 0, 0, 0); + if(inst->minVert == 0) + memcpy(mesh->indices, indices, inst->numIndices*2); + else + for(int32 j = 0; j < inst->numIndices; j++) + mesh->indices[j] = indices[j] + inst->minVert; + unlockIndices(inst->indexBuffer); + + pipe->uninstanceCB(geo, inst); + mesh++; + inst++; + } + geo->generateTriangles(); + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +} + +static void +render(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE + if(geo->instData == nil) + pipe->instance(atomic); + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_D3D8); + if(pipe->renderCB) + pipe->renderCB(atomic, (InstanceDataHeader*)geo->instData); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_D3D8); + this->impl.instance = d3d8::instance; + this->impl.uninstance = d3d8::uninstance; + this->impl.render = d3d8::render; + this->instanceCB = nil; + this->uninstanceCB = nil; + this->renderCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +defaultInstanceCB(Geometry *geo, InstanceData *inst) +{ + inst->vertexShader = makeFVFDeclaration(geo->flags, geo->numTexCoordSets); + inst->stride = getStride(geo->flags, geo->numTexCoordSets); + + assert(inst->vertexBuffer == nil); + inst->vertexBuffer = createVertexBuffer(inst->numVertices*inst->stride, + inst->vertexShader, false); + inst->managed = 1; + + uint8 *dst = lockVertices(inst->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + instV3d(VERT_FLOAT3, dst, + &geo->morphTargets[0].vertices[inst->minVert], + inst->numVertices, inst->stride); + dst += 12; + + if(geo->flags & Geometry::NORMALS){ + instV3d(VERT_FLOAT3, dst, + &geo->morphTargets[0].normals[inst->minVert], + inst->numVertices, inst->stride); + dst += 12; + } + + inst->vertexAlpha = 0; + if(geo->flags & Geometry::PRELIT){ + inst->vertexAlpha = instColor(VERT_ARGB, dst, &geo->colors[inst->minVert], + inst->numVertices, inst->stride); + dst += 4; + } + + for(int32 i = 0; i < geo->numTexCoordSets; i++){ + instTexCoords(VERT_FLOAT2, dst, &geo->texCoords[i][inst->minVert], + inst->numVertices, inst->stride); + dst += 8; + } + unlockVertices(inst->vertexBuffer); +} + +void +defaultUninstanceCB(Geometry *geo, InstanceData *inst) +{ + uint8 *src = lockVertices(inst->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + uninstV3d(VERT_FLOAT3, + &geo->morphTargets[0].vertices[inst->minVert], + src, inst->numVertices, inst->stride); + src += 12; + + if(geo->flags & Geometry::NORMALS){ + uninstV3d(VERT_FLOAT3, + &geo->morphTargets[0].normals[inst->minVert], + src, inst->numVertices, inst->stride); + src += 12; + } + + inst->vertexAlpha = 0; + if(geo->flags & Geometry::PRELIT){ + uninstColor(VERT_ARGB, &geo->colors[inst->minVert], src, + inst->numVertices, inst->stride); + src += 4; + } + + for(int32 i = 0; i < geo->numTexCoordSets; i++){ + uninstTexCoords(VERT_FLOAT2, &geo->texCoords[i][inst->minVert], src, + inst->numVertices, inst->stride); + src += 8; + } + unlockVertices(inst->vertexBuffer); +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB; + return pipe; +} + +// Native Texture and Raster + +// only handles 4 and 8 bit textures right now +Raster* +readAsImage(Stream *stream, int32 width, int32 height, int32 depth, int32 format, int32 numLevels) +{ + uint8 palette[256*4]; + int32 pallen = 0; + uint8 *data = nil; + + Image *img = Image::create(width, height, 32); + img->allocate(); + + if(format & Raster::PAL4){ + pallen = 16; + stream->read8(palette, 4*32); + }else if(format & Raster::PAL8){ + pallen = 256; + stream->read8(palette, 4*256); + } + if(!Raster::formatHasAlpha(format)) + for(int32 i = 0; i < pallen; i++) + palette[i*4+3] = 0xFF; + + // Only read one mipmap + for(int32 i = 0; i < numLevels; i++){ + uint32 size = stream->readU32(); + if(i == 0){ + data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_IMAGE); + stream->read8(data, size); + }else + stream->seek(size); + } + + if(format & (Raster::PAL4 | Raster::PAL8)){ + uint8 *idx = data; + uint8 *pixels = img->pixels; + for(int y = 0; y < img->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < img->width; x++){ + line[0] = palette[*idx*4+0]; + line[1] = palette[*idx*4+1]; + line[2] = palette[*idx*4+2]; + line[3] = palette[*idx*4+3]; + line += 4; + idx++; + } + pixels += img->stride; + } + } + + rwFree(data); + Raster *ras = Raster::createFromImage(img, PLATFORM_D3D8); + img->destroy(); + return ras; +} + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_D3D8){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + // Texture + tex->filterAddressing = stream->readU32(); + stream->read8(tex->name, 32); + stream->read8(tex->mask, 32); + + // Raster + uint32 format = stream->readU32(); + bool32 hasAlpha = stream->readI32(); + int32 width = stream->readU16(); + int32 height = stream->readU16(); + int32 depth = stream->readU8(); + int32 numLevels = stream->readU8(); + int32 type = stream->readU8(); + int32 compression = stream->readU8(); + + int32 pallength = 0; + if(format & Raster::PAL4 || format & Raster::PAL8){ + pallength = format & Raster::PAL4 ? 32 : 256; + if(!d3d::isP8supported){ + tex->raster = readAsImage(stream, width, height, depth, format|type, numLevels); + return tex; + } + } + + Raster *raster; + D3dRaster *ras; + if(compression){ + raster = Raster::create(width, height, depth, format | type | 0x80, PLATFORM_D3D8); + ras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + allocateDXT(raster, compression, numLevels, hasAlpha); + ras->customFormat = 1; + }else{ + raster = Raster::create(width, height, depth, format | type, PLATFORM_D3D8); + ras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + } + tex->raster = raster; + + // TODO: check if format supported and convert if necessary + + if(pallength != 0) + stream->read8(ras->palette, 4*pallength); + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = stream->readU32(); + if(i < raster->getNumLevels()){ + data = raster->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + stream->read8(data, size); + raster->unlock(i); + }else + stream->seek(size); + } + return tex; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + int32 chunksize = getSizeNativeTexture(tex); + writeChunkHeader(stream, ID_STRUCT, chunksize-12); + stream->writeU32(PLATFORM_D3D8); + + // Texture + stream->writeU32(tex->filterAddressing); + stream->write8(tex->name, 32); + stream->write8(tex->mask, 32); + + // Raster + Raster *raster = tex->raster; + D3dRaster *ras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + int32 numLevels = raster->getNumLevels(); + stream->writeI32(raster->format); + stream->writeI32(ras->hasAlpha); + stream->writeU16(raster->width); + stream->writeU16(raster->height); + stream->writeU8(raster->depth); + stream->writeU8(numLevels); + stream->writeU8(raster->type); + int32 compression = 0; + if(ras->format) + switch(ras->format){ + case 0x31545844: // DXT1 + compression = 1; + break; + case 0x32545844: // DXT2 + compression = 2; + break; + case 0x33545844: // DXT3 + compression = 3; + break; + case 0x34545844: // DXT4 + compression = 4; + break; + case 0x35545844: // DXT5 + compression = 5; + break; + } + stream->writeU8(compression); + + if(raster->format & Raster::PAL4) + stream->write8(ras->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->write8(ras->palette, 4*256); + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = getLevelSize(raster, i); + stream->writeU32(size); + data = raster->lock(i, Raster::LOCKREAD); + stream->write8(data, size); + raster->unlock(i); + } +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 72 + 16; + int32 levels = tex->raster->getNumLevels(); + if(tex->raster->format & Raster::PAL4) + size += 4*32; + else if(tex->raster->format & Raster::PAL8) + size += 4*256; + for(int32 i = 0; i < levels; i++) + size += 4 + getLevelSize(tex->raster, i); + return size; +} + +} +} diff --git a/src/d3d/d3d8matfx.cpp b/src/d3d/d3d8matfx.cpp new file mode 100644 index 0000000..580203e --- /dev/null +++ b/src/d3d/d3d8matfx.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwd3d.h" +#include "rwd3d8.h" + +namespace rw { +namespace d3d8 { +using namespace d3d; + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_D3D8] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_D3D8])->destroy(); + matFXGlobals.pipelines[PLATFORM_D3D8] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_D3D8, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB; + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +} +} diff --git a/src/d3d/d3d8render.cpp b/src/d3d/d3d8render.cpp new file mode 100644 index 0000000..b6eef0e --- /dev/null +++ b/src/d3d/d3d8render.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3d8.h" + +namespace rw { +namespace d3d8 { +using namespace d3d; + +#ifndef RW_D3D9 +void defaultRenderCB(Atomic*, InstanceDataHeader*) {} +#else + +// This is a bit abandoned, use d3d9 instead + +void +defaultRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + RawMatrix world; + + d3d::lightingCB_Fix(atomic); + + Geometry *geo = atomic->geometry; + d3d::setRenderState(D3DRS_LIGHTING, !!(geo->flags & rw::Geometry::LIGHT)); + + Frame *f = atomic->getFrame(); + convMatrix(&world, f->getLTM()); + d3ddevice->SetTransform(D3DTS_WORLD, (D3DMATRIX*)&world); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + d3d::setTexture(0, inst->material->texture); + d3d::setMaterial(inst->material->color, inst->material->surfaceProps); + + + d3d::setRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL); + d3d::setRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL); + if(geo->flags & Geometry::PRELIT) + d3d::setRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); + else + d3d::setRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + + d3ddevice->SetFVF(inst->vertexShader); + setStreamSource(0, (IDirect3DVertexBuffer9*)inst->vertexBuffer, 0, inst->stride); + setIndices((IDirect3DIndexBuffer9*)inst->indexBuffer); + uint32 numPrim = inst->primType == D3DPT_TRIANGLESTRIP ? inst->numIndices-2 : inst->numIndices/3; + d3d::flushCache(); + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)inst->primType, inst->baseIndex, + 0, inst->numVertices, 0, numPrim); + inst++; + } +} + +#endif +} +} diff --git a/src/d3d/d3d8skin.cpp b/src/d3d/d3d8skin.cpp new file mode 100644 index 0000000..d9b0921 --- /dev/null +++ b/src/d3d/d3d8skin.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwd3d.h" +#include "rwd3d8.h" + +namespace rw { +namespace d3d8 { +using namespace d3d; + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_D3D8] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_D3D8])->destroy(); + skinGlobals.pipelines[PLATFORM_D3D8] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_D3D8, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB; + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + return pipe; +} + +} +} diff --git a/src/d3d/d3d9.cpp b/src/d3d/d3d9.cpp new file mode 100644 index 0000000..3abe111 --- /dev/null +++ b/src/d3d/d3d9.cpp @@ -0,0 +1,850 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3d9.h" + +#include "rwd3dimpl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace d3d9 { +using namespace d3d; + +// TODO: move to header, but not as #define +#ifndef RW_D3D9 +static VertexElement _d3ddec_end = {0xFF,0,D3DDECLTYPE_UNUSED,0,0,0}; +#define D3DDECL_END() _d3ddec_end +#endif + +#define NUMDECLELT 12 + +static void* +driverOpen(void *o, int32, int32) +{ +#ifdef RW_D3D9 + createDefaultShaders(); +#endif + engine->driver[PLATFORM_D3D9]->defaultPipeline = makeDefaultPipeline(); + + engine->driver[PLATFORM_D3D9]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_D3D9]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_D3D9]->rasterLock = rasterLock; + engine->driver[PLATFORM_D3D9]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_D3D9]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_D3D9]->imageFindRasterFormat = imageFindRasterFormat; + engine->driver[PLATFORM_D3D9]->rasterFromImage = rasterFromImage; + engine->driver[PLATFORM_D3D9]->rasterToImage = rasterToImage; + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ +#ifdef RW_D3D9 + destroyDefaultShaders(); +#endif + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_D3D9, 0, PLATFORM_D3D9, + driverOpen, driverClose); + // shared between D3D8 and 9 + if(nativeRasterOffset == 0) + registerNativeRaster(); +} + +void* +createVertexDeclaration(VertexElement *elements) +{ +#ifdef RW_D3D9 + IDirect3DVertexDeclaration9 *decl = 0; + d3ddevice->CreateVertexDeclaration((D3DVERTEXELEMENT9*)elements, &decl); + if(decl) + d3d9Globals.numVertexDeclarations++; + return decl; +#else + int n = 0; + VertexElement *e = (VertexElement*)elements; + while(e[n++].stream != 0xFF) + ; + e = rwNewT(VertexElement, n, MEMDUR_EVENT | ID_DRIVER); + memcpy(e, elements, n*sizeof(VertexElement)); + return e; +#endif +} + +void +destroyVertexDeclaration(void *declaration) +{ +#ifdef RW_D3D9 + if(declaration){ + if(((IUnknown*)declaration)->Release() != 0) + printf("declaration wasn't destroyed\n"); + d3d9Globals.numVertexDeclarations--; + } +#else + rwFree(declaration); +#endif +} + +uint32 +getDeclaration(void *declaration, VertexElement *elements) +{ +#ifdef RW_D3D9 + IDirect3DVertexDeclaration9 *decl = (IDirect3DVertexDeclaration9*)declaration; + UINT numElt; + decl->GetDeclaration((D3DVERTEXELEMENT9*)elements, &numElt); + return numElt; +#else + int n = 0; + VertexElement *e = (VertexElement*)declaration; + while(e[n++].stream != 0xFF) + ; + if(elements) + memcpy(elements, declaration, n*sizeof(VertexElement)); + return n; +#endif +} + +void +freeInstanceData(Geometry *geometry) +{ + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D9) + return; + InstanceDataHeader *header = + (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + destroyVertexDeclaration(header->vertexDeclaration); + destroyIndexBuffer(header->indexBuffer); + destroyVertexBuffer(header->vertexStream[0].vertexBuffer); + destroyVertexBuffer(header->vertexStream[1].vertexBuffer); + rwFree(header->inst); + rwFree(header); + return; +} + + +void* +destroyNativeData(void *object, int32, int32) +{ + freeInstanceData((Geometry*)object); + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_D3D9){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_D3D9; + + int32 size = stream->readI32(); + uint8 *data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_GEOMETRY); + stream->read8(data, size); + uint8 *p = data; + header->serialNumber = *(uint32*)p; p += 4; + header->numMeshes = *(uint32*)p; p += 4; + header->indexBuffer = nil; p += 4; + header->primType = *(uint32*)p; p += 4; + p += 16*2; // skip vertex streams, they're repeated with the vertex buffers + header->useOffsets = *(bool32*)p; p += 4; + header->vertexDeclaration = nil; p += 4; + header->totalNumIndex = *(uint32*)p; p += 4; + header->totalNumVertex = *(uint32*)p; p += 4; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + inst->numIndex = *(uint32*)p; p += 4; + inst->minVert = *(uint32*)p; p += 4; + uint32 matid = *(uint32*)p; p += 4; + inst->material = geometry->matList.materials[matid]; + inst->vertexAlpha = *(bool32*)p; p += 4; + inst->vertexShader = nil; p += 4; + inst->baseIndex = 0; p += 4; + inst->numVertices = *(uint32*)p; p += 4; + inst->startIndex = *(uint32*)p; p += 4; + inst->numPrimitives = *(uint32*)p; p += 4; + inst++; + } + + VertexElement elements[NUMDECLELT]; + uint32 numDeclarations = stream->readU32(); + stream->read8(elements, numDeclarations*8); + header->vertexDeclaration = createVertexDeclaration(elements); + + assert(header->indexBuffer == nil); + header->indexBuffer = createIndexBuffer(header->totalNumIndex*2, false); + uint16 *indices = lockIndices(header->indexBuffer, 0, 0, 0); + stream->read8(indices, 2*header->totalNumIndex); + unlockIndices(header->indexBuffer); + + VertexStream *s; + p = data; + for(int i = 0; i < 2; i++){ + stream->read8(p, 16); + s = &header->vertexStream[i]; + s->vertexBuffer = (void*)(uintptr)*(uint32*)p; p += 4; + s->offset = 0; p += 4; + s->stride = *(uint32*)p; p += 4; + s->geometryFlags = *(uint16*)p; p += 2; + s->managed = *p++; + s->dynamicLock = *p++; + + if(s->vertexBuffer == nil) + continue; + // TODO: use dynamic VB when doing morphing + assert(s->vertexBuffer == nil); + s->vertexBuffer = createVertexBuffer(s->stride*header->totalNumVertex, 0, false); + uint8 *verts = lockVertices(s->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + stream->read8(verts, s->stride*header->totalNumVertex); + unlockVertices(s->vertexBuffer); + } + + // TODO: somehow depends on number of streams used (baseIndex = minVert when more than one) + inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + inst->baseIndex = inst->minVert + header->vertexStream[0].offset / header->vertexStream[0].stride; + inst++; + } + + rwFree(data); + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_STRUCT, len-12); + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D9) + return stream; + stream->writeU32(PLATFORM_D3D9); + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + int32 size = 64 + geometry->meshHeader->numMeshes*36; + uint8 *data = rwNewT(uint8, size, MEMDUR_FUNCTION | ID_GEOMETRY); + stream->writeI32(size); + + uint8 *p = data; + *(uint32*)p = header->serialNumber; p += 4; + *(uint32*)p = header->numMeshes; p += 4; + p += 4; // skip index buffer + *(uint32*)p = header->primType; p += 4; + p += 16*2; // skip vertex streams, they're repeated with the vertex buffers + *(bool32*)p = header->useOffsets; p += 4; + p += 4; // skip vertex declaration + *(uint32*)p = header->totalNumIndex; p += 4; + *(uint32*)p = header->totalNumVertex; p += 4; + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + *(uint32*)p = inst->numIndex; p += 4; + *(uint32*)p = inst->minVert; p += 4; + int32 matid = geometry->matList.findIndex(inst->material); + *(int32*)p = matid; p += 4; + *(bool32*)p = inst->vertexAlpha; p += 4; + *(uint32*)p = 0; p += 4; // vertex shader + *(uint32*)p = inst->baseIndex; p += 4; // not used but meh... + *(uint32*)p = inst->numVertices; p += 4; + *(uint32*)p = inst->startIndex; p += 4; + *(uint32*)p = inst->numPrimitives; p += 4; + inst++; + } + stream->write8(data, size); + + VertexElement elements[NUMDECLELT]; + uint32 numElt = getDeclaration(header->vertexDeclaration, elements); + stream->writeU32(numElt); + stream->write8(elements, 8*numElt); + + uint16 *indices = lockIndices(header->indexBuffer, 0, 0, 0); + stream->write8(indices, 2*header->totalNumIndex); + unlockIndices(header->indexBuffer); + + VertexStream *s; + for(int i = 0; i < 2; i++){ + s = &header->vertexStream[i]; + p = data; + *(uint32*)p = s->vertexBuffer ? 0xbadeaffe : 0; p += 4; + *(uint32*)p = s->offset; p += 4; + *(uint32*)p = s->stride; p += 4; + *(uint16*)p = s->geometryFlags; p += 2; + *p++ = s->managed; + *p++ = s->dynamicLock; + stream->write8(data, 16); + + if(s->vertexBuffer == nil) + continue; + uint8 *verts = lockVertices(s->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + stream->write8(verts, s->stride*header->totalNumVertex); + unlockVertices(s->vertexBuffer); + } + + rwFree(data); + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_D3D9) + return 0; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + int32 size = 12 + 4 + 4 + 64 + header->numMeshes*36; + uint32 numElt = getDeclaration(header->vertexDeclaration, nil); + size += 4 + numElt*8; + size += 2*header->totalNumIndex; + size += 0x10 + header->vertexStream[0].stride*header->totalNumVertex; + size += 0x10 + header->vertexStream[1].stride*header->totalNumVertex; + return size; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +static InstanceDataHeader* +instanceMesh(rw::ObjPipeline *rwpipe, Geometry *geo) +{ + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + MeshHeader *meshh = geo->meshHeader; + header->platform = PLATFORM_D3D9; + + header->serialNumber = meshh->serialNum; + header->numMeshes = meshh->numMeshes; + header->primType = meshh->flags == 1 ? D3DPT_TRIANGLESTRIP : D3DPT_TRIANGLELIST; + header->useOffsets = 0; + header->vertexDeclaration = nil; + header->totalNumVertex = geo->numVertices; + header->totalNumIndex = meshh->totalIndices; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + header->indexBuffer = createIndexBuffer(header->totalNumIndex*2, false); + + uint16 *indices = lockIndices(header->indexBuffer, 0, 0, 0); + InstanceData *inst = header->inst; + Mesh *mesh = meshh->getMeshes(); + uint32 startindex = 0; + for(uint32 i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &inst->minVert, (int32*)&inst->numVertices); + inst->numIndex = mesh->numIndices; + inst->material = mesh->material; + inst->vertexAlpha = 0; + inst->vertexShader = nil; + inst->baseIndex = inst->minVert; + inst->startIndex = startindex; + inst->numPrimitives = header->primType == D3DPT_TRIANGLESTRIP ? inst->numIndex-2 : inst->numIndex/3; + if(inst->minVert == 0) + memcpy(&indices[inst->startIndex], mesh->indices, inst->numIndex*2); + else + for(uint32 j = 0; j < inst->numIndex; j++) + indices[inst->startIndex+j] = mesh->indices[j] - inst->minVert; + startindex += inst->numIndex; + mesh++; + inst++; + } + unlockIndices(header->indexBuffer); + + memset(&header->vertexStream, 0, 2*sizeof(VertexStream)); + + return header; +} + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // don't try to (re)instance native data + if(geo->flags & Geometry::NATIVE) + return; + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + if(geo->instData){ + // Already have instanced data, so check if we have to reinstance + assert(header->platform == PLATFORM_D3D9); + if(header->serialNumber != geo->meshHeader->serialNum){ + // Mesh changed, so reinstance everything + freeInstanceData(geo); + } + } + + // no instance or complete reinstance + if(geo->instData == nil){ + geo->instData = instanceMesh(rwpipe, geo); + pipe->instanceCB(geo, (InstanceDataHeader*)geo->instData, 0); + }else if(geo->lockedSinceInst) + pipe->instanceCB(geo, (InstanceDataHeader*)geo->instData, 1); + + geo->lockedSinceInst = 0; +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_D3D9); + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + geo->allocateMeshes(geo->meshHeader->numMeshes, geo->meshHeader->totalIndices, 0); + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + uint16 *indices = lockIndices(header->indexBuffer, 0, 0, 0); + InstanceData *inst = header->inst; + Mesh *mesh = geo->meshHeader->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + if(inst->minVert == 0) + memcpy(mesh->indices, &indices[inst->startIndex], inst->numIndex*2); + else + for(uint32 j = 0; j < inst->numIndex; j++) + mesh->indices[j] = indices[inst->startIndex+j] + inst->minVert; + mesh++; + inst++; + } + unlockIndices(header->indexBuffer); + + pipe->uninstanceCB(geo, header); + geo->generateTriangles(); + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +} + +static void +render(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + pipe->instance(atomic); + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_D3D9); + if(pipe->renderCB) + pipe->renderCB(atomic, (InstanceDataHeader*)geo->instData); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_D3D9); + this->impl.instance = d3d9::instance; + this->impl.uninstance = d3d9::uninstance; + this->impl.render = d3d9::render; + this->instanceCB = nil; + this->uninstanceCB = nil; + this->renderCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +defaultInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) +{ + int i = 0; + VertexElement dcl[NUMDECLELT]; + VertexStream *s = &header->vertexStream[0]; + + bool isPrelit = (geo->flags & Geometry::PRELIT) != 0; + bool hasNormals = (geo->flags & Geometry::NORMALS) != 0; + + // TODO: support both vertex buffers + + if(!reinstance){ + // Create declarations and buffers only the first time + + assert(s->vertexBuffer == nil); + s->offset = 0; + s->managed = 1; + s->geometryFlags = 0; + s->dynamicLock = 0; + + dcl[i].stream = 0; + dcl[i].offset = 0; + dcl[i].type = D3DDECLTYPE_FLOAT3; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_POSITION; + dcl[i].usageIndex = 0; + i++; + uint16 stride = 12; + s->geometryFlags |= 0x2; + + if(isPrelit){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_D3DCOLOR; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_COLOR; + dcl[i].usageIndex = 0; + i++; + s->geometryFlags |= 0x8; + stride += 4; + } + + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT2; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_TEXCOORD; + dcl[i].usageIndex = (uint8)n; + i++; + s->geometryFlags |= 0x10 << n; + stride += 8; + } + + if(hasNormals){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT3; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_NORMAL; + dcl[i].usageIndex = 0; + i++; + s->geometryFlags |= 0x4; + stride += 12; + } + + // We expect some attributes to always be there, use the constant buffer as fallback + if(!isPrelit){ + dcl[i].stream = 2; + dcl[i].offset = offsetof(VertexConstantData, color); + dcl[i].type = D3DDECLTYPE_D3DCOLOR; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_COLOR; + dcl[i].usageIndex = 0; + i++; + } + if(geo->numTexCoordSets == 0){ + dcl[i].stream = 2; + dcl[i].offset = offsetof(VertexConstantData, texCoors[0]); + dcl[i].type = D3DDECLTYPE_FLOAT2; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_TEXCOORD; + dcl[i].usageIndex = 0; + i++; + } + + dcl[i] = D3DDECL_END(); + s->stride = stride; + + assert(header->vertexDeclaration == nil); + header->vertexDeclaration = createVertexDeclaration((VertexElement*)dcl); + + assert(s->vertexBuffer == nil); + s->vertexBuffer = createVertexBuffer(header->totalNumVertex*s->stride, 0, false); + }else + getDeclaration(header->vertexDeclaration, dcl); + + uint8 *verts = lockVertices(s->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + + // Instance vertices + if(!reinstance || geo->lockedSinceInst&Geometry::LOCKVERTICES){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_POSITION || dcl[i].usageIndex != 0; i++) + ; + instV3d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + geo->morphTargets[0].vertices, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + // Instance prelight colors + if(isPrelit && (!reinstance || geo->lockedSinceInst&Geometry::LOCKPRELIGHT)){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_COLOR || dcl[i].usageIndex != 0; i++) + ; + InstanceData *inst = header->inst; + uint32 n = header->numMeshes; + while(n--){ + uint32 stride = header->vertexStream[dcl[i].stream].stride; + inst->vertexAlpha = instColor(vertFormatMap[dcl[i].type], + verts + dcl[i].offset + stride*inst->minVert, + geo->colors + inst->minVert, + inst->numVertices, + stride); + inst++; + } + } + + // Instance tex coords + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + if(!reinstance || geo->lockedSinceInst&(Geometry::LOCKTEXCOORDS<texCoords[n], + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + } + + // Instance normals + if(hasNormals && (!reinstance || geo->lockedSinceInst&Geometry::LOCKNORMALS)){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_NORMAL || dcl[i].usageIndex != 0; i++) + ; + instV3d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + geo->morphTargets[0].normals, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + unlockVertices(s->vertexBuffer); +} + +void +defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + VertexElement dcl[NUMDECLELT]; + + uint8 *verts[2]; + verts[0] = lockVertices(header->vertexStream[0].vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + verts[1] = lockVertices(header->vertexStream[1].vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + getDeclaration(header->vertexDeclaration, dcl); + + int i; + for(i = 0; dcl[i].usage != D3DDECLUSAGE_POSITION || dcl[i].usageIndex != 0; i++) + ; + uninstV3d(vertFormatMap[dcl[i].type], + geo->morphTargets[0].vertices, + verts[dcl[i].stream] + dcl[i].offset, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + + if(geo->flags & Geometry::PRELIT){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_COLOR || dcl[i].usageIndex != 0; i++) + ; + uninstColor(vertFormatMap[dcl[i].type], + geo->colors, + verts[dcl[i].stream] + dcl[i].offset, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_TEXCOORD || dcl[i].usageIndex != n; i++) + ; + uninstTexCoords(vertFormatMap[dcl[i].type], + geo->texCoords[n], + verts[dcl[i].stream] + dcl[i].offset, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + if(geo->flags & Geometry::NORMALS){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_NORMAL || dcl[i].usageIndex != 0; i++) + ; + uninstV3d(vertFormatMap[dcl[i].type], + geo->morphTargets[0].normals, + verts[dcl[i].stream] + dcl[i].offset, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + unlockVertices(verts[0]); + unlockVertices(verts[1]); +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB_Shader; + return pipe; +} + +// Native Texture and Raster + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_D3D9){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + // Texture + tex->filterAddressing = stream->readU32(); + stream->read8(tex->name, 32); + stream->read8(tex->mask, 32); + + // Raster + int32 format = stream->readI32(); + int32 d3dformat = stream->readI32(); + int32 width = stream->readU16(); + int32 height = stream->readU16(); + int32 depth = stream->readU8(); + int32 numLevels = stream->readU8(); + int32 type = stream->readU8(); +/* +#define HAS_ALPHA (1<<0) +#define IS_CUBE (1<<1) +#define USE_AUTOMIPMAPGEN (1<<2) +#define IS_COMPRESSED (1<<3) +*/ + int32 flags = stream->readU8(); + + Raster *raster; + D3dRaster *ext; + + if(flags & 8){ + // is compressed + assert((flags & 2) == 0 && "Can't have cube maps yet"); + raster = Raster::create(width, height, depth, format | type | Raster::DONTALLOCATE, PLATFORM_D3D9); + assert(raster); + ext = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + ext->format = d3dformat; + ext->hasAlpha = flags & 1; + ext->texture = createTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? numLevels : 1, + ext->format); + assert(ext->texture); + raster->flags &= ~Raster::DONTALLOCATE; + ext->customFormat = 1; + }else if(flags & 2){ + assert(0 && "Can't have cube maps yet"); + }else{ + raster = Raster::create(width, height, depth, format | type, PLATFORM_D3D9); + assert(raster); + ext = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + } + tex->raster = raster; + + // TODO: check if format supported and convert if necessary + + if(raster->format & Raster::PAL4) + stream->read8(ext->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->read8(ext->palette, 4*256); + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = stream->readU32(); + if(i < raster->getNumLevels()){ + data = raster->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + stream->read8(data, size); + raster->unlock(i); + }else + stream->seek(size); + } + return tex; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + int32 chunksize = getSizeNativeTexture(tex); + writeChunkHeader(stream, ID_STRUCT, chunksize-12); + stream->writeU32(PLATFORM_D3D9); + + // Texture + stream->writeU32(tex->filterAddressing); + stream->write8(tex->name, 32); + stream->write8(tex->mask, 32); + + // Raster + Raster *raster = tex->raster; + D3dRaster *ext = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + int32 numLevels = raster->getNumLevels(); + stream->writeI32(raster->format); + stream->writeU32(ext->format); + stream->writeU16(raster->width); + stream->writeU16(raster->height); + stream->writeU8(raster->depth); + stream->writeU8(numLevels); + stream->writeU8(raster->type); + uint8 flags = 0; + if(ext->hasAlpha) + flags |= 1; + // no automipmapgen and cube supported yet + if(ext->customFormat) + flags |= 8; + stream->writeU8(flags); + + if(raster->format & Raster::PAL4) + stream->write8(ext->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->write8(ext->palette, 4*256); + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = getLevelSize(raster, i); + stream->writeU32(size); + data = raster->lock(i, Raster::LOCKREAD); + stream->write8(data, size); + raster->unlock(i); + } +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 72 + 16; + int32 levels = tex->raster->getNumLevels(); + if(tex->raster->format & Raster::PAL4) + size += 4*32; + else if(tex->raster->format & Raster::PAL8) + size += 4*256; + for(int32 i = 0; i < levels; i++) + size += 4 + getLevelSize(tex->raster, i); + return size; +} + +} +} diff --git a/src/d3d/d3d9matfx.cpp b/src/d3d/d3d9matfx.cpp new file mode 100644 index 0000000..e85540d --- /dev/null +++ b/src/d3d/d3d9matfx.cpp @@ -0,0 +1,302 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwrender.h" +#include "../rwplugins.h" +#include "rwd3d.h" +#include "rwd3d9.h" + +namespace rw { +namespace d3d9 { +using namespace d3d; + +#ifndef RW_D3D9 +void matfxRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header) {} +#else + +static void *matfx_env_amb_VS; +static void *matfx_env_amb_dir_VS; +static void *matfx_env_all_VS; +static void *matfx_env_PS; +static void *matfx_env_tex_PS; + +enum +{ + VSLOC_texMat = VSLOC_afterLights, + + PSLOC_shininess = 1, + PSLOC_colorClamp = 2 +}; + +void +matfxRender_Default(InstanceDataHeader *header, InstanceData *inst, int32 lightBits) +{ + Material *m = inst->material; + + // Pick a shader + if((lightBits & VSLIGHT_MASK) == 0) + setVertexShader(default_amb_VS); + else if((lightBits & VSLIGHT_MASK) == VSLIGHT_DIRECT) + setVertexShader(default_amb_dir_VS); + else + setVertexShader(default_all_VS); + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + if(inst->material->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); +} + +static Frame *lastEnvFrame; + +static RawMatrix normal2texcoord = { + { 0.5f, 0.0f, 0.0f }, 0.0f, + { 0.0f, -0.5f, 0.0f }, 0.0f, + { 0.0f, 0.0f, 1.0f }, 0.0f, + { 0.5f, 0.5f, 0.0f }, 1.0f +}; + +void +uploadEnvMatrix(Frame *frame) +{ + Matrix invMat; + if(frame == nil) + frame = engine->currentCamera->getFrame(); + + // cache the matrix across multiple meshes + if(frame == lastEnvFrame) + return; + lastEnvFrame = frame; + + RawMatrix envMtx, invMtx; + Matrix::invert(&invMat, frame->getLTM()); + convMatrix(&invMtx, &invMat); + invMtx.pos.set(0.0f, 0.0f, 0.0f); + RawMatrix::mult(&envMtx, &invMtx, &normal2texcoord); + d3ddevice->SetVertexShaderConstantF(VSLOC_texMat, (float*)&envMtx, 4); +} + +void +matfxRender_EnvMap(InstanceDataHeader *header, InstanceData *inst, int32 lightBits, MatFX::Env *env) +{ + Material *m = inst->material; + + if(env->tex == nil || env->coefficient == 0.0f){ + matfxRender_Default(header, inst, lightBits); + return; + } + + d3d::setTexture(1, env->tex); + uploadEnvMatrix(env->frame); + + SetRenderState(SRCBLEND, BLENDONE); + + static float zero[4]; + static float one[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + struct { + float shininess; + float disableFBA; + float unused[2]; + } fxparams; + fxparams.shininess = env->coefficient; + fxparams.disableFBA = env->fbAlpha ? 0.0f : 1.0f; + d3ddevice->SetPixelShaderConstantF(PSLOC_shininess, (float*)&fxparams, 1); + // This clamps the vertex color below. With it we can achieve both PC and PS2 style matfx + if(MatFX::modulateEnvMap) + d3ddevice->SetPixelShaderConstantF(PSLOC_colorClamp, zero, 1); + else + d3ddevice->SetPixelShaderConstantF(PSLOC_colorClamp, one, 1); + + // Pick a shader + if((lightBits & VSLIGHT_MASK) == 0) + setVertexShader(matfx_env_amb_VS); + else if((lightBits & VSLIGHT_MASK) == VSLIGHT_DIRECT) + setVertexShader(matfx_env_amb_dir_VS); + else + setVertexShader(matfx_env_all_VS); + + bool32 texAlpha = PLUGINOFFSET(D3dRaster, env->tex->raster, nativeRasterOffset)->hasAlpha; + + if(inst->material->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(matfx_env_tex_PS); + }else + setPixelShader(matfx_env_PS); + + SetRenderState(VERTEXALPHA, texAlpha || inst->vertexAlpha || m->color.alpha != 255); + + drawInst(header, inst); + + SetRenderState(SRCBLEND, BLENDSRCALPHA); +} + +void +matfxRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header) +{ + int vsBits; + setStreamSource(0, (IDirect3DVertexBuffer9*)header->vertexStream[0].vertexBuffer, + 0, header->vertexStream[0].stride); + setIndices((IDirect3DIndexBuffer9*)header->indexBuffer); + setVertexDeclaration((IDirect3DVertexDeclaration9*)header->vertexDeclaration); + + lastEnvFrame = nil; + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + bool normals = !!(atomic->geometry->flags & Geometry::NORMALS); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + setMaterial(m->color, m->surfaceProps); + + MatFX *matfx = MatFX::get(m); + if(matfx == nil) + matfxRender_Default(header, inst, vsBits); + else switch(matfx->type){ + case MatFX::ENVMAP: + if(normals) + matfxRender_EnvMap(header, inst, vsBits, &matfx->fx[0].env); + else + matfxRender_Default(header, inst, vsBits); + break; + case MatFX::NOTHING: + case MatFX::BUMPMAP: + case MatFX::BUMPENVMAP: + case MatFX::DUAL: + case MatFX::UVTRANSFORM: + case MatFX::DUALUVTRANSFORM: + // not supported yet + matfxRender_Default(header, inst, vsBits); + break; + } + + inst++; + } + d3d::setTexture(1, nil); +} + +#define VS_NAME g_vs20_main +#define PS_NAME g_ps20_main + +void +createMatFXShaders(void) +{ + { + static +#include "shaders/matfx_env_amb_VS.h" + matfx_env_amb_VS = createVertexShader((void*)VS_NAME); + assert(matfx_env_amb_VS); + } + { + static +#include "shaders/matfx_env_amb_dir_VS.h" + matfx_env_amb_dir_VS = createVertexShader((void*)VS_NAME); + assert(matfx_env_amb_dir_VS); + } + { + static +#include "shaders/matfx_env_all_VS.h" + matfx_env_all_VS = createVertexShader((void*)VS_NAME); + assert(matfx_env_all_VS); + } + + + { + static +#include "shaders/matfx_env_PS.h" + matfx_env_PS = createPixelShader((void*)PS_NAME); + assert(matfx_env_PS); + } + { + static +#include "shaders/matfx_env_tex_PS.h" + matfx_env_tex_PS = createPixelShader((void*)PS_NAME); + assert(matfx_env_tex_PS); + } + +} + +void +destroyMatFXShaders(void) +{ + destroyVertexShader(matfx_env_amb_VS); + matfx_env_amb_VS = nil; + + destroyVertexShader(matfx_env_amb_dir_VS); + matfx_env_amb_dir_VS = nil; + + destroyVertexShader(matfx_env_all_VS); + matfx_env_all_VS = nil; + + + destroyPixelShader(matfx_env_PS); + matfx_env_PS = nil; + + destroyPixelShader(matfx_env_tex_PS); + matfx_env_tex_PS = nil; +} + +#endif + +static void* +matfxOpen(void *o, int32, int32) +{ +#ifdef RW_D3D9 + createMatFXShaders(); +#endif + + matFXGlobals.pipelines[PLATFORM_D3D9] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ +#ifdef RW_D3D9 + destroyMatFXShaders(); +#endif + + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_D3D9])->destroy(); + matFXGlobals.pipelines[PLATFORM_D3D9] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_D3D9, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = matfxRenderCB_Shader; + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +} +} diff --git a/src/d3d/d3d9render.cpp b/src/d3d/d3d9render.cpp new file mode 100644 index 0000000..0460922 --- /dev/null +++ b/src/d3d/d3d9render.cpp @@ -0,0 +1,184 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "../rwrender.h" +#include "rwd3d.h" +#include "rwd3d9.h" + +namespace rw { +namespace d3d9 { +using namespace d3d; + +#ifndef RW_D3D9 +void defaultRenderCB(Atomic*, InstanceDataHeader*) {} +void defaultRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header) {} +#else + +void +drawInst_simple(d3d9::InstanceDataHeader *header, d3d9::InstanceData *inst) +{ + d3d::flushCache(); + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)header->primType, inst->baseIndex, + 0, inst->numVertices, + inst->startIndex, inst->numPrimitives); +} + +// Emulate PS2 GS alpha test FB_ONLY case: failed alpha writes to frame- but not to depth buffer +void +drawInst_GSemu(d3d9::InstanceDataHeader *header, InstanceData *inst) +{ + uint32 hasAlpha; + int alphafunc, alpharef, gsalpharef; + int zwrite; + d3d::getRenderState(D3DRS_ALPHABLENDENABLE, &hasAlpha); + if(hasAlpha){ + zwrite = rw::GetRenderState(rw::ZWRITEENABLE); + alphafunc = rw::GetRenderState(rw::ALPHATESTFUNC); + if(zwrite){ + alpharef = rw::GetRenderState(rw::ALPHATESTREF); + gsalpharef = rw::GetRenderState(rw::GSALPHATESTREF); + + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAGREATEREQUAL); + SetRenderState(rw::ALPHATESTREF, gsalpharef); + drawInst_simple(header, inst); + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHALESS); + SetRenderState(rw::ZWRITEENABLE, 0); + drawInst_simple(header, inst); + SetRenderState(rw::ZWRITEENABLE, 1); + SetRenderState(rw::ALPHATESTFUNC, alphafunc); + SetRenderState(rw::ALPHATESTREF, alpharef); + }else{ + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAALWAYS); + drawInst_simple(header, inst); + SetRenderState(rw::ALPHATESTFUNC, alphafunc); + } + }else + drawInst_simple(header, inst); +} + +void +drawInst(d3d9::InstanceDataHeader *header, d3d9::InstanceData *inst) +{ + if(rw::GetRenderState(rw::GSALPHATEST)) + drawInst_GSemu(header, inst); + else + drawInst_simple(header, inst); +} + +/* +void +defaultRenderCB_Fix(Atomic *atomic, InstanceDataHeader *header) +{ + RawMatrix world; + Geometry *geo = atomic->geometry; + + int lighting = !!(geo->flags & rw::Geometry::LIGHT); + if(lighting) + d3d::lightingCB_Fix(atomic); + + d3d::setRenderState(D3DRS_LIGHTING, lighting); + + Frame *f = atomic->getFrame(); + convMatrix(&world, f->getLTM()); + d3ddevice->SetTransform(D3DTS_WORLD, (D3DMATRIX*)&world); + + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + SetRenderState(VERTEXALPHA, inst->vertexAlpha || inst->material->color.alpha != 255); + const static rw::RGBA white = { 255, 255, 255, 255 }; + d3d::setMaterial(white, inst->material->surfaceProps); + + d3d::setRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL); + if(geo->flags & Geometry::PRELIT) + d3d::setRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1); + else + d3d::setRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + d3d::setRenderState(D3DRS_DIFFUSEMATERIALSOURCE, inst->vertexAlpha ? D3DMCS_COLOR1 : D3DMCS_MATERIAL); + + if(inst->material->texture){ + // Texture + d3d::setTexture(0, inst->material->texture); + d3d::setTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + d3d::setTextureStageState(0, D3DTSS_COLORARG1, D3DTA_CURRENT); + d3d::setTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE); + d3d::setTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + d3d::setTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + d3d::setTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE); + }else{ + d3d::setTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + d3d::setTextureStageState(0, D3DTSS_COLORARG1, D3DTA_CURRENT); + d3d::setTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + d3d::setTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + } + + // Material colour + const rw::RGBA *col = &inst->material->color; + d3d::setTextureStageState(1, D3DTSS_CONSTANT, D3DCOLOR_ARGB(col->alpha,col->red,col->green,col->blue)); + d3d::setTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE); + d3d::setTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + d3d::setTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CONSTANT); + d3d::setTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + d3d::setTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + d3d::setTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CONSTANT); + + drawInst(header, inst); + inst++; + } + d3d::setTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + d3d::setTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); +} +*/ + +void +defaultRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header) +{ + int vsBits; + setStreamSource(0, header->vertexStream[0].vertexBuffer, 0, header->vertexStream[0].stride); + setIndices(header->indexBuffer); + setVertexDeclaration(header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + // Pick a shader + if((vsBits & VSLIGHT_MASK) == 0) + setVertexShader(default_amb_VS); + else if((vsBits & VSLIGHT_MASK) == VSLIGHT_DIRECT) + setVertexShader(default_amb_dir_VS); + else + setVertexShader(default_all_VS); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + setMaterial(m->color, m->surfaceProps); + + if(m->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); + inst++; + } +} + +#endif +} +} diff --git a/src/d3d/d3d9skin.cpp b/src/d3d/d3d9skin.cpp new file mode 100644 index 0000000..23c234d --- /dev/null +++ b/src/d3d/d3d9skin.cpp @@ -0,0 +1,418 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwd3d.h" +#include "rwd3d9.h" + +namespace rw { +namespace d3d9 { +using namespace d3d; + +#ifndef RW_D3D9 +void skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) {} +void skinRenderCB(Atomic *atomic, InstanceDataHeader *header) {} +#else + + +static void *skin_amb_VS; +static void *skin_amb_dir_VS; +static void *skin_all_VS; + +#define NUMDECLELT 14 + +void +skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) +{ + int i = 0; + VertexElement dcl[NUMDECLELT]; + VertexStream *s = &header->vertexStream[0]; + + bool isPrelit = (geo->flags & Geometry::PRELIT) != 0; + bool hasNormals = (geo->flags & Geometry::NORMALS) != 0; + + // TODO: support both vertex buffers + + if(!reinstance){ + // Create declarations and buffers only the first time + + assert(s->vertexBuffer == nil); + s->offset = 0; + s->managed = 1; + s->geometryFlags = 0; + s->dynamicLock = 0; + + dcl[i].stream = 0; + dcl[i].offset = 0; + dcl[i].type = D3DDECLTYPE_FLOAT3; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_POSITION; + dcl[i].usageIndex = 0; + i++; + uint16 stride = 12; + s->geometryFlags |= 0x2; + + if(isPrelit){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_D3DCOLOR; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_COLOR; + dcl[i].usageIndex = 0; + i++; + s->geometryFlags |= 0x8; + stride += 4; + } + + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT2; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_TEXCOORD; + dcl[i].usageIndex = (uint8)n; + i++; + s->geometryFlags |= 0x10 << n; + stride += 8; + } + + if(hasNormals){ + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT3; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_NORMAL; + dcl[i].usageIndex = 0; + i++; + s->geometryFlags |= 0x4; + stride += 12; + } + + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_FLOAT4; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_BLENDWEIGHT; + dcl[i].usageIndex = 0; + i++; + stride += 16; + + dcl[i].stream = 0; + dcl[i].offset = stride; + dcl[i].type = D3DDECLTYPE_UBYTE4; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_BLENDINDICES; + dcl[i].usageIndex = 0; + i++; + stride += 4; + + // We expect some attributes to always be there, use the constant buffer as fallback + if(!isPrelit){ + dcl[i].stream = 2; + dcl[i].offset = offsetof(VertexConstantData, color); + dcl[i].type = D3DDECLTYPE_D3DCOLOR; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_COLOR; + dcl[i].usageIndex = 0; + i++; + } + if(geo->numTexCoordSets == 0){ + dcl[i].stream = 2; + dcl[i].offset = offsetof(VertexConstantData, texCoors[0]); + dcl[i].type = D3DDECLTYPE_FLOAT2; + dcl[i].method = D3DDECLMETHOD_DEFAULT; + dcl[i].usage = D3DDECLUSAGE_TEXCOORD; + dcl[i].usageIndex = 0; + i++; + } + + dcl[i] = D3DDECL_END(); + s->stride = stride; + + assert(header->vertexDeclaration == nil); + header->vertexDeclaration = createVertexDeclaration((VertexElement*)dcl); + + assert(s->vertexBuffer == nil); + s->vertexBuffer = createVertexBuffer(header->totalNumVertex*s->stride, 0, false); + }else + getDeclaration(header->vertexDeclaration, dcl); + + Skin *skin = Skin::get(geo); + uint8 *verts = lockVertices(s->vertexBuffer, 0, 0, D3DLOCK_NOSYSLOCK); + + // Instance vertices + if(!reinstance || geo->lockedSinceInst&Geometry::LOCKVERTICES){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_POSITION || dcl[i].usageIndex != 0; i++) + ; + instV3d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + geo->morphTargets[0].vertices, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + // Instance prelight colors + if(isPrelit && (!reinstance || geo->lockedSinceInst&Geometry::LOCKPRELIGHT)){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_COLOR || dcl[i].usageIndex != 0; i++) + ; + InstanceData *inst = header->inst; + uint32 n = header->numMeshes; + while(n--){ + uint32 stride = header->vertexStream[dcl[i].stream].stride; + inst->vertexAlpha = instColor(vertFormatMap[dcl[i].type], + verts + dcl[i].offset + stride*inst->minVert, + geo->colors + inst->minVert, + inst->numVertices, + stride); + inst++; + } + } + + // Instance tex coords + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + if(!reinstance || geo->lockedSinceInst&(Geometry::LOCKTEXCOORDS<texCoords[n], + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + } + + // Instance normals + if(hasNormals && (!reinstance || geo->lockedSinceInst&Geometry::LOCKNORMALS)){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_NORMAL || dcl[i].usageIndex != 0; i++) + ; + instV3d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + geo->morphTargets[0].normals, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + // Instance skin weights + if(!reinstance){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_BLENDWEIGHT || dcl[i].usageIndex != 0; i++) + ; + instV4d(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + (V4d*)skin->weights, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + // Instance skin indices + if(!reinstance){ + for(i = 0; dcl[i].usage != D3DDECLUSAGE_BLENDINDICES || dcl[i].usageIndex != 0; i++) + ; + // not really colors of course but what the heck + instColor(vertFormatMap[dcl[i].type], verts + dcl[i].offset, + (RGBA*)skin->indices, + header->totalNumVertex, + header->vertexStream[dcl[i].stream].stride); + } + + unlockVertices(s->vertexBuffer); +} + +enum +{ + VSLOC_boneMatrices = VSLOC_afterLights +}; + +static float skinMatrices[64*16]; + +void +uploadSkinMatrices(Atomic *a) +{ + int i; + Skin *skin = Skin::get(a->geometry); + float *m = skinMatrices; + HAnimHierarchy *hier = Skin::getHierarchy(a); + + if(hier){ + Matrix *invMats = (Matrix*)skin->inverseMatrices; + Matrix tmp, tmp2; + + assert(skin->numBones == hier->numNodes); + if(hier->flags & HAnimHierarchy::LOCALSPACEMATRICES){ + for(i = 0; i < hier->numNodes; i++){ + invMats[i].flags = 0; + Matrix::mult(&tmp, &invMats[i], &hier->matrices[i]); + RawMatrix::transpose((RawMatrix*)m, (RawMatrix*)&tmp); + m += 12; + } + }else{ + Matrix invAtmMat; + Matrix::invert(&invAtmMat, a->getFrame()->getLTM()); + for(i = 0; i < hier->numNodes; i++){ + invMats[i].flags = 0; + Matrix::mult(&tmp, &hier->matrices[i], &invAtmMat); + Matrix::mult(&tmp2, &invMats[i], &tmp); + RawMatrix::transpose((RawMatrix*)m, (RawMatrix*)&tmp2); + m += 12; + } + } + }else{ + for(i = 0; i < skin->numBones; i++){ + m[0] = 1.0f; + m[1] = 0.0f; + m[2] = 0.0f; + m[3] = 0.0f; + + m[4] = 0.0f; + m[5] = 1.0f; + m[6] = 0.0f; + m[7] = 0.0f; + + m[8] = 0.0f; + m[9] = 0.0f; + m[10] = 1.0f; + m[11] = 0.0f; + + m += 12; + } + } + d3ddevice->SetVertexShaderConstantF(VSLOC_boneMatrices, skinMatrices, skin->numBones*3); +} + +void +skinRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + int vsBits; + + setStreamSource(0, (IDirect3DVertexBuffer9*)header->vertexStream[0].vertexBuffer, + 0, header->vertexStream[0].stride); + setIndices((IDirect3DIndexBuffer9*)header->indexBuffer); + setVertexDeclaration((IDirect3DVertexDeclaration9*)header->vertexDeclaration); + + vsBits = lightingCB_Shader(atomic); + uploadMatrices(atomic->getFrame()->getLTM()); + + uploadSkinMatrices(atomic); + + // Pick a shader + if((vsBits & VSLIGHT_MASK) == 0) + setVertexShader(skin_amb_VS); + else if((vsBits & VSLIGHT_MASK) == VSLIGHT_DIRECT) + setVertexShader(skin_amb_dir_VS); + else + setVertexShader(skin_all_VS); + + InstanceData *inst = header->inst; + for(uint32 i = 0; i < header->numMeshes; i++){ + Material *m = inst->material; + + SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 255); + + setMaterial(m->color, m->surfaceProps); + + if(inst->material->texture){ + d3d::setTexture(0, m->texture); + setPixelShader(default_tex_PS); + }else + setPixelShader(default_PS); + + drawInst(header, inst); + inst++; + } +} + +#define VS_NAME g_vs20_main +#define PS_NAME g_ps20_main + +void +createSkinShaders(void) +{ + { + static +#include "shaders/skin_amb_VS.h" + skin_amb_VS = createVertexShader((void*)VS_NAME); + assert(skin_amb_VS); + } + { + static +#include "shaders/skin_amb_dir_VS.h" + skin_amb_dir_VS = createVertexShader((void*)VS_NAME); + assert(skin_amb_dir_VS); + } + // Skinning takes a lot of instructions....lighting may be not possible + // TODO: should do something about this + { + static +#include "shaders/skin_all_VS.h" + skin_all_VS = createVertexShader((void*)VS_NAME); +// assert(skin_all_VS); + } +} + +void +destroySkinShaders(void) +{ + destroyVertexShader(skin_amb_VS); + skin_amb_VS = nil; + + destroyVertexShader(skin_amb_dir_VS); + skin_amb_dir_VS = nil; + + if(skin_all_VS){ + destroyVertexShader(skin_all_VS); + skin_all_VS = nil; + } +} + +#endif + +static void* +skinOpen(void *o, int32, int32) +{ +#ifdef RW_D3D9 + createSkinShaders(); +#endif + + skinGlobals.pipelines[PLATFORM_D3D9] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ +#ifdef RW_D3D9 + destroySkinShaders(); +#endif + + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_D3D9])->destroy(); + skinGlobals.pipelines[PLATFORM_D3D9] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_D3D9, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = nil; + pipe->renderCB = skinRenderCB; + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + return pipe; +} + +} +} diff --git a/src/d3d/d3ddevice.cpp b/src/d3d/d3ddevice.cpp new file mode 100644 index 0000000..7849d37 --- /dev/null +++ b/src/d3d/d3ddevice.cpp @@ -0,0 +1,1807 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwerror.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "rwd3d.h" +#include "rwd3dimpl.h" + +#define PLUGIN_ID 0 + +namespace rw { +namespace d3d { + +#ifdef RW_D3D9 + +D3d9Globals d3d9Globals; + +// Keep track of rasters exclusively in video memory +// as they need special treatment sometimes +struct VidmemRaster +{ + Raster *raster; + VidmemRaster *next; +}; +static VidmemRaster *vidmemRasters; +void addVidmemRaster(Raster *raster); +void removeVidmemRaster(Raster *raster); + +// Same thing for dynamic vertex buffers +struct DynamicVB +{ + uint32 length; + uint32 fvf; + IDirect3DVertexBuffer9 **buf; + DynamicVB *next; +}; +static DynamicVB *dynamicVBs; +void addDynamicVB(uint32 length, uint32 fvf, IDirect3DVertexBuffer9 **buf); +void removeDynamicVB(IDirect3DVertexBuffer9 **buf); + +// Same thing for dynamic index buffers +struct DynamicIB +{ + uint32 length; + IDirect3DIndexBuffer9 **buf; + DynamicIB *next; +}; +static DynamicIB *dynamicIBs; +void addDynamicIB(uint32 length, IDirect3DIndexBuffer9 **buf); +void removeDynamicIB(IDirect3DIndexBuffer9 **buf); + + +struct RwRasterStateCache { + Raster *raster; + Texture::Addressing addressingU; + Texture::Addressing addressingV; + Texture::FilterMode filter; +}; + +#define MAXNUMSTAGES 8 + +// cached RW render states +struct RwStateCache { + bool32 vertexAlpha; + bool32 textureAlpha; + uint32 srcblend, destblend; + uint32 zwrite; + uint32 ztest; + uint32 fogenable; + RGBA fogcolor; + uint32 cullmode; + uint32 alphafunc; + uint32 alpharef; + + // emulation of PS2 GS + bool32 gsalpha; + uint32 gsalpharef; + + RwRasterStateCache texstage[MAXNUMSTAGES]; +}; +static RwStateCache rwStateCache; + +void *constantVertexStream; + +D3dShaderState d3dShaderState; + +#define MAXNUMSTATES (D3DRS_BLENDOPALPHA+1) +#define MAXNUMTEXSTATES (D3DTSS_CONSTANT+1) +#define MAXNUMSAMPLERSTATES (D3DSAMP_DMAPOFFSET+1) +#define MAXNUMSTREAMS (3) +#define MAXNUMRENDERTARGETS (4) + +static int32 numDirtyStates; +static uint32 dirtyStates[MAXNUMSTATES]; +static struct { + uint32 value; + bool32 dirty; +} stateCache[MAXNUMSTATES]; +static uint32 d3dStates[MAXNUMSTATES]; + +// Things that aren't just integers +struct D3dDeviceCache { + IDirect3DVertexShader9 *vertexShader; + IDirect3DPixelShader9 *pixelShader; + IDirect3DVertexDeclaration9 *vertexDeclaration; + IDirect3DIndexBuffer9 *indices; + struct { + IDirect3DVertexBuffer9 *buffer; + uint32 offset; + uint32 stride; + } vertexStreams[MAXNUMSTREAMS]; + IDirect3DSurface9 *renderTargets[MAXNUMRENDERTARGETS]; + IDirect3DSurface9 *depthSurface; +}; +static D3dDeviceCache deviceCache; + +static int32 numDirtyTextureStageStates; +static struct { + uint32 stage; + uint32 type; +} dirtyTextureStageStates[MAXNUMTEXSTATES*MAXNUMSTAGES]; +static struct { + uint32 value; + bool32 dirty; +} textureStageStateCache[MAXNUMTEXSTATES][MAXNUMSTAGES]; +static uint32 d3dTextureStageStates[MAXNUMTEXSTATES][MAXNUMSTAGES]; + +static uint32 d3dSamplerStates[MAXNUMSAMPLERSTATES][MAXNUMSTAGES]; + + +static bool validStates[MAXNUMSTATES]; +static bool validTexStates[MAXNUMTEXSTATES]; + +static D3DMATERIAL9 d3dmaterial; + + +static uint32 blendMap[] = { + D3DBLEND_ZERO, // actually invalid + D3DBLEND_ZERO, + D3DBLEND_ONE, + D3DBLEND_SRCCOLOR, + D3DBLEND_INVSRCCOLOR, + D3DBLEND_SRCALPHA, + D3DBLEND_INVSRCALPHA, + D3DBLEND_DESTALPHA, + D3DBLEND_INVDESTALPHA, + D3DBLEND_DESTCOLOR, + D3DBLEND_INVDESTCOLOR, + D3DBLEND_SRCALPHASAT +}; + +static uint32 alphafuncMap[] = { + D3DCMP_ALWAYS, + D3DCMP_GREATEREQUAL, + D3DCMP_LESS +}; + +static uint32 cullmodeMap[] = { + D3DCULL_NONE, // actually invalid + D3DCULL_NONE, + D3DCULL_CW, + D3DCULL_CCW +}; + +// TODO: support mipmaps +static uint32 filterConvMap_NoMIP[] = { + 0, D3DTEXF_POINT, D3DTEXF_LINEAR, + D3DTEXF_POINT, D3DTEXF_LINEAR, + D3DTEXF_POINT, D3DTEXF_LINEAR +}; +static uint32 addressConvMap[] = { + 0, D3DTADDRESS_WRAP, D3DTADDRESS_MIRROR, + D3DTADDRESS_CLAMP, D3DTADDRESS_BORDER +}; + +// D3D render state + +void +setRenderState(uint32 state, uint32 value) +{ + if(stateCache[state].value != value){ + stateCache[state].value = value; + if(!stateCache[state].dirty){ + stateCache[state].dirty = 1; + dirtyStates[numDirtyStates++] = state; + } + } +} + +void +getRenderState(uint32 state, uint32 *value) +{ + *value = stateCache[state].value; +} + +void +setRenderTarget(int n, void *surf) +{ + if(surf != deviceCache.renderTargets[n]){ + deviceCache.renderTargets[n] = (IDirect3DSurface9*)surf; + d3ddevice->SetRenderTarget(n, deviceCache.renderTargets[n]); + } +} + +void +setDepthSurface(void *surf) +{ + if(surf != deviceCache.depthSurface){ + deviceCache.depthSurface = (IDirect3DSurface9*)surf; + d3ddevice->SetDepthStencilSurface(deviceCache.depthSurface); + } +} + +void +setTextureStageState(uint32 stage, uint32 type, uint32 value) +{ + if(textureStageStateCache[type][stage].value != value){ + textureStageStateCache[type][stage].value = value; + if(!textureStageStateCache[type][stage].dirty){ + textureStageStateCache[type][stage].dirty = 1; + dirtyTextureStageStates[numDirtyTextureStageStates].stage = stage; + dirtyTextureStageStates[numDirtyTextureStageStates].type = type; + numDirtyTextureStageStates++; + } + } +} + +void +getTextureStageState(uint32 stage, uint32 type, uint32 *value) +{ + *value = textureStageStateCache[type][stage].value; +} + +void +flushCache(void) +{ + uint32 s, t; + uint32 v; + for(int32 i = 0; i < numDirtyStates; i++){ + s = dirtyStates[i]; + v = stateCache[s].value; + stateCache[s].dirty = 0; + if(d3dStates[s] != v){ + d3ddevice->SetRenderState((D3DRENDERSTATETYPE)s, v); + d3dStates[s] = v; + } + } + numDirtyStates = 0; + for(int32 i = 0; i < numDirtyTextureStageStates; i++){ + s = dirtyTextureStageStates[i].stage; + t = dirtyTextureStageStates[i].type; + v = textureStageStateCache[t][s].value; + textureStageStateCache[t][s].dirty = 0; + if(d3dTextureStageStates[t][s] != v){ + d3ddevice->SetTextureStageState(s, (D3DTEXTURESTAGESTATETYPE)t, v); + d3dTextureStageStates[t][s] = v; + } + } + numDirtyTextureStageStates = 0; + + if(d3dShaderState.fogDirty){ + d3ddevice->SetVertexShaderConstantF(VSLOC_fogData, (float*)&d3dShaderState.fogData, 1); + d3ddevice->SetPixelShaderConstantF(PSLOC_fogColor, (float*)&d3dShaderState.fogColor, 1); + d3dShaderState.fogDirty = false; + } +} + +void +setSamplerState(uint32 stage, uint32 type, uint32 value) +{ + if(d3dSamplerStates[type][stage] != value){ + d3ddevice->SetSamplerState(stage, (D3DSAMPLERSTATETYPE)type, value); + d3dSamplerStates[type][stage] = value; + } +} + +void +getSamplerState(uint32 stage, uint32 type, uint32 *value) +{ + *value = d3dSamplerStates[type][stage]; +} + +// Bring D3D device in accordance with saved render states (after a reset) +static void +restoreD3d9Device(void) +{ + int32 i; + uint32 s, t; + + for(i = 0; i < MAXNUMSTAGES; i++){ + Raster *raster = rwStateCache.texstage[i].raster; + if(raster){ + D3dRaster *d3draster = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + d3ddevice->SetTexture(i, (IDirect3DTexture9*)d3draster->texture); + }else + d3ddevice->SetTexture(i, nil); + setSamplerState(i, D3DSAMP_ADDRESSU, addressConvMap[rwStateCache.texstage[i].addressingU]); + setSamplerState(i, D3DSAMP_ADDRESSV, addressConvMap[rwStateCache.texstage[i].addressingV]); + setSamplerState(i, D3DSAMP_MAGFILTER, filterConvMap_NoMIP[rwStateCache.texstage[i].filter]); + setSamplerState(i, D3DSAMP_MINFILTER, filterConvMap_NoMIP[rwStateCache.texstage[i].filter]); + } + for(s = 0; s < MAXNUMSTATES; s++) + if(validStates[s]) + d3ddevice->SetRenderState((D3DRENDERSTATETYPE)s, d3dStates[s]); + for(t = 0; t < MAXNUMTEXSTATES; t++) + if(validTexStates[t]) + for(s = 0; s < MAXNUMSTAGES; s++) + d3ddevice->SetTextureStageState(s, (D3DTEXTURESTAGESTATETYPE)t, d3dTextureStageStates[t][s]); + for(t = 1; t < MAXNUMSAMPLERSTATES; t++) + for(s = 0; s < MAXNUMSTAGES; s++) + d3ddevice->SetSamplerState(s, (D3DSAMPLERSTATETYPE)t, d3dSamplerStates[t][s]); + d3ddevice->SetMaterial(&d3dmaterial); + + d3ddevice->SetVertexShader(deviceCache.vertexShader); + d3ddevice->SetPixelShader(deviceCache.pixelShader); + d3ddevice->SetVertexDeclaration(deviceCache.vertexDeclaration); + d3ddevice->SetIndices(deviceCache.indices); + for(i = 0; i < MAXNUMSTREAMS; i++) + d3ddevice->SetStreamSource(i, deviceCache.vertexStreams[i].buffer, deviceCache.vertexStreams[i].offset, deviceCache.vertexStreams[i].stride); +} + +void +evictD3D9Raster(Raster *raster) +{ + int i; + // Make sure we're not still referencing this raster + D3dRaster *natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + switch(raster->type){ + case Raster::CAMERATEXTURE: + for(i = 0; i < MAXNUMRENDERTARGETS; i++) + if(deviceCache.renderTargets[i] == natras->texture) + setRenderTarget(i, i == 0 ? d3d9Globals.defaultRenderTarget : nil); + // fall through + case Raster::NORMAL: + case Raster::TEXTURE: + for(i = 0; i < MAXNUMSTAGES; i++) + if(rwStateCache.texstage[i].raster == raster) + rwStateCache.texstage[i].raster = nil; + break; + case Raster::ZBUFFER: + if(natras->texture == deviceCache.depthSurface) + setDepthSurface(d3d9Globals.defaultDepthSurf); + break; + } +} + +// RW render state + +static void +setDepthTest(bool32 enable) +{ + if(rwStateCache.ztest != enable){ + rwStateCache.ztest = enable; + if(rwStateCache.zwrite && !enable){ + // If we still want to write, enable but set mode to always + setRenderState(D3DRS_ZENABLE, TRUE); + setRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); + }else{ + setRenderState(D3DRS_ZENABLE, rwStateCache.ztest); + setRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); + } + } +} + +static void +setDepthWrite(bool32 enable) +{ + if(rwStateCache.zwrite != enable){ + rwStateCache.zwrite = enable; + if(enable && !rwStateCache.ztest){ + // Have to switch on ztest so writing can work + setRenderState(D3DRS_ZENABLE, TRUE); + setRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); + } + setRenderState(D3DRS_ZWRITEENABLE, rwStateCache.zwrite); + } +} + +static void +setVertexAlpha(bool32 enable) +{ + if(rwStateCache.vertexAlpha != enable){ + if(!rwStateCache.textureAlpha){ + setRenderState(D3DRS_ALPHABLENDENABLE, enable); + setRenderState(D3DRS_ALPHATESTENABLE, enable); + } + rwStateCache.vertexAlpha = enable; + } +} + +void +setRasterStage(uint32 stage, Raster *raster) +{ + bool32 alpha; + D3dRaster *d3draster = nil; + if(raster != rwStateCache.texstage[stage].raster){ + rwStateCache.texstage[stage].raster = raster; + if(raster){ + assert(raster->platform == PLATFORM_D3D8 || + raster->platform == PLATFORM_D3D9); + d3draster = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + d3ddevice->SetTexture(stage, (IDirect3DTexture9*)d3draster->texture); + alpha = d3draster->hasAlpha; + }else{ + d3ddevice->SetTexture(stage, nil); + alpha = 0; + } + if(stage == 0){ + if(rwStateCache.textureAlpha != alpha){ + rwStateCache.textureAlpha = alpha; + if(!rwStateCache.vertexAlpha){ + setRenderState(D3DRS_ALPHABLENDENABLE, alpha); + setRenderState(D3DRS_ALPHATESTENABLE, alpha); + } + } + } + } +} + +static void +setFilterMode(uint32 stage, int32 filter) +{ + // TODO: mip mapping + if(rwStateCache.texstage[stage].filter != (Texture::FilterMode)filter){ + rwStateCache.texstage[stage].filter = (Texture::FilterMode)filter; + setSamplerState(stage, D3DSAMP_MAGFILTER, filterConvMap_NoMIP[filter]); + setSamplerState(stage, D3DSAMP_MINFILTER, filterConvMap_NoMIP[filter]); + } +} + +static void +setAddressU(uint32 stage, int32 addressing) +{ + if(rwStateCache.texstage[stage].addressingU != (Texture::Addressing)addressing){ + rwStateCache.texstage[stage].addressingU = (Texture::Addressing)addressing; + setSamplerState(stage, D3DSAMP_ADDRESSU, addressConvMap[addressing]); + } +} + +static void +setAddressV(uint32 stage, int32 addressing) +{ + if(rwStateCache.texstage[stage].addressingV != (Texture::Addressing)addressing){ + rwStateCache.texstage[stage].addressingV = (Texture::Addressing)addressing; + setSamplerState(stage, D3DSAMP_ADDRESSV, addressConvMap[addressing]); + } +} + +void +setTexture(uint32 stage, Texture *tex) +{ + if(tex == nil){ + setRasterStage(stage, nil); + return; + } + if(tex->raster){ + setFilterMode(stage, tex->getFilter()); + setAddressU(stage, tex->getAddressU()); + setAddressV(stage, tex->getAddressV()); + } + setRasterStage(stage, tex->raster); +} + +void +setD3dMaterial(D3DMATERIAL9 *mat9) +{ + if(d3dmaterial.Diffuse.r != mat9->Diffuse.r || + d3dmaterial.Diffuse.g != mat9->Diffuse.g || + d3dmaterial.Diffuse.b != mat9->Diffuse.b || + d3dmaterial.Diffuse.a != mat9->Diffuse.a || + d3dmaterial.Ambient.r != mat9->Ambient.r || + d3dmaterial.Ambient.g != mat9->Ambient.g || + d3dmaterial.Ambient.b != mat9->Ambient.b || + d3dmaterial.Ambient.a != mat9->Ambient.a || + d3dmaterial.Specular.r != mat9->Specular.r || + d3dmaterial.Specular.g != mat9->Specular.g || + d3dmaterial.Specular.b != mat9->Specular.b || + d3dmaterial.Specular.a != mat9->Specular.a || + d3dmaterial.Emissive.r != mat9->Emissive.r || + d3dmaterial.Emissive.g != mat9->Emissive.g || + d3dmaterial.Emissive.b != mat9->Emissive.b || + d3dmaterial.Emissive.a != mat9->Emissive.a || + d3dmaterial.Power != mat9->Power){ + d3ddevice->SetMaterial(mat9); + d3dmaterial = *mat9; + } +} + +void +setMaterial_fix(const RGBA &color, const SurfaceProperties &surfProps) +{ + D3DMATERIAL9 mat9; + D3DCOLORVALUE black = { 0, 0, 0, 0 }; + float ambmult = surfProps.ambient/255.0f; + float diffmult = surfProps.diffuse/255.0f; + mat9.Ambient.r = color.red*ambmult; + mat9.Ambient.g = color.green*ambmult; + mat9.Ambient.b = color.blue*ambmult; + mat9.Ambient.a = color.alpha; + mat9.Diffuse.r = color.red*diffmult; + mat9.Diffuse.g = color.green*diffmult; + mat9.Diffuse.b = color.blue*diffmult; + mat9.Diffuse.a = color.alpha; + mat9.Power = 0.0f; + mat9.Emissive = black; + mat9.Specular = black; + setD3dMaterial(&mat9); +} + + +void +setMaterial(const RGBA &color, const SurfaceProperties &surfaceprops) +{ + if(!equal(d3dShaderState.matColor, color)){ + rw::RGBAf col; + convColor(&col, &color); + d3ddevice->SetVertexShaderConstantF(VSLOC_matColor, (float*)&col, 1); + d3dShaderState.matColor = color; + } + + if(d3dShaderState.surfProps.ambient != surfaceprops.ambient || + d3dShaderState.surfProps.specular != surfaceprops.specular || + d3dShaderState.surfProps.diffuse != surfaceprops.diffuse){ + float surfProps[4]; + surfProps[0] = surfaceprops.ambient; + surfProps[1] = surfaceprops.specular; + surfProps[2] = surfaceprops.diffuse; + surfProps[3] = 0.0f; + d3ddevice->SetVertexShaderConstantF(VSLOC_surfProps, surfProps, 1); + d3dShaderState.surfProps = surfaceprops; + } +} + +static void +setRwRenderState(int32 state, void *pvalue) +{ + uint32 value = (uint32)(uintptr)pvalue; + uint32 bval = value ? TRUE : FALSE; + switch(state){ + case TEXTURERASTER: + setRasterStage(0, (Raster*)pvalue); + break; + case TEXTUREADDRESS: + setAddressU(0, value); + setAddressV(0, value); + break; + case TEXTUREADDRESSU: + setAddressU(0, value); + break; + case TEXTUREADDRESSV: + setAddressV(0, value); + break; + case TEXTUREFILTER: + setFilterMode(0, value); + break; + case VERTEXALPHA: + setVertexAlpha(bval); + break; + case SRCBLEND: + if(rwStateCache.srcblend != value){ + rwStateCache.srcblend = value; + setRenderState(D3DRS_SRCBLEND, blendMap[value]); + } + break; + case DESTBLEND: + if(rwStateCache.destblend != value){ + rwStateCache.destblend = value; + setRenderState(D3DRS_DESTBLEND, blendMap[value]); + } + break; + case ZTESTENABLE: + setDepthTest(bval); + break; + case ZWRITEENABLE: + setDepthWrite(bval); + break; + case FOGENABLE: + if(rwStateCache.fogenable != bval){ + rwStateCache.fogenable = bval; +// setRenderState(D3DRS_FOGENABLE, rwStateCache.fogenable); + d3dShaderState.fogData.disable = bval ? 0.0f : 1.0f; + d3dShaderState.fogDirty = true; + }; + break; + case FOGCOLOR:{ + RGBA c; + c.red = value; + c.green = value>>8; + c.blue = value>>16; + c.alpha = value>>24; + if(!equal(rwStateCache.fogcolor, c)){ + rwStateCache.fogcolor = c; + setRenderState(D3DRS_FOGCOLOR, D3DCOLOR_RGBA(c.red, c.green, c.blue, c.alpha)); + convColor(&d3dShaderState.fogColor, &c); + d3dShaderState.fogDirty = true; + }} break; + case CULLMODE: + if(rwStateCache.cullmode != value){ + rwStateCache.cullmode = value; + setRenderState(D3DRS_CULLMODE, cullmodeMap[value]); + } + break; + case ALPHATESTFUNC: + if(rwStateCache.alphafunc != value){ + rwStateCache.alphafunc = value; + setRenderState(D3DRS_ALPHAFUNC, alphafuncMap[rwStateCache.alphafunc]); + } + break; + case ALPHATESTREF: + if(rwStateCache.alpharef != value){ + rwStateCache.alpharef = value; + setRenderState(D3DRS_ALPHAREF, rwStateCache.alpharef); + } + break; + case GSALPHATEST: + rwStateCache.gsalpha = value; + break; + case GSALPHATESTREF: + rwStateCache.gsalpharef = value; + break; + } +} + +static void* +getRwRenderState(int32 state) +{ + uint32 val; + switch(state){ + case TEXTURERASTER: + return rwStateCache.texstage[0].raster; + case TEXTUREADDRESS: + if(rwStateCache.texstage[0].addressingU == rwStateCache.texstage[0].addressingV) + val = rwStateCache.texstage[0].addressingU; + else + val = 0; // invalid + break; + case TEXTUREADDRESSU: + val = rwStateCache.texstage[0].addressingU; + break; + case TEXTUREADDRESSV: + val = rwStateCache.texstage[0].addressingV; + break; + case TEXTUREFILTER: + val = rwStateCache.texstage[0].filter; + break; + + case VERTEXALPHA: + val = rwStateCache.vertexAlpha; + break; + case SRCBLEND: + val = rwStateCache.srcblend; + break; + case DESTBLEND: + val = rwStateCache.destblend; + break; + case ZTESTENABLE: + val = rwStateCache.ztest; + break; + case ZWRITEENABLE: + val = rwStateCache.zwrite; + break; + case FOGENABLE: + val = rwStateCache.fogenable; + break; + case FOGCOLOR: + val = RWRGBAINT(rwStateCache.fogcolor.red, rwStateCache.fogcolor.green, + rwStateCache.fogcolor.blue, rwStateCache.fogcolor.alpha); + break; + case CULLMODE: + val = rwStateCache.cullmode; + break; + case ALPHATESTFUNC: + val = rwStateCache.alphafunc; + break; + case ALPHATESTREF: + val = rwStateCache.alpharef; + break; + case GSALPHATEST: + val = rwStateCache.gsalpha; + break; + case GSALPHATESTREF: + val = rwStateCache.gsalpharef; + break; + default: + val = 0; + } + return (void*)(uintptr)val; +} + +// Shaders + +void +setVertexShader(void *vs) +{ + if(deviceCache.vertexShader != vs){ + deviceCache.vertexShader = (IDirect3DVertexShader9*)vs; + d3ddevice->SetVertexShader(deviceCache.vertexShader); + } +} + +void +setPixelShader(void *ps) +{ + if(deviceCache.pixelShader != ps){ + deviceCache.pixelShader = (IDirect3DPixelShader9*)ps; + d3ddevice->SetPixelShader(deviceCache.pixelShader); + } +} + +void +setIndices(void *indexBuffer) +{ + if(deviceCache.indices != indexBuffer){ + deviceCache.indices = (IDirect3DIndexBuffer9*)indexBuffer; + d3ddevice->SetIndices(deviceCache.indices); + } +} + +void +setStreamSource(int n, void *buffer, uint32 offset, uint32 stride) +{ + if(deviceCache.vertexStreams[n].buffer != buffer || + deviceCache.vertexStreams[n].offset != offset || + deviceCache.vertexStreams[n].stride != stride){ + deviceCache.vertexStreams[n].buffer = (IDirect3DVertexBuffer9*)buffer; + deviceCache.vertexStreams[n].offset = offset; + deviceCache.vertexStreams[n].stride = stride; + d3ddevice->SetStreamSource(n, deviceCache.vertexStreams[n].buffer, offset, stride); + } +} + +void +setVertexDeclaration(void *declaration) +{ + if(deviceCache.vertexDeclaration != declaration){ + deviceCache.vertexDeclaration = (IDirect3DVertexDeclaration9*)declaration; + d3ddevice->SetVertexDeclaration(deviceCache.vertexDeclaration); + } +} + +void* +createVertexShader(void *csosrc) +{ + void *shdr; + if(d3ddevice->CreateVertexShader((DWORD*)csosrc, (IDirect3DVertexShader9**)&shdr) == D3D_OK){ + d3d9Globals.numVertexShaders++; + return shdr; + } + return nil; +} + +void* +createPixelShader(void *csosrc) +{ + void *shdr; + if(d3ddevice->CreatePixelShader((DWORD*)csosrc, (IDirect3DPixelShader9**)&shdr) == D3D_OK){ + d3d9Globals.numPixelShaders++; + return shdr; + } + return nil; +} + +void +destroyVertexShader(void *shader) +{ + if(shader){ + ((IDirect3DVertexShader9*)shader)->Release(); + d3d9Globals.numVertexShaders--; + } +} + +void +destroyPixelShader(void *shader) +{ + if(shader){ + ((IDirect3DPixelShader9*)shader)->Release(); + d3d9Globals.numPixelShaders--; + } +} + + +// Camera + +static void +setRenderSurfaces(Camera *cam) +{ + Raster *fbuf = cam->frameBuffer; + assert(fbuf); + { + D3dRaster *natras = PLUGINOFFSET(D3dRaster, fbuf, nativeRasterOffset); + assert(fbuf->type == Raster::CAMERA || fbuf->type == Raster::CAMERATEXTURE); + if(natras->texture == nil) + setRenderTarget(0, d3d9Globals.defaultRenderTarget); + else{ + assert(fbuf->type == Raster::CAMERATEXTURE); + IDirect3DSurface9 *surf; + ((IDirect3DTexture9*)natras->texture)->GetSurfaceLevel(0, &surf); + setRenderTarget(0, surf); + surf->Release(); + } + } + + Raster *zbuf = cam->zBuffer; + if(zbuf){ + D3dRaster *natras = PLUGINOFFSET(D3dRaster, zbuf, nativeRasterOffset); + assert(zbuf->type == Raster::ZBUFFER); + setDepthSurface(natras->texture); + }else + setDepthSurface(nil); + +} + +static void +beginUpdate(Camera *cam) +{ + float view[16], proj[16]; + + // View Matrix + Matrix inv; + Matrix::invert(&inv, cam->getFrame()->getLTM()); + // Since we're looking into positive Z, + // flip X to ge a left handed view space. + view[0] = -inv.right.x; + view[1] = inv.right.y; + view[2] = inv.right.z; + view[3] = 0.0f; + view[4] = -inv.up.x; + view[5] = inv.up.y; + view[6] = inv.up.z; + view[7] = 0.0f; + view[8] = -inv.at.x; + view[9] = inv.at.y; + view[10] = inv.at.z; + view[11] = 0.0f; + view[12] = -inv.pos.x; + view[13] = inv.pos.y; + view[14] = inv.pos.z; + view[15] = 1.0f; + memcpy(&cam->devView, view, sizeof(RawMatrix)); +// d3ddevice->SetTransform(D3DTS_VIEW, (D3DMATRIX*)view); + + // Projection Matrix + float32 invwx = 1.0f/cam->viewWindow.x; + float32 invwy = 1.0f/cam->viewWindow.y; + float32 invz = 1.0f/(cam->farPlane-cam->nearPlane); + + proj[0] = invwx; + proj[1] = 0.0f; + proj[2] = 0.0f; + proj[3] = 0.0f; + + proj[4] = 0.0f; + proj[5] = invwy; + proj[6] = 0.0f; + proj[7] = 0.0f; + + proj[8] = cam->viewOffset.x*invwx; + proj[9] = cam->viewOffset.y*invwy; + proj[12] = -proj[8]; + proj[13] = -proj[9]; + if(cam->projection == Camera::PERSPECTIVE){ + proj[10] = cam->farPlane*invz; + proj[11] = 1.0f; + + proj[15] = 0.0f; + }else{ + proj[10] = invz; + proj[11] = 0.0f; + + proj[15] = 1.0f; + } + proj[14] = -cam->nearPlane*proj[10]; + memcpy(&cam->devProj, proj, sizeof(RawMatrix)); +// d3ddevice->SetTransform(D3DTS_PROJECTION, (D3DMATRIX*)proj); + + // TODO: figure out where this is really done +// setRenderState(D3DRS_FOGSTART, *(uint32*)&cam->fogPlane); +// setRenderState(D3DRS_FOGEND, *(uint32*)&cam->farPlane); + d3dShaderState.fogData.start = cam->fogPlane; + d3dShaderState.fogData.end = cam->farPlane; + d3dShaderState.fogData.range = 1.0f/(cam->fogPlane - cam->farPlane); + // TODO: not quite sure this is the right place to do this... + d3dShaderState.fogData.disable = rwStateCache.fogenable ? 0.0f : 1.0f; + d3dShaderState.fogDisable.start = 0.0f; + d3dShaderState.fogDisable.end = 0.0f; + d3dShaderState.fogDisable.range = 0.0f; + d3dShaderState.fogDisable.disable = 1.0f; + d3dShaderState.fogDirty = true; + + setRenderSurfaces(cam); + + D3DVIEWPORT9 vp; + vp.MinZ = 0.0f; + vp.MaxZ = 1.0f; + vp.X = cam->frameBuffer->offsetX; + vp.Y = cam->frameBuffer->offsetY; + vp.Width = cam->frameBuffer->width; + vp.Height = cam->frameBuffer->height; + d3ddevice->SetViewport(&vp); + + // TODO: figure out when to call this + d3ddevice->BeginScene(); +} + +static void +endUpdate(Camera *cam) +{ + // TODO: figure out when to call this + d3ddevice->EndScene(); +} + +// Manage video memory + +void +addVidmemRaster(Raster *raster) +{ + VidmemRaster *vmr = rwNewT(VidmemRaster, 1, ID_DRIVER | MEMDUR_EVENT); + vmr->raster = raster; + vmr->next = vidmemRasters; + vidmemRasters = vmr; +} + +void +removeVidmemRaster(Raster *raster) +{ + VidmemRaster **p, *vmr; + for(p = &vidmemRasters; *p; p = &(*p)->next) + if((*p)->raster == raster) + goto found; + return; +found: + vmr = *p; + *p = vmr->next; + rwFree(vmr); +} + +static void +releaseVidmemRasters(void) +{ + VidmemRaster *vmr; + Raster *raster; + D3dRaster *natras; + for(vmr = vidmemRasters; vmr; vmr = vmr->next){ + raster = vmr->raster; + natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + switch(raster->type){ + case Raster::CAMERATEXTURE: + destroyTexture(natras->texture); + natras->texture = nil; + break; + + case Raster::ZBUFFER: + // we'll leave the default surface dangling so we can tell the difference + if(natras->texture != d3d9Globals.defaultDepthSurf){ + ((IDirect3DSurface9*)natras->texture)->Release(); + natras->texture = nil; + } + break; + } + } +} + +static void +recreateVidmemRasters(void) +{ + VidmemRaster *vmr; + Raster *raster; + D3dRaster *natras; + for(vmr = vidmemRasters; vmr; vmr = vmr->next){ + raster = vmr->raster; + natras = PLUGINOFFSET(D3dRaster, raster, nativeRasterOffset); + switch(raster->type){ + case Raster::CAMERATEXTURE: { + int32 levels = Raster::calculateNumLevels(raster->width, raster->height); + IDirect3DTexture9 *tex; + d3ddevice->CreateTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? levels : 1, + D3DUSAGE_RENDERTARGET, + (D3DFORMAT)natras->format, D3DPOOL_DEFAULT, &tex, nil); + natras->texture = tex; + if(natras->texture) + d3d9Globals.numTextures++; + break; + } + + case Raster::ZBUFFER: + if(natras->texture){ + RECT rect; + GetClientRect(d3d9Globals.window, &rect); + raster->width = rect.right; + raster->height = rect.bottom; + natras->texture = d3d9Globals.defaultDepthSurf; + natras->format = d3d9Globals.present.AutoDepthStencilFormat; + raster->depth = findFormatDepth(natras->format); + }else{ + IDirect3DSurface9 *surf = nil; + d3ddevice->CreateDepthStencilSurface(raster->width, raster->height, (D3DFORMAT)natras->format, + d3d9Globals.present.MultiSampleType, d3d9Globals.present.MultiSampleQuality, + FALSE, &surf, nil); + natras->texture = surf; + } + break; + } + } +} + +void +addDynamicVB(uint32 length, uint32 fvf, IDirect3DVertexBuffer9 **buf) +{ + DynamicVB *dvb = rwNewT(DynamicVB, 1, ID_DRIVER | MEMDUR_EVENT); + dvb->length = length; + dvb->fvf = fvf; + dvb->buf = buf; + dvb->next = dynamicVBs; + dynamicVBs = dvb; +} + +void +removeDynamicVB(IDirect3DVertexBuffer9 **buf) +{ + DynamicVB **p, *dvb; + for(p = &dynamicVBs; *p; p = &(*p)->next) + if((*p)->buf == buf) + goto found; + return; +found: + dvb = *p; + *p = dvb->next; + rwFree(dvb); +} + +static void +releaseDynamicVBs(void) +{ + DynamicVB *dvb; + int i; + for(dvb = dynamicVBs; dvb; dvb = dvb->next){ + for(i = 0; i < MAXNUMSTREAMS; i++) + if(deviceCache.vertexStreams[i].buffer == *dvb->buf) + deviceCache.vertexStreams[i].buffer = nil; + destroyVertexBuffer(*dvb->buf); + *dvb->buf = nil; + } +} + +static void +recreateDynamicVBs(void) +{ + DynamicVB *dvb; + for(dvb = dynamicVBs; dvb; dvb = dvb->next){ + assert(*dvb->buf == nil); + *dvb->buf = (IDirect3DVertexBuffer9*)createVertexBuffer(dvb->length, dvb->fvf, true); + } +} + + +void +addDynamicIB(uint32 length, IDirect3DIndexBuffer9 **buf) +{ + DynamicIB *ivb = rwNewT(DynamicIB, 1, ID_DRIVER | MEMDUR_EVENT); + ivb->length = length; + ivb->buf = buf; + ivb->next = dynamicIBs; + dynamicIBs = ivb; +} + +void +removeDynamicIB(IDirect3DIndexBuffer9 **buf) +{ + DynamicIB **p, *ivb; + for(p = &dynamicIBs; *p; p = &(*p)->next) + if((*p)->buf == buf) + goto found; + return; +found: + ivb = *p; + *p = ivb->next; + rwFree(ivb); +} + +static void +releaseDynamicIBs(void) +{ + DynamicIB *ivb; + for(ivb = dynamicIBs; ivb; ivb = ivb->next){ + if(deviceCache.indices == *ivb->buf) + deviceCache.indices = nil; + destroyIndexBuffer(*ivb->buf); + *ivb->buf = nil; + } +} + +static void +recreateDynamicIBs(void) +{ + DynamicIB *ivb; + for(ivb = dynamicIBs; ivb; ivb = ivb->next){ + assert(*ivb->buf == nil); + *ivb->buf = (IDirect3DIndexBuffer9*)createIndexBuffer(ivb->length, true); + } +} + +static void +releaseVideoMemory(void) +{ + int32 i; + for(i = 0; i < MAXNUMSTAGES; i++) + d3ddevice->SetTexture(i, nil); + d3ddevice->SetVertexDeclaration(nil); + d3ddevice->SetVertexShader(nil); + d3ddevice->SetPixelShader(nil); + d3ddevice->SetIndices(nil); + for(i = 0; i < MAXNUMSTREAMS; i++) + d3ddevice->SetStreamSource(0, nil, 0, 0); + + setRenderTarget(0, d3d9Globals.defaultRenderTarget); + for(i = 1; i < MAXNUMRENDERTARGETS; i++) + setRenderTarget(i, nil); + setDepthSurface(d3d9Globals.defaultDepthSurf); + + releaseVidmemRasters(); + releaseDynamicVBs(); + releaseDynamicIBs(); +} + +static void +restoreVideoMemory(void) +{ + // Have to get these back before recreating rasters + d3ddevice->GetRenderTarget(0, &d3d9Globals.defaultRenderTarget); + d3d9Globals.defaultRenderTarget->Release(); // refcount increased by Get + deviceCache.renderTargets[0] = d3d9Globals.defaultRenderTarget; + d3ddevice->GetDepthStencilSurface(&d3d9Globals.defaultDepthSurf); + d3d9Globals.defaultDepthSurf->Release(); // refcount increased by Get + deviceCache.depthSurface = d3d9Globals.defaultDepthSurf; + + recreateDynamicIBs(); + recreateDynamicVBs(); + // important that we get all raster back before restoring state + recreateVidmemRasters(); + + restoreD3d9Device(); +} + +static void +clearCamera(Camera *cam, RGBA *col, uint32 mode) +{ + int flags = 0; + if(mode & Camera::CLEARIMAGE) + mode |= D3DCLEAR_TARGET; + if(mode & Camera::CLEARZ) + mode |= D3DCLEAR_ZBUFFER; + D3DCOLOR c = D3DCOLOR_RGBA(col->red, col->green, col->blue, col->alpha); + + RECT r; + GetClientRect(d3d9Globals.window, &r); + BOOL icon = IsIconic(d3d9Globals.window); + Raster *ras = cam->frameBuffer; + if(!icon && + (r.right != d3d9Globals.present.BackBufferWidth || r.bottom != d3d9Globals.present.BackBufferHeight)){ + + d3d9Globals.present.BackBufferWidth = r.right; + d3d9Globals.present.BackBufferHeight = r.bottom; + + releaseVideoMemory(); + d3d::d3ddevice->Reset(&d3d9Globals.present); + restoreVideoMemory(); + } + + setRenderSurfaces(cam); + + d3ddevice->Clear(0, 0, mode, c, 1.0f, 0); +} + +static void +showRaster(Raster *raster, uint32 flag) +{ + UINT interval = flag & Raster::FLIPWAITVSYNCH ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE; + if(d3d9Globals.present.PresentationInterval != interval){ + d3d9Globals.present.PresentationInterval = interval; + releaseVideoMemory(); + d3d::d3ddevice->Reset(&d3d9Globals.present); + restoreVideoMemory(); + } + + // not used but we want cameras to have rasters + assert(raster); + HRESULT res = d3ddevice->Present(nil, nil, 0, nil); + + if(res == D3DERR_DEVICELOST){ + res = d3ddevice->TestCooperativeLevel(); + // lost while being minimized, not reset once we're back + if(res == D3DERR_DEVICENOTRESET){ + releaseVideoMemory(); + d3d::d3ddevice->Reset(&d3d9Globals.present); + restoreVideoMemory(); + } + } +} + +static bool32 +rasterRenderFast(Raster *raster, int32 x, int32 y) +{ + IDirect3DTexture9 *dsttex; + IDirect3DSurface9 *srcsurf, *dstsurf; + D3DSURFACE_DESC srcdesc, dstdesc; + RECT rect = { x, y, x, y }; + + Raster *src = raster; + Raster *dst = Raster::getCurrentContext(); + D3dRaster *natdst = PLUGINOFFSET(D3dRaster, dst, nativeRasterOffset); + D3dRaster *natsrc = PLUGINOFFSET(D3dRaster, src, nativeRasterOffset); + + switch(dst->type){ + case Raster::CAMERATEXTURE: + switch(src->type){ + case Raster::CAMERA: + dsttex = (IDirect3DTexture9*)natdst->texture; + dsttex->GetSurfaceLevel(0, &dstsurf); + assert(dstsurf); + dstsurf->GetDesc(&dstdesc); + + d3ddevice->GetRenderTarget(0, &srcsurf); + assert(srcsurf); + srcsurf->GetDesc(&srcdesc); + + rect.right += srcdesc.Width; + rect.bottom += srcdesc.Height; + + d3ddevice->StretchRect(srcsurf, &rect, dstsurf, &rect, D3DTEXF_NONE); + dstsurf->Release(); + srcsurf->Release(); + return 1; + } + break; + } + + return 0; +} + + +// +// Device +// + +int +findFormatDepth(uint32 format) +{ + // not all formats actually + switch(format){ + case D3DFMT_R8G8B8: return 24; + case D3DFMT_A8R8G8B8: return 32; + case D3DFMT_X8R8G8B8: return 32; + case D3DFMT_R5G6B5: return 16; + case D3DFMT_X1R5G5B5: return 16; + case D3DFMT_A1R5G5B5: return 16; + case D3DFMT_A4R4G4B4: return 16; + case D3DFMT_R3G3B2: return 8; + case D3DFMT_A8: return 8; + case D3DFMT_A8R3G3B2: return 16; + case D3DFMT_X4R4G4B4: return 16; + case D3DFMT_A2B10G10R10: return 32; + case D3DFMT_A8B8G8R8: return 32; + case D3DFMT_X8B8G8R8: return 32; + case D3DFMT_G16R16: return 32; + case D3DFMT_A2R10G10B10: return 32; + case D3DFMT_A16B16G16R16: return 64; + + case D3DFMT_L8: return 8; + case D3DFMT_D16: return 16; + case D3DFMT_D24S8: return 32; + case D3DFMT_D24X8: return 32; + case D3DFMT_D24X4S4: return 32; + case D3DFMT_D32: return 32; + + default: return 0; + } +} + +// the commented ones don't "work" +static D3DFORMAT fbFormats[] = { +// D3DFMT_A1R5G5B5, +/// D3DFMT_A2R10G10B10, // works but let's not use it... +// D3DFMT_A8R8G8B8, + D3DFMT_X8R8G8B8, +// D3DFMT_X1R5G5B5, + D3DFMT_R5G6B5 +}; + +static void +addVideoMode(D3DDISPLAYMODE *mode) +{ + int i; + + for(i = 1; i < d3d9Globals.numModes; i++){ + if(d3d9Globals.modes[i].mode.Width == mode->Width && + d3d9Globals.modes[i].mode.Height == mode->Height && + d3d9Globals.modes[i].mode.Format == mode->Format){ + // had this format already, remember highest refresh rate + if(mode->RefreshRate > d3d9Globals.modes[i].mode.RefreshRate) + d3d9Globals.modes[i].mode.RefreshRate = mode->RefreshRate; + return; + } + } + + // none found, add + d3d9Globals.modes[d3d9Globals.numModes].mode = *mode; + d3d9Globals.modes[d3d9Globals.numModes].flags = VIDEOMODEEXCLUSIVE; + d3d9Globals.numModes++; +} + +static void +makeVideoModeList(void) +{ + int i, j; + D3DDISPLAYMODE mode; + + d3d9Globals.numModes = 1; + for(i = 0; i < nelem(fbFormats); i++) + d3d9Globals.numModes += d3d9Globals.d3d9->GetAdapterModeCount(d3d9Globals.adapter, fbFormats[i]); + + rwFree(d3d9Globals.modes); + d3d9Globals.modes = rwNewT(DisplayMode, d3d9Globals.numModes, ID_DRIVER | MEMDUR_EVENT); + + // first mode is current mode as windowed + d3d9Globals.d3d9->GetAdapterDisplayMode(d3d9Globals.adapter, &d3d9Globals.modes[0].mode); + d3d9Globals.modes[0].flags = 0; + d3d9Globals.numModes = 1; + + for(i = 0; i < nelem(fbFormats); i++){ + int n = d3d9Globals.d3d9->GetAdapterModeCount(d3d9Globals.adapter, fbFormats[i]); + for(j = 0; j < n; j++){ + d3d9Globals.d3d9->EnumAdapterModes(d3d9Globals.adapter, fbFormats[i], j, &mode); + addVideoMode(&mode); + } + } +} + +static int +openD3D(EngineOpenParams *params) +{ + HWND win = params->window; + + d3d9Globals.window = win; + d3d9Globals.numAdapters = 0; + d3d9Globals.modes = nil; + d3d9Globals.numModes = 0; + d3d9Globals.currentMode = 0; + + d3d9Globals.d3d9 = Direct3DCreate9(D3D_SDK_VERSION); + if(d3d9Globals.d3d9 == nil){ + RWERROR((ERR_GENERAL, "Direct3DCreate9() failed")); + return 0; + } + + d3d9Globals.numAdapters = d3d9Globals.d3d9->GetAdapterCount(); + d3d9Globals.adapter = 0; + + for(d3d9Globals.adapter = 0; d3d9Globals.adapter < d3d9Globals.numAdapters; d3d9Globals.adapter++) + if(d3d9Globals.d3d9->GetDeviceCaps(d3d9Globals.adapter, D3DDEVTYPE_HAL, &d3d9Globals.caps) == D3D_OK) + goto found; + // no adapter + d3d9Globals.d3d9->Release(); + d3d9Globals.d3d9 = nil; + RWERROR((ERR_GENERAL, "Direct3DCreate9() failed")); + return 0; + +found: + makeVideoModeList(); + return 1; +} + +static int +closeD3D(void) +{ + ULONG ref = d3d9Globals.d3d9->Release(); + if(ref != 0) + printf("IDirect3D9_Release did not destroy\n"); + d3d9Globals.d3d9 = nil; + rwFree(d3d9Globals.modes); + d3d9Globals.modes = nil; + d3d9Globals.numModes = 0; + d3d9Globals.currentMode = 0; + return 1; +} + +static int +startD3D(void) +{ + HRESULT hr; + int vp; + if(d3d9Globals.caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) + vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; + else + vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; + + uint32 width, height, depth; + D3DFORMAT format, zformat; + d3d9Globals.startMode = d3d9Globals.modes[d3d9Globals.currentMode]; + format = d3d9Globals.startMode.mode.Format; + + bool windowed = !(d3d9Globals.startMode.flags & VIDEOMODEEXCLUSIVE); + + // Use window size in windowed mode, otherwise get size from video mode + if(windowed){ + RECT rect; + GetClientRect(d3d9Globals.window, &rect); + width = rect.right - rect.left; + height = rect.bottom - rect.top; + }else{ + // this will be much better for restoring after iconification + SetWindowLong(d3d9Globals.window, GWL_STYLE, WS_POPUP); + width = d3d9Globals.startMode.mode.Width; + height = d3d9Globals.startMode.mode.Height; + } + + // See if we can get an alpha channel + if(format == D3DFMT_X8R8G8B8){ + if(d3d9Globals.d3d9->CheckDeviceType(d3d9Globals.adapter, D3DDEVTYPE_HAL, format, D3DFMT_A8R8G8B8, windowed) == D3D_OK) + format = D3DFMT_A8R8G8B8; + } + + depth = findFormatDepth(format); + + // TOOD: use something more sophisticated maybe? + if(depth == 32) + zformat = D3DFMT_D24S8; + else + zformat = D3DFMT_D16; + + d3d9Globals.present.BackBufferWidth = width; + d3d9Globals.present.BackBufferHeight = height; + d3d9Globals.present.BackBufferFormat = format; + d3d9Globals.present.BackBufferCount = 1; + d3d9Globals.present.MultiSampleType = D3DMULTISAMPLE_NONE; + d3d9Globals.present.MultiSampleQuality = 0; + d3d9Globals.present.SwapEffect = D3DSWAPEFFECT_DISCARD; + d3d9Globals.present.hDeviceWindow = d3d9Globals.window; + d3d9Globals.present.Windowed = windowed; + d3d9Globals.present.EnableAutoDepthStencil = true; + d3d9Globals.present.AutoDepthStencilFormat = zformat; + d3d9Globals.present.Flags = 0; + d3d9Globals.present.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; +// d3d9Globals.present.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + d3d9Globals.present.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + + assert(d3d::d3ddevice == nil); + + BOOL icon = IsIconic(d3d9Globals.window); + IDirect3DDevice9 *dev; + hr = d3d9Globals.d3d9->CreateDevice(d3d9Globals.adapter, D3DDEVTYPE_HAL, + d3d9Globals.window, vp, &d3d9Globals.present, &dev); + if(FAILED(hr)){ + RWERROR((ERR_GENERAL, "CreateDevice() failed")); + return 0; + } + d3d::d3ddevice = dev; + return 1; +} + +static int +initD3D(void) +{ + int32 s, t; + + d3ddevice->GetRenderTarget(0, &d3d9Globals.defaultRenderTarget); + d3d9Globals.defaultRenderTarget->Release(); // refcount increased by Get + deviceCache.renderTargets[0] = d3d9Globals.defaultRenderTarget; + d3ddevice->GetDepthStencilSurface(&d3d9Globals.defaultDepthSurf); + d3d9Globals.defaultDepthSurf->Release(); // refcount increased by Get + deviceCache.depthSurface = d3d9Globals.defaultDepthSurf; + + d3d9Globals.numTextures = 0; + d3d9Globals.numVertexShaders = 0; + d3d9Globals.numPixelShaders = 0; + d3d9Globals.numVertexBuffers = 0; + d3d9Globals.numIndexBuffers = 0; + d3d9Globals.numVertexDeclarations = 0; + + VertexConstantData constants; + constants.normal.x = 0.0f; + constants.normal.y = 0.0f; + constants.normal.z = 0.0f; + constants.color.red = 0; + constants.color.green = 0; + constants.color.blue = 0; + constants.color.alpha = 255; + for(s = 0; s < 8; s++){ + constants.texCoors[s].u = 0.0f; + constants.texCoors[s].v = 0.0f; + } + assert(constantVertexStream == nil); + constantVertexStream = createVertexBuffer(sizeof(constants)*10000, 0, false); + assert(constantVertexStream); + uint8 *lockedvertices = lockVertices(constantVertexStream, 0, sizeof(constants), D3DLOCK_NOSYSLOCK); + assert(lockedvertices); + memcpy(lockedvertices, &constants, sizeof(constants)); + unlockVertices(constantVertexStream); + setStreamSource(2, constantVertexStream, 0, 0); + + d3ddevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL); + rwStateCache.alphafunc = ALPHAGREATEREQUAL; + d3ddevice->SetRenderState(D3DRS_ALPHAREF, 10); + rwStateCache.alpharef = 10; + + rwStateCache.gsalpha = 0; + rwStateCache.gsalpharef = 128; + + d3ddevice->SetRenderState(D3DRS_FOGENABLE, FALSE); + rwStateCache.fogenable = 0; + d3ddevice->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_LINEAR); + + d3ddevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + rwStateCache.cullmode = CULLNONE; + + d3ddevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); + d3ddevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + rwStateCache.srcblend = BLENDSRCALPHA; + d3ddevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + rwStateCache.destblend = BLENDINVSRCALPHA; + d3ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE, 0); + rwStateCache.vertexAlpha = 0; + rwStateCache.textureAlpha = 0; + + setTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); +// setTextureStageState(0, D3DTSS_CONSTANT, 0xFFFFFFFF); +// setTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); +// setTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_CONSTANT); +// setTextureStageState(0, D3DTSS_COLOROP, D3DTA_CONSTANT); + + // These states exist, not all do + validStates[D3DRS_ZENABLE] = 1; + validStates[D3DRS_FILLMODE] = 1; + validStates[D3DRS_SHADEMODE] = 1; + validStates[D3DRS_ZWRITEENABLE] = 1; + validStates[D3DRS_ALPHATESTENABLE] = 1; + validStates[D3DRS_LASTPIXEL] = 1; + validStates[D3DRS_SRCBLEND] = 1; + validStates[D3DRS_DESTBLEND] = 1; + validStates[D3DRS_CULLMODE] = 1; + validStates[D3DRS_ZFUNC] = 1; + validStates[D3DRS_ALPHAREF] = 1; + validStates[D3DRS_ALPHAFUNC] = 1; + validStates[D3DRS_DITHERENABLE] = 1; + validStates[D3DRS_ALPHABLENDENABLE] = 1; + validStates[D3DRS_FOGENABLE] = 1; + validStates[D3DRS_SPECULARENABLE] = 1; + validStates[D3DRS_FOGCOLOR] = 1; + validStates[D3DRS_FOGTABLEMODE] = 1; + validStates[D3DRS_FOGSTART] = 1; + validStates[D3DRS_FOGEND] = 1; + validStates[D3DRS_FOGDENSITY] = 1; + validStates[D3DRS_RANGEFOGENABLE] = 1; + validStates[D3DRS_STENCILENABLE] = 1; + validStates[D3DRS_STENCILFAIL] = 1; + validStates[D3DRS_STENCILZFAIL] = 1; + validStates[D3DRS_STENCILPASS] = 1; + validStates[D3DRS_STENCILFUNC] = 1; + validStates[D3DRS_STENCILREF] = 1; + validStates[D3DRS_STENCILMASK] = 1; + validStates[D3DRS_STENCILWRITEMASK] = 1; + validStates[D3DRS_TEXTUREFACTOR] = 1; + validStates[D3DRS_WRAP0] = 1; + validStates[D3DRS_WRAP1] = 1; + validStates[D3DRS_WRAP2] = 1; + validStates[D3DRS_WRAP3] = 1; + validStates[D3DRS_WRAP4] = 1; + validStates[D3DRS_WRAP5] = 1; + validStates[D3DRS_WRAP6] = 1; + validStates[D3DRS_WRAP7] = 1; + validStates[D3DRS_CLIPPING] = 1; + validStates[D3DRS_LIGHTING] = 1; + validStates[D3DRS_AMBIENT] = 1; + validStates[D3DRS_FOGVERTEXMODE] = 1; + validStates[D3DRS_COLORVERTEX] = 1; + validStates[D3DRS_LOCALVIEWER] = 1; + validStates[D3DRS_NORMALIZENORMALS] = 1; + validStates[D3DRS_DIFFUSEMATERIALSOURCE] = 1; + validStates[D3DRS_SPECULARMATERIALSOURCE] = 1; + validStates[D3DRS_AMBIENTMATERIALSOURCE] = 1; + validStates[D3DRS_EMISSIVEMATERIALSOURCE] = 1; + validStates[D3DRS_VERTEXBLEND] = 1; + validStates[D3DRS_CLIPPLANEENABLE] = 1; + validStates[D3DRS_POINTSIZE] = 1; + validStates[D3DRS_POINTSIZE_MIN] = 1; + validStates[D3DRS_POINTSPRITEENABLE] = 1; + validStates[D3DRS_POINTSCALEENABLE] = 1; + validStates[D3DRS_POINTSCALE_A] = 1; + validStates[D3DRS_POINTSCALE_B] = 1; + validStates[D3DRS_POINTSCALE_C] = 1; + validStates[D3DRS_MULTISAMPLEANTIALIAS] = 1; + validStates[D3DRS_MULTISAMPLEMASK] = 1; + validStates[D3DRS_PATCHEDGESTYLE] = 1; + validStates[D3DRS_DEBUGMONITORTOKEN] = 1; + validStates[D3DRS_POINTSIZE_MAX] = 1; + validStates[D3DRS_INDEXEDVERTEXBLENDENABLE] = 1; + validStates[D3DRS_COLORWRITEENABLE] = 1; + validStates[D3DRS_TWEENFACTOR] = 1; + validStates[D3DRS_BLENDOP] = 1; + validStates[D3DRS_POSITIONDEGREE] = 1; + validStates[D3DRS_NORMALDEGREE] = 1; + validStates[D3DRS_SCISSORTESTENABLE] = 1; + validStates[D3DRS_SLOPESCALEDEPTHBIAS] = 1; + validStates[D3DRS_ANTIALIASEDLINEENABLE] = 1; + validStates[D3DRS_MINTESSELLATIONLEVEL] = 1; + validStates[D3DRS_MAXTESSELLATIONLEVEL] = 1; + validStates[D3DRS_ADAPTIVETESS_X] = 1; + validStates[D3DRS_ADAPTIVETESS_Y] = 1; + validStates[D3DRS_ADAPTIVETESS_Z] = 1; + validStates[D3DRS_ADAPTIVETESS_W] = 1; + validStates[D3DRS_ENABLEADAPTIVETESSELLATION] = 1; + validStates[D3DRS_TWOSIDEDSTENCILMODE] = 1; + validStates[D3DRS_CCW_STENCILFAIL] = 1; + validStates[D3DRS_CCW_STENCILZFAIL] = 1; + validStates[D3DRS_CCW_STENCILPASS] = 1; + validStates[D3DRS_CCW_STENCILFUNC] = 1; + validStates[D3DRS_COLORWRITEENABLE1] = 1; + validStates[D3DRS_COLORWRITEENABLE2] = 1; + validStates[D3DRS_COLORWRITEENABLE3] = 1; + validStates[D3DRS_BLENDFACTOR] = 1; + validStates[D3DRS_SRGBWRITEENABLE] = 1; + validStates[D3DRS_DEPTHBIAS] = 1; + validStates[D3DRS_WRAP8] = 1; + validStates[D3DRS_WRAP9] = 1; + validStates[D3DRS_WRAP10] = 1; + validStates[D3DRS_WRAP11] = 1; + validStates[D3DRS_WRAP12] = 1; + validStates[D3DRS_WRAP13] = 1; + validStates[D3DRS_WRAP14] = 1; + validStates[D3DRS_WRAP15] = 1; + validStates[D3DRS_SEPARATEALPHABLENDENABLE] = 1; + validStates[D3DRS_SRCBLENDALPHA] = 1; + validStates[D3DRS_DESTBLENDALPHA] = 1; + validStates[D3DRS_BLENDOPALPHA] = 1; + + validTexStates[D3DTSS_COLOROP] = 1; + validTexStates[D3DTSS_COLORARG1] = 1; + validTexStates[D3DTSS_COLORARG2] = 1; + validTexStates[D3DTSS_ALPHAOP] = 1; + validTexStates[D3DTSS_ALPHAARG1] = 1; + validTexStates[D3DTSS_ALPHAARG2] = 1; + validTexStates[D3DTSS_BUMPENVMAT00] = 1; + validTexStates[D3DTSS_BUMPENVMAT01] = 1; + validTexStates[D3DTSS_BUMPENVMAT10] = 1; + validTexStates[D3DTSS_BUMPENVMAT11] = 1; + validTexStates[D3DTSS_TEXCOORDINDEX] = 1; + validTexStates[D3DTSS_BUMPENVLSCALE] = 1; + validTexStates[D3DTSS_BUMPENVLOFFSET] = 1; + validTexStates[D3DTSS_TEXTURETRANSFORMFLAGS] = 1; + validTexStates[D3DTSS_COLORARG0] = 1; + validTexStates[D3DTSS_ALPHAARG0] = 1; + validTexStates[D3DTSS_RESULTARG] = 1; + validTexStates[D3DTSS_CONSTANT] = 1; + + // Save the current states + for(s = 0; s < MAXNUMSTATES; s++) + if(validStates[s]){ + d3ddevice->GetRenderState((D3DRENDERSTATETYPE)s, (DWORD*)&d3dStates[s]); + stateCache[s].value = d3dStates[s]; + } + for(t = 0; t < MAXNUMTEXSTATES; t++) + if(validTexStates[t]) + for(s = 0; s < MAXNUMSTAGES; s++){ + d3ddevice->GetTextureStageState(s, (D3DTEXTURESTAGESTATETYPE)t, (DWORD*)&d3dTextureStageStates[t][s]); + textureStageStateCache[t][s].value = d3dTextureStageStates[t][s]; + } + for(t = 1; t < MAXNUMSAMPLERSTATES; t++) + for(s = 0; s < MAXNUMSTAGES; s++){ + d3ddevice->GetSamplerState(s, (D3DSAMPLERSTATETYPE)t, (DWORD*)&d3dSamplerStates[t][s]); + d3dSamplerStates[t][s] = d3dSamplerStates[t][s]; + } + // init rw cache + for(t = 0; t < MAXNUMSTAGES; t++){ + setFilterMode(t, Texture::NEAREST); + setAddressU(t, Texture::WRAP); + setAddressV(t, Texture::WRAP); + } + + openIm2D(); + openIm3D(); + + return 1; +} + +static int +termD3D(void) +{ + destroyVertexBuffer(constantVertexStream); + constantVertexStream = nil; + + closeIm3D(); + closeIm2D(); + + releaseVideoMemory(); + + ULONG ref = d3d::d3ddevice->Release(); + if(ref != 0) + printf("IDirect3D9Device_Release did not destroy\n"); + d3d::d3ddevice = nil; + return 1; +} + +static int +finalizeD3D(void) +{ + return 1; +} + +static int +deviceSystem(DeviceReq req, void *arg, int32 n) +{ + D3DADAPTER_IDENTIFIER9 adapter; + VideoMode *rwmode; + + switch(req){ + case DEVICEOPEN: + return openD3D((EngineOpenParams*)arg); + case DEVICECLOSE: + return closeD3D(); + + case DEVICEINIT: + return startD3D() && initD3D(); + case DEVICETERM: + return termD3D(); + + case DEVICEFINALIZE: + return finalizeD3D(); + + + case DEVICEGETNUMSUBSYSTEMS: + return d3d9Globals.numAdapters; + + case DEVICEGETCURRENTSUBSYSTEM: + return d3d9Globals.adapter; + + case DEVICESETSUBSYSTEM: + if(n >= d3d9Globals.numAdapters) + return 0; + d3d9Globals.adapter = n; + if(d3d9Globals.d3d9->GetDeviceCaps(d3d9Globals.adapter, D3DDEVTYPE_HAL, &d3d9Globals.caps) != D3D_OK) + return 0; + makeVideoModeList(); + return 1; + + case DEVICEGETSUBSSYSTEMINFO: + if(n >= d3d9Globals.numAdapters) + return 0; + if(d3d9Globals.d3d9->GetAdapterIdentifier(d3d9Globals.adapter, 0, &adapter) != D3D_OK) + return 0; + strncpy(((SubSystemInfo*)arg)->name, adapter.Description, sizeof(SubSystemInfo::name)); + return 1; + + + case DEVICEGETNUMVIDEOMODES: + return d3d9Globals.numModes; + + case DEVICEGETCURRENTVIDEOMODE: + return d3d9Globals.currentMode; + + case DEVICESETVIDEOMODE: + if(n >= d3d9Globals.numModes) + return 0; + d3d9Globals.currentMode = n; + return 1; + + case DEVICEGETVIDEOMODEINFO: + rwmode = (VideoMode*)arg; + rwmode->width = d3d9Globals.modes[n].mode.Width; + rwmode->height = d3d9Globals.modes[n].mode.Height; + rwmode->depth = findFormatDepth(d3d9Globals.modes[n].mode.Format); + rwmode->flags = d3d9Globals.modes[n].flags; + return 1; + } + return 1; +} + +Device renderdevice = { + 0.0f, 1.0f, + d3d::beginUpdate, + d3d::endUpdate, + d3d::clearCamera, + d3d::showRaster, + d3d::rasterRenderFast, + d3d::setRwRenderState, + d3d::getRwRenderState, + d3d::im2DRenderLine, + d3d::im2DRenderTriangle, + d3d::im2DRenderPrimitive, + d3d::im2DRenderIndexedPrimitive, + d3d::im3DTransform, + d3d::im3DRenderPrimitive, + d3d::im3DRenderIndexedPrimitive, + d3d::im3DEnd, + d3d::deviceSystem, +}; + +#endif +} +} diff --git a/src/d3d/d3dimmed.cpp b/src/d3d/d3dimmed.cpp new file mode 100644 index 0000000..7376591 --- /dev/null +++ b/src/d3d/d3dimmed.cpp @@ -0,0 +1,364 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" +#include "rwd3d9.h" +#include "rwd3dimpl.h" + +namespace rw { +namespace d3d { + +#ifdef RW_D3D9 + +// might want to tweak this +#define NUMINDICES 10000 +#define NUMVERTICES 10000 + +static int primTypeMap[] = { + D3DPT_POINTLIST, // invalid + D3DPT_LINELIST, + D3DPT_LINESTRIP, + D3DPT_TRIANGLELIST, + D3DPT_TRIANGLESTRIP, + D3DPT_TRIANGLEFAN, + D3DPT_POINTLIST, // actually not supported! +}; + +static IDirect3DVertexDeclaration9 *im2ddecl; +static IDirect3DVertexBuffer9 *im2dvertbuf; +static IDirect3DIndexBuffer9 *im2dindbuf; + +void *im2dOverridePS; + +void +openIm2D(void) +{ + D3DVERTEXELEMENT9 elements[4] = { + { 0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITIONT, 0 }, + { 0, offsetof(Im2DVertex, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 }, + { 0, offsetof(Im2DVertex, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, + D3DDECL_END() + }; + assert(im2ddecl == nil); + im2ddecl = (IDirect3DVertexDeclaration9*)d3d9::createVertexDeclaration((d3d9::VertexElement*)elements); + assert(im2ddecl); + + assert(im2dvertbuf == nil); + im2dvertbuf = (IDirect3DVertexBuffer9*)createVertexBuffer(NUMVERTICES*sizeof(Im2DVertex), 0, true); + assert(im2dvertbuf); + addDynamicVB(NUMVERTICES*sizeof(Im2DVertex), 0, &im2dvertbuf); + + assert(im2dindbuf == nil); + im2dindbuf = (IDirect3DIndexBuffer9*)createIndexBuffer(NUMINDICES*sizeof(uint16), true); + assert(im2dindbuf); + addDynamicIB(NUMINDICES*sizeof(uint16), &im2dindbuf); +} + +void +closeIm2D(void) +{ + d3d9::destroyVertexDeclaration(im2ddecl); + im2ddecl = nil; + + removeDynamicVB(&im2dvertbuf); + destroyVertexBuffer(im2dvertbuf); + im2dvertbuf = nil; + + removeDynamicIB(&im2dindbuf); + destroyIndexBuffer(im2dindbuf); + im2dindbuf = nil; +} + +static Im2DVertex tmpprimbuf[3]; + +void +im2DRenderLine(void *vertices, int32 numVertices, int32 vert1, int32 vert2) +{ + Im2DVertex *verts = (Im2DVertex*)vertices; + tmpprimbuf[0] = verts[vert1]; + tmpprimbuf[1] = verts[vert2]; + im2DRenderPrimitive(PRIMTYPELINELIST, tmpprimbuf, 2); +} + +void +im2DRenderTriangle(void *vertices, int32 numVertices, int32 vert1, int32 vert2, int32 vert3) +{ + Im2DVertex *verts = (Im2DVertex*)vertices; + tmpprimbuf[0] = verts[vert1]; + tmpprimbuf[1] = verts[vert2]; + tmpprimbuf[2] = verts[vert3]; + im2DRenderPrimitive(PRIMTYPETRILIST, tmpprimbuf, 3); +} + +void +im2DRenderPrimitive(PrimitiveType primType, void *vertices, int32 numVertices) +{ + if(numVertices > NUMVERTICES){ + // TODO: error + return; + } + uint8 *lockedvertices = lockVertices(im2dvertbuf, 0, numVertices*sizeof(Im2DVertex), D3DLOCK_DISCARD); + memcpy(lockedvertices, vertices, numVertices*sizeof(Im2DVertex)); + unlockVertices(im2dvertbuf); + + setStreamSource(0, im2dvertbuf, 0, sizeof(Im2DVertex)); + setVertexDeclaration(im2ddecl); + + if(im2dOverridePS) + setPixelShader(im2dOverridePS); + else if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(im2d_tex_PS); + else + setPixelShader(im2d_PS); + + d3d::flushCache(); + + uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = numVertices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = numVertices-1; + break; + case PRIMTYPETRILIST: + primCount = numVertices/3; + break; + case PRIMTYPETRISTRIP: + primCount = numVertices-2; + break; + case PRIMTYPETRIFAN: + primCount = numVertices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = numVertices; + break; + } + d3ddevice->DrawPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, primCount); +} + +void +im2DRenderIndexedPrimitive(PrimitiveType primType, + void *vertices, int32 numVertices, void *indices, int32 numIndices) +{ + if(numVertices > NUMVERTICES || + numIndices > NUMINDICES){ + // TODO: error + return; + } + uint16 *lockedindices = lockIndices(im2dindbuf, 0, numIndices*sizeof(uint16), D3DLOCK_DISCARD); + memcpy(lockedindices, indices, numIndices*sizeof(uint16)); + unlockIndices(im2dindbuf); + + uint8 *lockedvertices = lockVertices(im2dvertbuf, 0, numVertices*sizeof(Im2DVertex), D3DLOCK_DISCARD); + memcpy(lockedvertices, vertices, numVertices*sizeof(Im2DVertex)); + unlockVertices(im2dvertbuf); + + setStreamSource(0, im2dvertbuf, 0, sizeof(Im2DVertex)); + setIndices(im2dindbuf); + setVertexDeclaration(im2ddecl); + + if(im2dOverridePS) + setPixelShader(im2dOverridePS); + else if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(im2d_tex_PS); + else + setPixelShader(im2d_PS); + + d3d::flushCache(); + + uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = numIndices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = numIndices-1; + break; + case PRIMTYPETRILIST: + primCount = numIndices/3; + break; + case PRIMTYPETRISTRIP: + primCount = numIndices-2; + break; + case PRIMTYPETRIFAN: + primCount = numIndices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = numIndices; + break; + } + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, + 0, numVertices, + 0, primCount); +} + + +// Im3D + + +static IDirect3DVertexDeclaration9 *im3ddecl; +static IDirect3DVertexBuffer9 *im3dvertbuf; +static IDirect3DIndexBuffer9 *im3dindbuf; +static int32 num3DVertices; + +void +openIm3D(void) +{ + D3DVERTEXELEMENT9 elements[4] = { + { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, + { 0, offsetof(Im3DVertex, color), D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 }, + { 0, offsetof(Im3DVertex, u), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, + D3DDECL_END() + }; + + assert(im3ddecl == nil); + im3ddecl = (IDirect3DVertexDeclaration9*)d3d9::createVertexDeclaration((d3d9::VertexElement*)elements); + assert(im3ddecl); + + assert(im3dvertbuf == nil); + im3dvertbuf = (IDirect3DVertexBuffer9*)createVertexBuffer(NUMVERTICES*sizeof(Im3DVertex), 0, true); + assert(im3dvertbuf); + addDynamicVB(NUMVERTICES*sizeof(Im3DVertex), 0, &im3dvertbuf); + + assert(im3dindbuf == nil); + im3dindbuf = (IDirect3DIndexBuffer9*)createIndexBuffer(NUMINDICES*sizeof(uint16), true); + assert(im3dindbuf); + addDynamicIB(NUMINDICES*sizeof(uint16), &im3dindbuf); +} + +void +closeIm3D(void) +{ + d3d9::destroyVertexDeclaration(im3ddecl); + im3ddecl = nil; + + removeDynamicVB(&im3dvertbuf); + destroyVertexBuffer(im3dvertbuf); + im3dvertbuf = nil; + + removeDynamicIB(&im3dindbuf); + destroyIndexBuffer(im3dindbuf); + im3dindbuf = nil; +} + +void +im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags) +{ + if(world == nil) + uploadMatrices(); + else + uploadMatrices(world); + + if((flags & im3d::VERTEXUV) == 0) + SetRenderStatePtr(TEXTURERASTER, nil); + + static RGBA white = { 255, 255, 255, 255 }; + static SurfaceProperties surfprops = { 0.0f, 0.0f, 0.0f }; + setMaterial(white, surfprops); + + uint8 *lockedvertices = lockVertices(im3dvertbuf, 0, numVertices*sizeof(Im3DVertex), D3DLOCK_DISCARD); + memcpy(lockedvertices, vertices, numVertices*sizeof(Im3DVertex)); + unlockVertices(im3dvertbuf); + + setStreamSource(0, im3dvertbuf, 0, sizeof(Im3DVertex)); + setVertexDeclaration(im3ddecl); + + setVertexShader(default_amb_VS); + + num3DVertices = numVertices; +} + +void +im3DRenderPrimitive(PrimitiveType primType) +{ + if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(default_tex_PS); + else + setPixelShader(default_PS); + + d3d::flushCache(); + + uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = num3DVertices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = num3DVertices-1; + break; + case PRIMTYPETRILIST: + primCount = num3DVertices/3; + break; + case PRIMTYPETRISTRIP: + primCount = num3DVertices-2; + break; + case PRIMTYPETRIFAN: + primCount = num3DVertices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = num3DVertices; + break; + } + d3ddevice->DrawPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, primCount); +} + +void +im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices) +{ + uint16 *lockedindices = lockIndices(im3dindbuf, 0, numIndices*sizeof(uint16), D3DLOCK_DISCARD); + memcpy(lockedindices, indices, numIndices*sizeof(uint16)); + unlockIndices(im3dindbuf); + + setIndices(im3dindbuf); + + if(engine->device.getRenderState(TEXTURERASTER)) + setPixelShader(default_tex_PS); + else + setPixelShader(default_PS); + + d3d::flushCache(); + + uint32 primCount = 0; + switch(primType){ + case PRIMTYPELINELIST: + primCount = numIndices/2; + break; + case PRIMTYPEPOLYLINE: + primCount = numIndices-1; + break; + case PRIMTYPETRILIST: + primCount = numIndices/3; + break; + case PRIMTYPETRISTRIP: + primCount = numIndices-2; + break; + case PRIMTYPETRIFAN: + primCount = numIndices-2; + break; + case PRIMTYPEPOINTLIST: + primCount = numIndices; + break; + } + d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)primTypeMap[primType], 0, + 0, num3DVertices, + 0, primCount); +} + +void +im3DEnd(void) +{ +} + +#endif +} +} diff --git a/src/d3d/d3drender.cpp b/src/d3d/d3drender.cpp new file mode 100644 index 0000000..2820a46 --- /dev/null +++ b/src/d3d/d3drender.cpp @@ -0,0 +1,404 @@ +#include +#include +#include +#include + +#define WITH_D3D +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwd3d.h" + +namespace rw { +namespace d3d { + +#ifdef RW_D3D9 +IDirect3DDevice9 *d3ddevice = nil; + +#define MAX_LIGHTS 8 + + +#define VS_NAME g_vs20_main +#define PS_NAME g_ps20_main +void *default_amb_VS; +void *default_amb_dir_VS; +void *default_all_VS; +void *default_PS; +void *default_tex_PS; +void *im2d_PS; +void *im2d_tex_PS; + + +void +createDefaultShaders(void) +{ + { + static +#include "shaders/default_amb_VS.h" + default_amb_VS = createVertexShader((void*)VS_NAME); + assert(default_amb_VS); + } + { + static +#include "shaders/default_amb_dir_VS.h" + default_amb_dir_VS = createVertexShader((void*)VS_NAME); + assert(default_amb_dir_VS); + } + { + static +#include "shaders/default_all_VS.h" + default_all_VS = createVertexShader((void*)VS_NAME); + assert(default_all_VS); + } + + { + static +#include "shaders/default_PS.h" + default_PS = createPixelShader((void*)PS_NAME); + assert(default_PS); + } + { + static +#include "shaders/default_tex_PS.h" + default_tex_PS = createPixelShader((void*)PS_NAME); + assert(default_tex_PS); + } + + { + static +#include "shaders/im2d_PS.h" + im2d_PS = createPixelShader((void*)PS_NAME); + assert(im2d_PS); + } + { + static +#include "shaders/im2d_tex_PS.h" + im2d_tex_PS = createPixelShader((void*)PS_NAME); + assert(im2d_tex_PS); + } +} + +void +destroyDefaultShaders(void) +{ + destroyVertexShader(default_amb_VS); + default_amb_VS = nil; + destroyVertexShader(default_amb_dir_VS); + default_amb_dir_VS = nil; + destroyVertexShader(default_all_VS); + default_all_VS = nil; + + destroyPixelShader(default_PS); + default_PS = nil; + destroyPixelShader(default_tex_PS); + default_tex_PS = nil; + + destroyPixelShader(im2d_PS); + im2d_PS = nil; + destroyPixelShader(im2d_tex_PS); + im2d_tex_PS = nil; +} + + +void +lightingCB_Fix(Atomic *atomic) +{ + WorldLights lightData; + Light *directionals[8]; + Light *locals[8]; + lightData.directionals = directionals; + lightData.numDirectionals = 8; + lightData.locals = locals; + lightData.numLocals = 8; + + ((World*)engine->currentWorld)->enumerateLights(atomic, &lightData); + + int i, n; + RGBA amb; + D3DLIGHT9 light; + light.Type = D3DLIGHT_DIRECTIONAL; + //light.Diffuse = { 0.8f, 0.8f, 0.8f, 1.0f }; + light.Specular = { 0.0f, 0.0f, 0.0f, 0.0f }; + light.Ambient = { 0.0f, 0.0f, 0.0f, 0.0f }; + light.Position = { 0.0f, 0.0f, 0.0f }; + //light.Direction = { 0.0f, 0.0f, -1.0f }; + light.Range = 0.0f; + light.Falloff = 0.0f; + light.Attenuation0 = 0.0f; + light.Attenuation1 = 0.0f; + light.Attenuation2 = 0.0f; + light.Theta = 0.0f; + light.Phi = 0.0f; + + convColor(&amb, &lightData.ambient); + d3d::setRenderState(D3DRS_AMBIENT, D3DCOLOR_RGBA(amb.red, amb.green, amb.blue, amb.alpha)); + + n = 0; + for(i = 0; i < lightData.numDirectionals; i++){ + if(n >= MAX_LIGHTS) + return; + Light *l = lightData.directionals[i]; + light.Type = D3DLIGHT_DIRECTIONAL; + light.Diffuse = *(D3DCOLORVALUE*)&l->color; + light.Direction = *(D3DVECTOR*)&l->getFrame()->getLTM()->at; + d3ddevice->SetLight(n, &light); + d3ddevice->LightEnable(n, TRUE); + n++; + } + + for(i = 0; i < lightData.numLocals; i++){ + if(n >= MAX_LIGHTS) + return; + Light *l = lightData.locals[i]; + switch(l->getType()){ + case Light::POINT: + light.Type = D3DLIGHT_POINT; + light.Diffuse = *(D3DCOLORVALUE*)&l->color; + light.Position = *(D3DVECTOR*)&l->getFrame()->getLTM()->pos; + light.Direction.x = 0.0f; + light.Direction.y = 0.0f; + light.Direction.z = 0.0f; + light.Range = l->radius; + light.Falloff = 1.0f; + light.Attenuation0 = 1.0f; + light.Attenuation1 = 0.0f/l->radius; + light.Attenuation2 = 5.0f/(l->radius*l->radius); + d3ddevice->SetLight(n, &light); + d3ddevice->LightEnable(n, TRUE); + n++; + break; + + case Light::SPOT: + light.Type = D3DLIGHT_SPOT; + light.Diffuse = *(D3DCOLORVALUE*)&l->color; + light.Position = *(D3DVECTOR*)&l->getFrame()->getLTM()->pos; + light.Direction = *(D3DVECTOR*)&l->getFrame()->getLTM()->at; + light.Range = l->radius; + light.Falloff = 1.0f; + light.Attenuation0 = 1.0f; + light.Attenuation1 = 0.0f/l->radius; + light.Attenuation2 = 5.0f/(l->radius*l->radius); + light.Theta = l->getAngle()*2.0f; + light.Phi = light.Theta; + d3ddevice->SetLight(n, &light); + d3ddevice->LightEnable(n, TRUE); + n++; + break; + + case Light::SOFTSPOT: + light.Type = D3DLIGHT_SPOT; + light.Diffuse = *(D3DCOLORVALUE*)&l->color; + light.Position = *(D3DVECTOR*)&l->getFrame()->getLTM()->pos; + light.Direction = *(D3DVECTOR*)&l->getFrame()->getLTM()->at; + light.Range = l->radius; + light.Falloff = 1.0f; + light.Attenuation0 = 1.0f; + light.Attenuation1 = 0.0f/l->radius; + light.Attenuation2 = 5.0f/(l->radius*l->radius); + light.Theta = 0.0f; + light.Phi = l->getAngle()*2.0f; + d3ddevice->SetLight(n, &light); + d3ddevice->LightEnable(n, TRUE); + n++; + break; + } + } + + for(; n < MAX_LIGHTS; n++) + d3ddevice->LightEnable(n, FALSE); +} + + +struct LightVS +{ + V3d color; float param0; + V3d position; float param1; + V3d direction; float param2; +}; + +void +setAmbient(const RGBAf &color) +{ + if(!equal(d3dShaderState.ambient, color)){ + d3dShaderState.ambient = color; + d3ddevice->SetVertexShaderConstantF(VSLOC_ambLight, (float*)&color, 1); + } +} + +void +setNumLights(int numDir, int numPoint, int numSpot) +{ + static int32 numLights[4*3]; + if(d3dShaderState.numDir != numDir || + d3dShaderState.numPoint != numPoint || + d3dShaderState.numSpot != numSpot){ + numLights[0] = d3dShaderState.numDir = numDir; + numLights[4] = d3dShaderState.numPoint = numPoint; + numLights[8] = d3dShaderState.numSpot = numSpot; + d3ddevice->SetVertexShaderConstantI(VSLOC_numLights, numLights, 3); + } +} + +int32 +uploadLights(WorldLights *lightData) +{ + int i; + int bits = 0; + float32 firstLight[4]; + firstLight[0] = 0; // directional + firstLight[1] = 0; // point + firstLight[2] = 0; // spot + firstLight[3] = 0; + + if(lightData->numAmbients) + bits |= VSLIGHT_AMBIENT; + + LightVS directionals[8]; + LightVS points[8]; + LightVS spots[8]; + for(i = 0; i < lightData->numDirectionals; i++){ + Light *l = lightData->directionals[i]; + directionals[i].color.x = l->color.red; + directionals[i].color.y = l->color.green; + directionals[i].color.z = l->color.blue; + directionals[i].direction = l->getFrame()->getLTM()->at; + bits |= VSLIGHT_DIRECT; + } + + int np = 0; + int ns = 0; + for(i = 0; i < lightData->numLocals; i++){ + Light *l = lightData->locals[i]; + + switch(l->getType()){ + case Light::POINT: + points[np].color.x = l->color.red; + points[np].color.y = l->color.green; + points[np].color.z = l->color.blue; + points[np].param0 = l->radius; + points[np].position = l->getFrame()->getLTM()->pos; + np++; + bits |= VSLIGHT_POINT; + break; + case Light::SPOT: + case Light::SOFTSPOT: + spots[ns].color.x = l->color.red; + spots[ns].color.y = l->color.green; + spots[ns].color.z = l->color.blue; + spots[ns].param0 = l->radius; + spots[ns].position = l->getFrame()->getLTM()->pos; + spots[ns].direction = l->getFrame()->getLTM()->at; + spots[ns].param1 = l->minusCosAngle; + // lower bound of falloff + if(l->getType() == Light::SOFTSPOT) + spots[ns].param2 = 0.0f; + else + spots[ns].param2 = 1.0f; + bits |= VSLIGHT_SPOT; + ns++; + break; + } + } + + firstLight[0] = 0; + int numDir = lightData->numDirectionals; + firstLight[1] = numDir + firstLight[0]; + int numPoint = np; + firstLight[2] = numPoint + firstLight[1]; + int numSpot = ns; + + setNumLights(numDir, numPoint, numSpot); + if(d3dShaderState.lightOffset[0] != firstLight[0] || + d3dShaderState.lightOffset[1] != firstLight[1] || + d3dShaderState.lightOffset[2] != firstLight[2]){ + d3dShaderState.lightOffset[0] = firstLight[0]; + d3dShaderState.lightOffset[1] = firstLight[1]; + d3dShaderState.lightOffset[2] = firstLight[2]; + d3ddevice->SetVertexShaderConstantF(VSLOC_lightOffset, firstLight, 1); + } + + int32 off = VSLOC_lights; + if(numDir) + d3ddevice->SetVertexShaderConstantF(off, (float*)&directionals, numDir*3); + off += numDir*3; + + if(numPoint) + d3ddevice->SetVertexShaderConstantF(off, (float*)&points, numPoint*3); + off += numPoint*3; + + if(numSpot) + d3ddevice->SetVertexShaderConstantF(off, (float*)&spots, numSpot*3); + + return bits; +} + +int32 +lightingCB_Shader(Atomic *atomic) +{ + WorldLights lightData; + Light *directionals[8]; + Light *locals[8]; + lightData.directionals = directionals; + lightData.numDirectionals = 8; + lightData.locals = locals; + lightData.numLocals = 8; + + if(atomic->geometry->flags & rw::Geometry::LIGHT){ + ((World*)engine->currentWorld)->enumerateLights(atomic, &lightData); + setAmbient(lightData.ambient); + if((atomic->geometry->flags & rw::Geometry::NORMALS) == 0){ + // Get rid of lights that need normals when we don't have any + lightData.numDirectionals = 0; + lightData.numLocals = 0; + } + return uploadLights(&lightData); + }else{ + static const RGBAf black = { 0.0f, 0.0f, 0.0f, 0.0f }; + setAmbient(black); + setNumLights(0, 0, 0); + return 0; + } +} + +static RawMatrix identityXform = { + { 1.0f, 0.0f, 0.0f }, 0.0f, + { 0.0f, 1.0f, 0.0f }, 0.0f, + { 0.0f, 0.0f, 1.0f }, 0.0f, + { 0.0f, 0.0f, 0.0f }, 1.0f +}; + +void +uploadMatrices(void) +{ + RawMatrix combined; + Camera *cam = engine->currentCamera; + d3ddevice->SetVertexShaderConstantF(VSLOC_world, (float*)&identityXform, 4); + d3ddevice->SetVertexShaderConstantF(VSLOC_normal, (float*)&identityXform, 4); + + RawMatrix::mult(&combined, &cam->devView, &cam->devProj); + d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&combined, 4); +} + +void +uploadMatrices(Matrix *worldMat) +{ + RawMatrix combined, world, worldview; + Camera *cam = engine->currentCamera; + convMatrix(&world, worldMat); + d3ddevice->SetVertexShaderConstantF(VSLOC_world, (float*)&world, 4); + // TODO: inverse transpose + d3ddevice->SetVertexShaderConstantF(VSLOC_normal, (float*)&world, 4); + + RawMatrix::mult(&worldview, &world, &cam->devView); + RawMatrix::mult(&combined, &worldview, &cam->devProj); + d3ddevice->SetVertexShaderConstantF(VSLOC_combined, (float*)&combined, 4); +} + + + +#endif + +} +} diff --git a/src/d3d/rwd3d.h b/src/d3d/rwd3d.h new file mode 100644 index 0000000..8a55467 --- /dev/null +++ b/src/d3d/rwd3d.h @@ -0,0 +1,291 @@ +#ifdef RW_D3D9 +#ifdef WITH_D3D +#include +#endif +#endif + +namespace rw { + +#ifdef RW_D3D9 + +#ifdef _WINDOWS_ +struct EngineOpenParams +{ + HWND window; +}; +#else +struct EngineOpenParams +{ + uint32 please_include_windows_h; +}; +#endif +#else +#ifdef _D3D9_H_ +#error "please don't include d3d9.h for non-d3d9 platforms" +#endif +#endif + +namespace d3d { + +extern bool32 isP8supported; + +extern Device renderdevice; + +#ifdef RW_D3D9 +#ifdef _D3D9_H_ +extern IDirect3DDevice9 *d3ddevice; +void setD3dMaterial(D3DMATERIAL9 *mat9); +#endif + +#define COLOR_ARGB(a, r, g, b) ((rw::uint32)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff))) + +struct Im3DVertex +{ + V3d position; + uint32 color; + float32 u, v; + + void setX(float32 x) { this->position.x = x; } + void setY(float32 y) { this->position.y = y; } + void setZ(float32 z) { this->position.z = z; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { this->color = COLOR_ARGB(a, r, g, b); } + void setU(float32 u) { this->u = u; } + void setV(float32 v) { this->v = v; } + + float getX(void) { return this->position.x; } + float getY(void) { return this->position.y; } + float getZ(void) { return this->position.z; } + RGBA getColor(void) { return makeRGBA(this->color>>16 & 0xFF, this->color>>8 & 0xFF, + this->color & 0xFF, this->color>>24 & 0xFF); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + +struct Im2DVertex +{ + float32 x, y, z; + float32 q; + uint32 color; + float32 u, v; + + void setScreenX(float32 x) { this->x = x; } + void setScreenY(float32 y) { this->y = y; } + void setScreenZ(float32 z) { this->z = z; } + void setCameraZ(float32 z) { } + void setRecipCameraZ(float32 recipz) { this->q = recipz; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { this->color = COLOR_ARGB(a, r, g, b); } + void setU(float32 u, float recipZ) { this->u = u; } + void setV(float32 v, float recipZ) { this->v = v; } + + float getScreenX(void) { return this->x; } + float getScreenY(void) { return this->y; } + float getScreenZ(void) { return this->z; } + float getCameraZ(void) { return 1.0f/this->q; } + float getRecipCameraZ(void) { return this->q; } + RGBA getColor(void) { return makeRGBA(this->color>>16 & 0xFF, this->color>>8 & 0xFF, + this->color & 0xFF, this->color>>24 & 0xFF); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + +#else +enum { + D3DLOCK_NOSYSLOCK = 0, // ignored + D3DPOOL_MANAGED = 0, // ignored + D3DPT_TRIANGLELIST = 4, + D3DPT_TRIANGLESTRIP = 5, + + + D3DDECLTYPE_FLOAT1 = 0, // 1D float expanded to (value, 0., 0., 1.) + D3DDECLTYPE_FLOAT2 = 1, // 2D float expanded to (value, value, 0., 1.) + D3DDECLTYPE_FLOAT3 = 2, // 3D float expanded to (value, value, value, 1.) + D3DDECLTYPE_FLOAT4 = 3, // 4D float + D3DDECLTYPE_D3DCOLOR = 4, // 4D packed unsigned bytes mapped to 0. to 1. range + // Input is in D3DCOLOR format (ARGB) expanded to (R, G, B, A) + D3DDECLTYPE_UBYTE4 = 5, // 4D unsigned byte + D3DDECLTYPE_SHORT2 = 6, // 2D signed short expanded to (value, value, 0., 1.) + D3DDECLTYPE_SHORT4 = 7, // 4D signed short + + D3DDECLTYPE_UBYTE4N = 8, // Each of 4 bytes is normalized by dividing to 255.0 + D3DDECLTYPE_SHORT2N = 9, // 2D signed short normalized (v[0]/32767.0,v[1]/32767.0,0,1) + D3DDECLTYPE_SHORT4N = 10, // 4D signed short normalized (v[0]/32767.0,v[1]/32767.0,v[2]/32767.0,v[3]/32767.0) + D3DDECLTYPE_USHORT2N = 11, // 2D unsigned short normalized (v[0]/65535.0,v[1]/65535.0,0,1) + D3DDECLTYPE_USHORT4N = 12, // 4D unsigned short normalized (v[0]/65535.0,v[1]/65535.0,v[2]/65535.0,v[3]/65535.0) + D3DDECLTYPE_UDEC3 = 13, // 3D unsigned 10 10 10 format expanded to (value, value, value, 1) + D3DDECLTYPE_DEC3N = 14, // 3D signed 10 10 10 format normalized and expanded to (v[0]/511.0, v[1]/511.0, v[2]/511.0, 1) + D3DDECLTYPE_FLOAT16_2 = 15, // Two 16-bit floating point values, expanded to (value, value, 0, 1) + D3DDECLTYPE_FLOAT16_4 = 16, // Four 16-bit floating point values + D3DDECLTYPE_UNUSED = 17, // When the type field in a decl is unused. + + + D3DDECLMETHOD_DEFAULT = 0, + + + D3DDECLUSAGE_POSITION = 0, + D3DDECLUSAGE_BLENDWEIGHT, // 1 + D3DDECLUSAGE_BLENDINDICES, // 2 + D3DDECLUSAGE_NORMAL, // 3 + D3DDECLUSAGE_PSIZE, // 4 + D3DDECLUSAGE_TEXCOORD, // 5 + D3DDECLUSAGE_TANGENT, // 6 + D3DDECLUSAGE_BINORMAL, // 7 + D3DDECLUSAGE_TESSFACTOR, // 8 + D3DDECLUSAGE_POSITIONT, // 9 + D3DDECLUSAGE_COLOR, // 10 + D3DDECLUSAGE_FOG, // 11 + D3DDECLUSAGE_DEPTH, // 12 + D3DDECLUSAGE_SAMPLE // 13 +}; +#endif + +extern int vertFormatMap[]; + +void *createIndexBuffer(uint32 length, bool dynamic); +void destroyIndexBuffer(void *indexBuffer); +uint16 *lockIndices(void *indexBuffer, uint32 offset, uint32 size, uint32 flags); +void unlockIndices(void *indexBuffer); + +void *createVertexBuffer(uint32 length, uint32 fvf, bool dynamic); +void destroyVertexBuffer(void *vertexBuffer); +uint8 *lockVertices(void *vertexBuffer, uint32 offset, uint32 size, uint32 flags); +void unlockVertices(void *vertexBuffer); + +void *createTexture(int32 width, int32 height, int32 levels, uint32 format); +void destroyTexture(void *texture); +uint8 *lockTexture(void *texture, int32 level); +void unlockTexture(void *texture, int32 level); + +// Native Texture and Raster + +struct D3dRaster +{ + void *texture; + void *palette; + void *lockedSurf; + uint32 format; + uint32 bpp; // bytes per pixel + bool32 hasAlpha; + bool32 customFormat; +}; + +int32 getLevelSize(Raster *raster, int32 level); +void allocateDXT(Raster *raster, int32 dxt, int32 numLevels, bool32 hasAlpha); +void setPalette(Raster *raster, void *palette, int32 size); +void setTexels(Raster *raster, void *texels, int32 level); + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); + +// Rendering + +void setRenderState(uint32 state, uint32 value); +void getRenderState(uint32 state, uint32 *value); +void setTextureStageState(uint32 stage, uint32 type, uint32 value); +void getTextureStageState(uint32 stage, uint32 type, uint32 *value); +void setSamplerState(uint32 stage, uint32 type, uint32 value); +void getSamplerState(uint32 stage, uint32 type, uint32 *value); +void flushCache(void); + +void setTexture(uint32 stage, Texture *tex); +void setMaterial(const RGBA &color, const SurfaceProperties &surfaceprops); + +void setVertexShader(void *vs); +void setPixelShader(void *ps); +void setIndices(void *indexBuffer); +void setStreamSource(int n, void *buffer, uint32 offset, uint32 stride); +void setVertexDeclaration(void *declaration); + +void *createVertexShader(void *csosrc); +void *createPixelShader(void *csosrc); +void destroyVertexShader(void *shader); +void destroyPixelShader(void *shader); + + +/* + * Vertex shaders and common pipeline stuff + */ + +// This data will be available in vertex stream 2 +struct VertexConstantData +{ + V3d normal; + RGBA color; + TexCoords texCoors[8]; +}; +extern void *constantVertexStream; + +// TODO: figure out why this even still exists... +struct D3dShaderState +{ + // for VS + struct { + float32 start; + float32 end; + float32 range; // 1/(start-end) + float32 disable; // lower clamp + } fogData, fogDisable; + RGBA matColor; + SurfaceProperties surfProps; + float lightOffset[3]; + int32 numDir, numPoint, numSpot; + RGBAf ambient; + // for PS + RGBAf fogColor; + + bool fogDirty; +}; +extern D3dShaderState d3dShaderState; + +// Standard Vertex shader locations +enum +{ + VSLOC_combined = 0, + VSLOC_world = 4, + VSLOC_normal = 8, + VSLOC_matColor = 12, + VSLOC_surfProps = 13, + VSLOC_fogData = 14, + VSLOC_ambLight = 15, + VSLOC_lightOffset = 16, + VSLOC_lights = 17, + VSLOC_afterLights = VSLOC_lights + 8*3, + + VSLOC_numLights = 0, + + PSLOC_fogColor = 0 +}; + +// Vertex shader bits +enum +{ + // These should be low so they could be used as indices + VSLIGHT_DIRECT = 1, + VSLIGHT_POINT = 2, + VSLIGHT_SPOT = 4, + VSLIGHT_MASK = 7, // all the above + // less critical + VSLIGHT_AMBIENT = 8, +}; + +void lightingCB_Fix(Atomic *atomic); +int32 lightingCB_Shader(Atomic *atomic); +// for VS +void uploadMatrices(void); // no world transform +void uploadMatrices(Matrix *worldMat); +int32 uploadLights(WorldLights *lightData); // called by lightingCB_Shader + +extern void *im2dOverridePS; + +extern void *default_amb_VS; +extern void *default_amb_dir_VS; +extern void *default_all_VS; +extern void *default_PS; +extern void *default_tex_PS; +extern void *im2d_PS; +extern void *im2d_tex_PS; +void createDefaultShaders(void); +void destroyDefaultShaders(void); + + +} +} diff --git a/src/d3d/rwd3d8.h b/src/d3d/rwd3d8.h new file mode 100644 index 0000000..422379d --- /dev/null +++ b/src/d3d/rwd3d8.h @@ -0,0 +1,74 @@ +namespace rw { +namespace d3d8 { + +void registerPlatformPlugins(void); + +struct InstanceData +{ + uint32 minVert; + int32 stride; + int32 numVertices; + int32 numIndices; + Material *material; + uint32 vertexShader; + uint32 primType; + void *indexBuffer; + void *vertexBuffer; + uint32 baseIndex; + uint8 vertexAlpha; + uint8 managed; + uint8 remapped; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + uint16 serialNumber; + uint16 numMeshes; + + InstanceData *inst; +}; + +uint32 makeFVFDeclaration(uint32 flags, int32 numTex); +int32 getStride(uint32 flags, int32 numTex); + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + void (*instanceCB)(Geometry *geo, InstanceData *header); + void (*uninstanceCB)(Geometry *geo, InstanceData *header); + void (*renderCB)(Atomic *atomic, InstanceDataHeader *header); +}; + +void defaultInstanceCB(Geometry *geo, InstanceData *header); +void defaultUninstanceCB(Geometry *geo, InstanceData *header); +void defaultRenderCB(Atomic *atomic, InstanceDataHeader *header); + +ObjPipeline *makeDefaultPipeline(void); + +// Skin plugin + +void initSkin(void); +ObjPipeline *makeSkinPipeline(void); + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Native Texture and Raster + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +} +} diff --git a/src/d3d/rwd3d9.h b/src/d3d/rwd3d9.h new file mode 100644 index 0000000..ea3c0e4 --- /dev/null +++ b/src/d3d/rwd3d9.h @@ -0,0 +1,111 @@ +namespace rw { +namespace d3d9 { + +void registerPlatformPlugins(void); + +struct VertexElement +{ + uint16 stream; + uint16 offset; + uint8 type; + uint8 method; + uint8 usage; + uint8 usageIndex; +}; + +struct VertexStream +{ + void *vertexBuffer; + uint32 offset; + uint32 stride; + uint16 geometryFlags; + uint8 managed; + uint8 dynamicLock; +}; + +struct InstanceData +{ + uint32 numIndex; + uint32 minVert; + Material *material; + bool32 vertexAlpha; + void *vertexShader; + uint32 baseIndex; + uint32 numVertices; + uint32 startIndex; + uint32 numPrimitives; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + uint32 serialNumber; + uint32 numMeshes; + void *indexBuffer; + uint32 primType; + VertexStream vertexStream[2]; + bool32 useOffsets; + void *vertexDeclaration; + uint32 totalNumIndex; + uint32 totalNumVertex; + + InstanceData *inst; +}; + +void *createVertexDeclaration(VertexElement *elements); +void destroyVertexDeclaration(void *delaration); +uint32 getDeclaration(void *declaration, VertexElement *elements); + +void drawInst_simple(d3d9::InstanceDataHeader *header, d3d9::InstanceData *inst); +// Emulate PS2 GS alpha test FB_ONLY case: failed alpha writes to frame- but not to depth buffer +void drawInst_GSemu(d3d9::InstanceDataHeader *header, InstanceData *inst); +// This one switches between the above two depending on render state; +void drawInst(d3d9::InstanceDataHeader *header, d3d9::InstanceData *inst); + + + + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + void (*instanceCB)(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); + void (*uninstanceCB)(Geometry *geo, InstanceDataHeader *header); + void (*renderCB)(Atomic *atomic, InstanceDataHeader *header); +}; + +void defaultInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); +void defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header); +void defaultRenderCB_Fix(Atomic *atomic, InstanceDataHeader *header); +void defaultRenderCB_Shader(Atomic *atomic, InstanceDataHeader *header); + +ObjPipeline *makeDefaultPipeline(void); + + +// Skin plugin + +void initSkin(void); +void uploadSkinMatrices(Atomic *atomic); +void skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); +ObjPipeline *makeSkinPipeline(void); + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Native Texture and Raster + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +} +} diff --git a/src/d3d/rwd3dimpl.h b/src/d3d/rwd3dimpl.h new file mode 100644 index 0000000..caa6fc1 --- /dev/null +++ b/src/d3d/rwd3dimpl.h @@ -0,0 +1,79 @@ +namespace rw { +namespace d3d { + +#ifdef RW_D3D9 +void openIm2D(void); +void closeIm2D(void); +void im2DRenderLine(void *vertices, int32 numVertices, int32 vert1, int32 vert2); +void im2DRenderTriangle(void *vertices, int32 numVertices, int32 vert1, int32 vert2, int32 vert3); +void im2DRenderPrimitive(PrimitiveType primType, void *vertices, int32 numVertices); +void im2DRenderIndexedPrimitive(PrimitiveType primType, void *vertices, int32 numVertices, void *indices, int32 numIndices); + +void openIm3D(void); +void closeIm3D(void); +void im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags); +void im3DRenderPrimitive(PrimitiveType primType); +void im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices); +void im3DEnd(void); + + +struct DisplayMode +{ + D3DDISPLAYMODE mode; + uint32 flags; +}; + +struct D3d9Globals +{ + HWND window; + + IDirect3D9 *d3d9; + int numAdapters; + int adapter; + D3DCAPS9 caps; + DisplayMode *modes; + int numModes; + int currentMode; + DisplayMode startMode; + + D3DPRESENT_PARAMETERS present; + + IDirect3DSurface9 *defaultRenderTarget; + IDirect3DSurface9 *defaultDepthSurf; + + int numTextures; + int numVertexShaders; + int numPixelShaders; + int numVertexBuffers; + int numIndexBuffers; + int numVertexDeclarations; +}; + +extern D3d9Globals d3d9Globals; + +void addVidmemRaster(Raster *raster); +void removeVidmemRaster(Raster *raster); + +void addDynamicVB(uint32 length, uint32 fvf, IDirect3DVertexBuffer9 **buf); // NB: don't share this pointer +void removeDynamicVB(IDirect3DVertexBuffer9 **buf); + +void addDynamicIB(uint32 length, IDirect3DIndexBuffer9 **buf); // NB: don't share this pointer +void removeDynamicIB(IDirect3DIndexBuffer9 **buf); + + +int findFormatDepth(uint32 format); +void evictD3D9Raster(Raster *raster); + +#endif + +Raster *rasterCreate(Raster *raster); +uint8 *rasterLock(Raster *raster, int32 level, int32 lockMode); +void rasterUnlock(Raster *raster, int32 level); +int32 rasterNumLevels(Raster *raster); +bool32 imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); +bool32 rasterFromImage(Raster *raster, Image *image); +Image *rasterToImage(Raster *raster); + +} +} diff --git a/src/d3d/rwxbox.h b/src/d3d/rwxbox.h new file mode 100644 index 0000000..fef8284 --- /dev/null +++ b/src/d3d/rwxbox.h @@ -0,0 +1,192 @@ +namespace rw { +namespace xbox { + +void registerPlatformPlugins(void); + +extern int v3dFormatMap[6]; +extern int v2dFormatMap[6]; + +struct InstanceData +{ + uint32 minVert; + int32 numVertices; + int32 numIndices; + void *indexBuffer; + Material *material; + uint32 vertexShader; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + int32 size; + uint16 serialNumber; + uint16 numMeshes; + uint32 primType; + int32 numVertices; + int32 stride; + void *vertexBuffer; + bool32 vertexAlpha; + InstanceData *begin; + InstanceData *end; + + uint8 *data; +}; + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + void (*instanceCB)(Geometry *geo, InstanceDataHeader *header); + void (*uninstanceCB)(Geometry *geo, InstanceDataHeader *header); +}; + +ObjPipeline *makeDefaultPipeline(void); + +void defaultInstanceCB(Geometry *geo, InstanceDataHeader *header); +void defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header); + +// Skin plugin + +Stream *readNativeSkin(Stream *stream, int32, void *object, int32 offset); +Stream *writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset); +int32 getSizeNativeSkin(void *object, int32 offset); + +void initSkin(void); +ObjPipeline *makeSkinPipeline(void); + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Vertex Format plugin + +extern uint32 vertexFormatSizes[6]; + +uint32 *getVertexFmt(Geometry *g); +uint32 makeVertexFmt(int32 flags, uint32 numTexSets); +uint32 getVertexFmtStride(uint32 fmt); + +void registerVertexFormatPlugin(void); + +// Native Texture and Raster + +struct XboxRaster +{ + void *texture; + void *palette; + uint32 format; + bool32 hasAlpha; + bool32 unknownFlag; +}; + +int32 getLevelSize(Raster *raster, int32 level); + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +enum { + D3DFMT_UNKNOWN = 0xFFFFFFFF, + + /* Swizzled formats */ + + D3DFMT_A8R8G8B8 = 0x00000006, + D3DFMT_X8R8G8B8 = 0x00000007, + D3DFMT_R5G6B5 = 0x00000005, + D3DFMT_R6G5B5 = 0x00000027, + D3DFMT_X1R5G5B5 = 0x00000003, + D3DFMT_A1R5G5B5 = 0x00000002, + D3DFMT_A4R4G4B4 = 0x00000004, + D3DFMT_A8 = 0x00000019, + D3DFMT_A8B8G8R8 = 0x0000003A, + D3DFMT_B8G8R8A8 = 0x0000003B, + D3DFMT_R4G4B4A4 = 0x00000039, + D3DFMT_R5G5B5A1 = 0x00000038, + D3DFMT_R8G8B8A8 = 0x0000003C, + D3DFMT_R8B8 = 0x00000029, + D3DFMT_G8B8 = 0x00000028, + + D3DFMT_P8 = 0x0000000B, + + D3DFMT_L8 = 0x00000000, + D3DFMT_A8L8 = 0x0000001A, + D3DFMT_AL8 = 0x00000001, + D3DFMT_L16 = 0x00000032, + + D3DFMT_V8U8 = 0x00000028, + D3DFMT_L6V5U5 = 0x00000027, + D3DFMT_X8L8V8U8 = 0x00000007, + D3DFMT_Q8W8V8U8 = 0x0000003A, + D3DFMT_V16U16 = 0x00000033, + + D3DFMT_D16_LOCKABLE = 0x0000002C, + D3DFMT_D16 = 0x0000002C, + D3DFMT_D24S8 = 0x0000002A, + D3DFMT_F16 = 0x0000002D, + D3DFMT_F24S8 = 0x0000002B, + + /* YUV formats */ + + D3DFMT_YUY2 = 0x00000024, + D3DFMT_UYVY = 0x00000025, + + /* Compressed formats */ + + D3DFMT_DXT1 = 0x0000000C, + D3DFMT_DXT2 = 0x0000000E, + D3DFMT_DXT3 = 0x0000000E, + D3DFMT_DXT4 = 0x0000000F, + D3DFMT_DXT5 = 0x0000000F, + + /* Linear formats */ + + D3DFMT_LIN_A1R5G5B5 = 0x00000010, + D3DFMT_LIN_A4R4G4B4 = 0x0000001D, + D3DFMT_LIN_A8 = 0x0000001F, + D3DFMT_LIN_A8B8G8R8 = 0x0000003F, + D3DFMT_LIN_A8R8G8B8 = 0x00000012, + D3DFMT_LIN_B8G8R8A8 = 0x00000040, + D3DFMT_LIN_G8B8 = 0x00000017, + D3DFMT_LIN_R4G4B4A4 = 0x0000003E, + D3DFMT_LIN_R5G5B5A1 = 0x0000003D, + D3DFMT_LIN_R5G6B5 = 0x00000011, + D3DFMT_LIN_R6G5B5 = 0x00000037, + D3DFMT_LIN_R8B8 = 0x00000016, + D3DFMT_LIN_R8G8B8A8 = 0x00000041, + D3DFMT_LIN_X1R5G5B5 = 0x0000001C, + D3DFMT_LIN_X8R8G8B8 = 0x0000001E, + + D3DFMT_LIN_A8L8 = 0x00000020, + D3DFMT_LIN_AL8 = 0x0000001B, + D3DFMT_LIN_L16 = 0x00000035, + D3DFMT_LIN_L8 = 0x00000013, + + D3DFMT_LIN_V16U16 = 0x00000036, + D3DFMT_LIN_V8U8 = 0x00000017, + D3DFMT_LIN_L6V5U5 = 0x00000037, + D3DFMT_LIN_X8L8V8U8 = 0x0000001E, + D3DFMT_LIN_Q8W8V8U8 = 0x00000012, + + D3DFMT_LIN_D24S8 = 0x0000002E, + D3DFMT_LIN_F24S8 = 0x0000002F, + D3DFMT_LIN_D16 = 0x00000030, + D3DFMT_LIN_F16 = 0x00000031, + + D3DFMT_VERTEXDATA = 100, + D3DFMT_INDEX16 = 101 +}; + +} +} diff --git a/src/d3d/rwxboximpl.h b/src/d3d/rwxboximpl.h new file mode 100644 index 0000000..524ec28 --- /dev/null +++ b/src/d3d/rwxboximpl.h @@ -0,0 +1,11 @@ +namespace rw { +namespace xbox { + +Raster *rasterCreate(Raster *raster); +uint8 *rasterLock(Raster *raster, int32 level, int32 lockMode); +void rasterUnlock(Raster*, int32); +int32 rasterNumLevels(Raster *raster); +Image *rasterToImage(Raster *raster); + +} +} diff --git a/src/d3d/shaders/default_PS.h b/src/d3d/shaders/default_PS.h new file mode 100644 index 0000000..9f2cb31 --- /dev/null +++ b/src/d3d/shaders/default_PS.h @@ -0,0 +1,72 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /Fh default_PS.h default_PS.hlsl +// +// +// Parameters: +// +// float4 fogColor; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// + + ps_2_0 + dcl t0.xyz + dcl v0 + add r0.xyz, v0, -c0 + mad r0.xyz, t0.z, r0, c0 + mov r0.w, v0.w + mov oC0, r0 + +// approximately 4 instruction slots used +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 34, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 83, 0, + 0, 0, 0, 2, 255, 255, + 1, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 76, 0, 0, 0, 48, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 2, 0, 60, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 67, 111, 108, + 111, 114, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 115, + 95, 50, 95, 48, 0, 77, + 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 57, 46, + 50, 57, 46, 57, 53, 50, + 46, 51, 49, 49, 49, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 7, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 2, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 144, + 0, 0, 228, 161, 4, 0, + 0, 4, 0, 0, 7, 128, + 0, 0, 170, 176, 0, 0, + 228, 128, 0, 0, 228, 160, + 1, 0, 0, 2, 0, 0, + 8, 128, 0, 0, 255, 144, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/default_PS.hlsl b/src/d3d/shaders/default_PS.hlsl new file mode 100644 index 0000000..eaf279d --- /dev/null +++ b/src/d3d/shaders/default_PS.hlsl @@ -0,0 +1,19 @@ +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float4 Color : COLOR0; +}; + +sampler2D tex0 : register(s0); + +float4 fogColor : register(c0); + +float4 main(VS_out input) : COLOR +{ + float4 color = input.Color; +#ifdef TEX + color *= tex2D(tex0, input.TexCoord0.xy); +#endif + color.rgb = lerp(fogColor.rgb, color.rgb, input.TexCoord0.z); + return color; +} diff --git a/src/d3d/shaders/default_VS.hlsl b/src/d3d/shaders/default_VS.hlsl new file mode 100644 index 0000000..d2f5452 --- /dev/null +++ b/src/d3d/shaders/default_VS.hlsl @@ -0,0 +1,51 @@ +#include "standardConstants.h" + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float4 Color : COLOR0; +}; + + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 Vertex = mul(worldMat, input.Position).xyz; + float3 Normal = mul(normalMat, input.Normal); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +#endif +#ifdef POINTLIGHTS + for(i = 0; i < numPointLights; i++) + output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +#endif +#ifdef SPOTLIGHTS + for(i = 0; i < numSpotLights; i++) + output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +#endif + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/d3d/shaders/default_all_VS.h b/src/d3d/shaders/default_all_VS.h new file mode 100644 index 0000000..e6afa5d --- /dev/null +++ b/src/d3d/shaders/default_all_VS.h @@ -0,0 +1,491 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh +// default_all_VS.h default_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// int numPointLights; +// int numSpotLights; +// float4 surfProps; +// float4x4 worldMat; +// +// +// Registers: +// +// Name Reg Size +// -------------- ----- ---- +// numDirLights i0 1 +// numPointLights i1 1 +// numSpotLights i2 1 +// combinedMat c0 4 +// worldMat c4 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// + + vs_2_0 + def c11, 0, 3, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + mov oPos, r0 + mul r0.xyz, v0.y, c5 + mad r0.xyz, c4, v0.x, r0 + mad r0.xyz, c6, v0.z, r0 + mad r0.xyz, c7, v0.w, r0 + mul r1.xyz, v1.y, c9 + mad r1.xyz, c8, v1.x, r1 + mad r1.xyz, c10, v1.z, r1 + mov r2.x, c13.x + mad r2.xyz, c15, r2.x, v3 + mov r3.xyz, r2 + mov r1.w, c11.x + rep i0 + add r2.w, r1.w, c16.x + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + dp3 r2.w, r1, -c19[a0.x] + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, c17[a0.x] + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c11.z + endrep + mov r2.xyz, r3 + mov r1.w, c11.x + rep i1 + add r2.w, r1.w, c16.y + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + add r4.xyz, r0, -c18[a0.x] + dp3 r2.w, r4, r4 + rsq r2.w, r2.w + mul r4.xyz, r2.w, r4 + dp3 r3.w, r1, -r4 + max r3.w, r3.w, c11.x + mul r4.xyz, r3.w, c17[a0.x] + rcp r2.w, r2.w + rcp r3.w, c17[a0.x].w + mad r2.w, r2.w, -r3.w, c11.z + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, r4 + mad r2.xyz, r4, c13.z, r2 + add r1.w, r1.w, c11.z + endrep + mov r3.xyz, r2 + mov r1.w, c11.x + rep i2 + add r2.w, r1.w, c16.z + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + add r4.xyz, r0, -c18[a0.x] + dp3 r2.w, r4, r4 + rsq r2.w, r2.w + mul r4.xyz, r2.w, r4 + dp3 r4.w, r1, -r4 + dp3 r4.x, r4, c19[a0.x] + max r4.y, r4.w, c11.x + mov r4.z, c11.z + add r4.xz, r4, c18[a0.x].w + rcp r4.z, r4.z + mul r4.x, r4.z, r4.x + slt r4.z, r4.x, c11.x + mad r4.y, r4.z, -r4.y, r4.y + max r4.x, r4.x, c19[a0.x].w + mul r4.x, r4.x, r4.y + mul r4.xyz, r4.x, c17[a0.x] + rcp r2.w, r2.w + rcp r4.w, c17[a0.x].w + mad r2.w, r2.w, -r4.w, c11.z + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, r4 + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c11.z + endrep + mov r3.w, v3.w + max r1, r3, c11.x + min r1, r1, c11.z + mul oD0, r1, c12 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c11.z + mov oT0.xy, v2 + +// approximately 95 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 158, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 67, 2, + 0, 0, 0, 2, 254, 255, + 12, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 60, 2, 0, 0, 12, 1, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 28, 1, + 0, 0, 0, 0, 0, 0, + 44, 1, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 56, 1, 0, 0, 0, 0, + 0, 0, 72, 1, 0, 0, + 2, 0, 16, 0, 1, 0, + 66, 0, 84, 1, 0, 0, + 0, 0, 0, 0, 100, 1, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 28, 1, + 0, 0, 0, 0, 0, 0, + 108, 1, 0, 0, 2, 0, + 17, 0, 24, 0, 70, 0, + 184, 1, 0, 0, 0, 0, + 0, 0, 200, 1, 0, 0, + 2, 0, 12, 0, 1, 0, + 50, 0, 28, 1, 0, 0, + 0, 0, 0, 0, 207, 1, + 0, 0, 2, 0, 8, 0, + 3, 0, 34, 0, 220, 1, + 0, 0, 0, 0, 0, 0, + 236, 1, 0, 0, 1, 0, + 0, 0, 1, 0, 2, 0, + 252, 1, 0, 0, 0, 0, + 0, 0, 12, 2, 0, 0, + 1, 0, 1, 0, 1, 0, + 6, 0, 252, 1, 0, 0, + 0, 0, 0, 0, 27, 2, + 0, 0, 1, 0, 2, 0, + 1, 0, 10, 0, 252, 1, + 0, 0, 0, 0, 0, 0, + 41, 2, 0, 0, 2, 0, + 13, 0, 1, 0, 54, 0, + 28, 1, 0, 0, 0, 0, + 0, 0, 51, 2, 0, 0, + 2, 0, 4, 0, 4, 0, + 18, 0, 56, 1, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 109, 98, 105, 110, + 101, 100, 77, 97, 116, 0, + 3, 0, 3, 0, 4, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 105, + 114, 115, 116, 76, 105, 103, + 104, 116, 0, 171, 1, 0, + 2, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 68, + 97, 116, 97, 0, 108, 105, + 103, 104, 116, 115, 0, 99, + 111, 108, 111, 114, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 111, 115, 105, 116, 105, + 111, 110, 0, 100, 105, 114, + 101, 99, 116, 105, 111, 110, + 0, 171, 115, 1, 0, 0, + 124, 1, 0, 0, 140, 1, + 0, 0, 124, 1, 0, 0, + 149, 1, 0, 0, 124, 1, + 0, 0, 5, 0, 0, 0, + 1, 0, 12, 0, 8, 0, + 3, 0, 160, 1, 0, 0, + 109, 97, 116, 67, 111, 108, + 0, 110, 111, 114, 109, 97, + 108, 77, 97, 116, 0, 171, + 171, 171, 3, 0, 3, 0, + 3, 0, 3, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 110, 117, 109, 68, 105, 114, + 76, 105, 103, 104, 116, 115, + 0, 171, 171, 171, 0, 0, + 2, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 110, 117, 109, 80, + 111, 105, 110, 116, 76, 105, + 103, 104, 116, 115, 0, 110, + 117, 109, 83, 112, 111, 116, + 76, 105, 103, 104, 116, 115, + 0, 115, 117, 114, 102, 80, + 114, 111, 112, 115, 0, 119, + 111, 114, 108, 100, 77, 97, + 116, 0, 118, 115, 95, 50, + 95, 48, 0, 77, 105, 99, + 114, 111, 115, 111, 102, 116, + 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, + 114, 32, 57, 46, 50, 57, + 46, 57, 53, 50, 46, 51, + 49, 49, 49, 0, 81, 0, + 0, 5, 11, 0, 15, 160, + 0, 0, 0, 0, 0, 0, + 64, 64, 0, 0, 128, 63, + 0, 0, 0, 0, 31, 0, + 0, 2, 0, 0, 0, 128, + 0, 0, 15, 144, 31, 0, + 0, 2, 3, 0, 0, 128, + 1, 0, 15, 144, 31, 0, + 0, 2, 5, 0, 0, 128, + 2, 0, 15, 144, 31, 0, + 0, 2, 10, 0, 0, 128, + 3, 0, 15, 144, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 85, 144, 1, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 3, 0, 228, 160, + 0, 0, 255, 144, 0, 0, + 228, 128, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 7, 128, 0, 0, + 85, 144, 5, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 4, 0, 228, 160, + 0, 0, 0, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 7, 128, 6, 0, + 228, 160, 0, 0, 170, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 7, 128, + 7, 0, 228, 160, 0, 0, + 255, 144, 0, 0, 228, 128, + 5, 0, 0, 3, 1, 0, + 7, 128, 1, 0, 85, 144, + 9, 0, 228, 160, 4, 0, + 0, 4, 1, 0, 7, 128, + 8, 0, 228, 160, 1, 0, + 0, 144, 1, 0, 228, 128, + 4, 0, 0, 4, 1, 0, + 7, 128, 10, 0, 228, 160, + 1, 0, 170, 144, 1, 0, + 228, 128, 1, 0, 0, 2, + 2, 0, 1, 128, 13, 0, + 0, 160, 4, 0, 0, 4, + 2, 0, 7, 128, 15, 0, + 228, 160, 2, 0, 0, 128, + 3, 0, 228, 144, 1, 0, + 0, 2, 3, 0, 7, 128, + 2, 0, 228, 128, 1, 0, + 0, 2, 1, 0, 8, 128, + 11, 0, 0, 160, 38, 0, + 0, 1, 0, 0, 228, 240, + 2, 0, 0, 3, 2, 0, + 8, 128, 1, 0, 255, 128, + 16, 0, 0, 160, 5, 0, + 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 11, 0, + 85, 160, 46, 0, 0, 2, + 0, 0, 1, 176, 2, 0, + 255, 128, 8, 0, 0, 4, + 2, 0, 8, 128, 1, 0, + 228, 128, 19, 32, 228, 161, + 0, 0, 0, 176, 11, 0, + 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 11, 0, + 0, 160, 5, 0, 0, 4, + 4, 0, 7, 128, 2, 0, + 255, 128, 17, 32, 228, 160, + 0, 0, 0, 176, 4, 0, + 0, 4, 3, 0, 7, 128, + 4, 0, 228, 128, 13, 0, + 170, 160, 3, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 2, 0, 7, 128, 3, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 8, 128, 11, 0, + 0, 160, 38, 0, 0, 1, + 1, 0, 228, 240, 2, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 16, 0, + 85, 160, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 85, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 2, 0, 255, 128, + 2, 0, 0, 4, 4, 0, + 7, 128, 0, 0, 228, 128, + 18, 32, 228, 161, 0, 0, + 0, 176, 8, 0, 0, 3, + 2, 0, 8, 128, 4, 0, + 228, 128, 4, 0, 228, 128, + 7, 0, 0, 2, 2, 0, + 8, 128, 2, 0, 255, 128, + 5, 0, 0, 3, 4, 0, + 7, 128, 2, 0, 255, 128, + 4, 0, 228, 128, 8, 0, + 0, 3, 3, 0, 8, 128, + 1, 0, 228, 128, 4, 0, + 228, 129, 11, 0, 0, 3, + 3, 0, 8, 128, 3, 0, + 255, 128, 11, 0, 0, 160, + 5, 0, 0, 4, 4, 0, + 7, 128, 3, 0, 255, 128, + 17, 32, 228, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 2, 0, 8, 128, 2, 0, + 255, 128, 6, 0, 0, 3, + 3, 0, 8, 128, 17, 32, + 255, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 2, 0, + 8, 128, 2, 0, 255, 128, + 3, 0, 255, 129, 11, 0, + 170, 160, 11, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 0, 160, + 5, 0, 0, 3, 4, 0, + 7, 128, 2, 0, 255, 128, + 4, 0, 228, 128, 4, 0, + 0, 4, 2, 0, 7, 128, + 4, 0, 228, 128, 13, 0, + 170, 160, 2, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 3, 0, 7, 128, 2, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 8, 128, 11, 0, + 0, 160, 38, 0, 0, 1, + 2, 0, 228, 240, 2, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 16, 0, + 170, 160, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 85, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 2, 0, 255, 128, + 2, 0, 0, 4, 4, 0, + 7, 128, 0, 0, 228, 128, + 18, 32, 228, 161, 0, 0, + 0, 176, 8, 0, 0, 3, + 2, 0, 8, 128, 4, 0, + 228, 128, 4, 0, 228, 128, + 7, 0, 0, 2, 2, 0, + 8, 128, 2, 0, 255, 128, + 5, 0, 0, 3, 4, 0, + 7, 128, 2, 0, 255, 128, + 4, 0, 228, 128, 8, 0, + 0, 3, 4, 0, 8, 128, + 1, 0, 228, 128, 4, 0, + 228, 129, 8, 0, 0, 4, + 4, 0, 1, 128, 4, 0, + 228, 128, 19, 32, 228, 160, + 0, 0, 0, 176, 11, 0, + 0, 3, 4, 0, 2, 128, + 4, 0, 255, 128, 11, 0, + 0, 160, 1, 0, 0, 2, + 4, 0, 4, 128, 11, 0, + 170, 160, 2, 0, 0, 4, + 4, 0, 5, 128, 4, 0, + 228, 128, 18, 32, 255, 160, + 0, 0, 0, 176, 6, 0, + 0, 2, 4, 0, 4, 128, + 4, 0, 170, 128, 5, 0, + 0, 3, 4, 0, 1, 128, + 4, 0, 170, 128, 4, 0, + 0, 128, 12, 0, 0, 3, + 4, 0, 4, 128, 4, 0, + 0, 128, 11, 0, 0, 160, + 4, 0, 0, 4, 4, 0, + 2, 128, 4, 0, 170, 128, + 4, 0, 85, 129, 4, 0, + 85, 128, 11, 0, 0, 4, + 4, 0, 1, 128, 4, 0, + 0, 128, 19, 32, 255, 160, + 0, 0, 0, 176, 5, 0, + 0, 3, 4, 0, 1, 128, + 4, 0, 0, 128, 4, 0, + 85, 128, 5, 0, 0, 4, + 4, 0, 7, 128, 4, 0, + 0, 128, 17, 32, 228, 160, + 0, 0, 0, 176, 6, 0, + 0, 2, 2, 0, 8, 128, + 2, 0, 255, 128, 6, 0, + 0, 3, 4, 0, 8, 128, + 17, 32, 255, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 2, 0, 8, 128, 2, 0, + 255, 128, 4, 0, 255, 129, + 11, 0, 170, 160, 11, 0, + 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 11, 0, + 0, 160, 5, 0, 0, 3, + 4, 0, 7, 128, 2, 0, + 255, 128, 4, 0, 228, 128, + 4, 0, 0, 4, 3, 0, + 7, 128, 4, 0, 228, 128, + 13, 0, 170, 160, 3, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 8, 128, 1, 0, + 255, 128, 11, 0, 170, 160, + 39, 0, 0, 0, 1, 0, + 0, 2, 3, 0, 8, 128, + 3, 0, 255, 144, 11, 0, + 0, 3, 1, 0, 15, 128, + 3, 0, 228, 128, 11, 0, + 0, 160, 10, 0, 0, 3, + 1, 0, 15, 128, 1, 0, + 228, 128, 11, 0, 170, 160, + 5, 0, 0, 3, 0, 0, + 15, 208, 1, 0, 228, 128, + 12, 0, 228, 160, 2, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 11, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/default_amb_VS.h b/src/d3d/shaders/default_amb_VS.h new file mode 100644 index 0000000..6c22e23 --- /dev/null +++ b/src/d3d/shaders/default_amb_VS.h @@ -0,0 +1,156 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /Fh default_amb_VS.h default_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// float4 fogData; +// float4 matCol; +// float4 surfProps; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// combinedMat c0 4 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// + + vs_2_0 + def c4, 0, 1, 0, 0 + dcl_position v0 + dcl_texcoord v1 + dcl_color v2 + mov r0.xyz, c15 + mad r0.xyz, r0, c13.x, v2 + mov r0.w, v2.w + max r0, r0, c4.x + min r0, r0, c4.y + mul oD0, r0, c12 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + add r1.x, r0.w, -c14.y + mov oPos, r0 + mul r0.x, r1.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.y + mov oT0.xy, v1 + +// approximately 16 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 69, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 220, 0, + 0, 0, 0, 2, 254, 255, + 5, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 213, 0, 0, 0, 128, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 144, 0, + 0, 0, 0, 0, 0, 0, + 160, 0, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 172, 0, 0, 0, 0, 0, + 0, 0, 188, 0, 0, 0, + 2, 0, 14, 0, 1, 0, + 58, 0, 144, 0, 0, 0, + 0, 0, 0, 0, 196, 0, + 0, 0, 2, 0, 12, 0, + 1, 0, 50, 0, 144, 0, + 0, 0, 0, 0, 0, 0, + 203, 0, 0, 0, 2, 0, + 13, 0, 1, 0, 54, 0, + 144, 0, 0, 0, 0, 0, + 0, 0, 97, 109, 98, 105, + 101, 110, 116, 76, 105, 103, + 104, 116, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 99, 111, + 109, 98, 105, 110, 101, 100, + 77, 97, 116, 0, 3, 0, + 3, 0, 4, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 68, + 97, 116, 97, 0, 109, 97, + 116, 67, 111, 108, 0, 115, + 117, 114, 102, 80, 114, 111, + 112, 115, 0, 118, 115, 95, + 50, 95, 48, 0, 77, 105, + 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, + 101, 114, 32, 57, 46, 50, + 57, 46, 57, 53, 50, 46, + 51, 49, 49, 49, 0, 171, + 171, 171, 81, 0, 0, 5, + 4, 0, 15, 160, 0, 0, + 0, 0, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 2, 0, + 15, 144, 1, 0, 0, 2, + 0, 0, 7, 128, 15, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 7, 128, 0, 0, + 228, 128, 13, 0, 0, 160, + 2, 0, 228, 144, 1, 0, + 0, 2, 0, 0, 8, 128, + 2, 0, 255, 144, 11, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 4, 0, + 0, 160, 10, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 4, 0, 85, 160, + 5, 0, 0, 3, 0, 0, + 15, 208, 0, 0, 228, 128, + 12, 0, 228, 160, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 85, 144, 1, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 3, 0, 228, 160, + 0, 0, 255, 144, 0, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 1, 128, 0, 0, + 255, 128, 14, 0, 85, 161, + 1, 0, 0, 2, 0, 0, + 15, 192, 0, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 1, 128, 1, 0, 0, 128, + 14, 0, 170, 160, 11, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 0, 128, 14, 0, + 255, 160, 10, 0, 0, 3, + 0, 0, 4, 224, 0, 0, + 0, 128, 4, 0, 85, 160, + 1, 0, 0, 2, 0, 0, + 3, 224, 1, 0, 228, 144, + 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/default_amb_dir_VS.h b/src/d3d/shaders/default_amb_dir_VS.h new file mode 100644 index 0000000..768042f --- /dev/null +++ b/src/d3d/shaders/default_amb_dir_VS.h @@ -0,0 +1,272 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /Fh default_amb_dir_VS.h +// default_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// float4 surfProps; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// numDirLights i0 1 +// combinedMat c0 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// + + vs_2_0 + def c4, 0, 3, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + mov oPos, r0 + mul r0.xyz, v1.y, c9 + mad r0.xyz, c8, v1.x, r0 + mad r0.xyz, c10, v1.z, r0 + mov r1.x, c13.x + mad r1.xyz, c15, r1.x, v3 + mov r2.xyz, r1 + mov r1.w, c4.x + rep i0 + add r3.x, r1.w, c16.x + mul r3.x, r3.x, c4.y + mova a0.x, r3.x + dp3 r3.x, r0, -c19[a0.x] + max r3.x, r3.x, c4.x + mul r3.xyz, r3.x, c17[a0.x] + mad r2.xyz, r3, c13.z, r2 + add r1.w, r1.w, c4.z + endrep + mov r2.w, v3.w + max r1, r2, c4.x + min r1, r1, c4.z + mul oD0, r1, c12 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.z + mov oT0.xy, v2 + +// approximately 34 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 134, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 225, 1, + 0, 0, 0, 2, 254, 255, + 9, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 218, 1, 0, 0, 208, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 224, 0, + 0, 0, 0, 0, 0, 0, + 240, 0, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 252, 0, 0, 0, 0, 0, + 0, 0, 12, 1, 0, 0, + 2, 0, 16, 0, 1, 0, + 66, 0, 24, 1, 0, 0, + 0, 0, 0, 0, 40, 1, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 224, 0, + 0, 0, 0, 0, 0, 0, + 48, 1, 0, 0, 2, 0, + 17, 0, 24, 0, 70, 0, + 124, 1, 0, 0, 0, 0, + 0, 0, 140, 1, 0, 0, + 2, 0, 12, 0, 1, 0, + 50, 0, 224, 0, 0, 0, + 0, 0, 0, 0, 147, 1, + 0, 0, 2, 0, 8, 0, + 3, 0, 34, 0, 160, 1, + 0, 0, 0, 0, 0, 0, + 176, 1, 0, 0, 1, 0, + 0, 0, 1, 0, 2, 0, + 192, 1, 0, 0, 0, 0, + 0, 0, 208, 1, 0, 0, + 2, 0, 13, 0, 1, 0, + 54, 0, 224, 0, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 109, 98, 105, 110, + 101, 100, 77, 97, 116, 0, + 3, 0, 3, 0, 4, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 105, + 114, 115, 116, 76, 105, 103, + 104, 116, 0, 171, 1, 0, + 2, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 68, + 97, 116, 97, 0, 108, 105, + 103, 104, 116, 115, 0, 99, + 111, 108, 111, 114, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 111, 115, 105, 116, 105, + 111, 110, 0, 100, 105, 114, + 101, 99, 116, 105, 111, 110, + 0, 171, 55, 1, 0, 0, + 64, 1, 0, 0, 80, 1, + 0, 0, 64, 1, 0, 0, + 89, 1, 0, 0, 64, 1, + 0, 0, 5, 0, 0, 0, + 1, 0, 12, 0, 8, 0, + 3, 0, 100, 1, 0, 0, + 109, 97, 116, 67, 111, 108, + 0, 110, 111, 114, 109, 97, + 108, 77, 97, 116, 0, 171, + 171, 171, 3, 0, 3, 0, + 3, 0, 3, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 110, 117, 109, 68, 105, 114, + 76, 105, 103, 104, 116, 115, + 0, 171, 171, 171, 0, 0, + 2, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 115, 117, 114, 102, + 80, 114, 111, 112, 115, 0, + 118, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 171, 171, 81, 0, + 0, 5, 4, 0, 15, 160, + 0, 0, 0, 0, 0, 0, + 64, 64, 0, 0, 128, 63, + 0, 0, 0, 0, 31, 0, + 0, 2, 0, 0, 0, 128, + 0, 0, 15, 144, 31, 0, + 0, 2, 3, 0, 0, 128, + 1, 0, 15, 144, 31, 0, + 0, 2, 5, 0, 0, 128, + 2, 0, 15, 144, 31, 0, + 0, 2, 10, 0, 0, 128, + 3, 0, 15, 144, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 85, 144, 1, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 3, 0, 228, 160, + 0, 0, 255, 144, 0, 0, + 228, 128, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 7, 128, 1, 0, + 85, 144, 9, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 8, 0, 228, 160, + 1, 0, 0, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 7, 128, 10, 0, + 228, 160, 1, 0, 170, 144, + 0, 0, 228, 128, 1, 0, + 0, 2, 1, 0, 1, 128, + 13, 0, 0, 160, 4, 0, + 0, 4, 1, 0, 7, 128, + 15, 0, 228, 160, 1, 0, + 0, 128, 3, 0, 228, 144, + 1, 0, 0, 2, 2, 0, + 7, 128, 1, 0, 228, 128, + 1, 0, 0, 2, 1, 0, + 8, 128, 4, 0, 0, 160, + 38, 0, 0, 1, 0, 0, + 228, 240, 2, 0, 0, 3, + 3, 0, 1, 128, 1, 0, + 255, 128, 16, 0, 0, 160, + 5, 0, 0, 3, 3, 0, + 1, 128, 3, 0, 0, 128, + 4, 0, 85, 160, 46, 0, + 0, 2, 0, 0, 1, 176, + 3, 0, 0, 128, 8, 0, + 0, 4, 3, 0, 1, 128, + 0, 0, 228, 128, 19, 32, + 228, 161, 0, 0, 0, 176, + 11, 0, 0, 3, 3, 0, + 1, 128, 3, 0, 0, 128, + 4, 0, 0, 160, 5, 0, + 0, 4, 3, 0, 7, 128, + 3, 0, 0, 128, 17, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 2, 0, + 7, 128, 3, 0, 228, 128, + 13, 0, 170, 160, 2, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 8, 128, 1, 0, + 255, 128, 4, 0, 170, 160, + 39, 0, 0, 0, 1, 0, + 0, 2, 2, 0, 8, 128, + 3, 0, 255, 144, 11, 0, + 0, 3, 1, 0, 15, 128, + 2, 0, 228, 128, 4, 0, + 0, 160, 10, 0, 0, 3, + 1, 0, 15, 128, 1, 0, + 228, 128, 4, 0, 170, 160, + 5, 0, 0, 3, 0, 0, + 15, 208, 1, 0, 228, 128, + 12, 0, 228, 160, 2, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 4, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/default_tex_PS.h b/src/d3d/shaders/default_tex_PS.h new file mode 100644 index 0000000..a25f918 --- /dev/null +++ b/src/d3d/shaders/default_tex_PS.h @@ -0,0 +1,89 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /DTEX /Fh default_tex_PS.h default_PS.hlsl +// +// +// Parameters: +// +// float4 fogColor; +// sampler2D tex0; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// tex0 s0 1 +// + + ps_2_0 + dcl t0.xyz + dcl v0 + dcl_2d s0 + texld r0, t0, s0 + mad r0.xyz, v0, r0, -c0 + mul r1.w, r0.w, v0.w + mad r1.xyz, t0.z, r0, c0 + mov oC0, r1 + +// approximately 5 instruction slots used (1 texture, 4 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 45, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 127, 0, + 0, 0, 0, 2, 255, 255, + 2, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 120, 0, 0, 0, 68, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 2, 0, 80, 0, + 0, 0, 0, 0, 0, 0, + 96, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 2, 0, + 104, 0, 0, 0, 0, 0, + 0, 0, 102, 111, 103, 67, + 111, 108, 111, 114, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 48, 0, 171, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 7, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 176, 0, 8, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 0, 0, 228, 144, + 0, 0, 228, 128, 0, 0, + 228, 161, 5, 0, 0, 3, + 1, 0, 8, 128, 0, 0, + 255, 128, 0, 0, 255, 144, + 4, 0, 0, 4, 1, 0, + 7, 128, 0, 0, 170, 176, + 0, 0, 228, 128, 0, 0, + 228, 160, 1, 0, 0, 2, + 0, 8, 15, 128, 1, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/im2d_PS.h b/src/d3d/shaders/im2d_PS.h new file mode 100644 index 0000000..0a5c28d --- /dev/null +++ b/src/d3d/shaders/im2d_PS.h @@ -0,0 +1,37 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /Fh im2d_PS.h im2d_PS.hlsl +// + ps_2_0 + dcl v0 + mov oC0, v0 + +// approximately 1 instruction slot used +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 22, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 35, 0, + 0, 0, 0, 2, 255, 255, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, + 28, 0, 0, 0, 112, 115, + 95, 50, 95, 48, 0, 77, + 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 57, 46, + 50, 57, 46, 57, 53, 50, + 46, 51, 49, 49, 49, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 1, 0, 0, 2, 0, 8, + 15, 128, 0, 0, 228, 144, + 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/im2d_PS.hlsl b/src/d3d/shaders/im2d_PS.hlsl new file mode 100644 index 0000000..a076531 --- /dev/null +++ b/src/d3d/shaders/im2d_PS.hlsl @@ -0,0 +1,17 @@ +struct VS_out { + float4 Position : POSITION; + float2 TexCoord0 : TEXCOORD0; + float4 Color : COLOR0; +}; + +sampler2D tex0 : register(s0); + + +float4 main(VS_out input) : COLOR +{ + float4 color = input.Color; +#ifdef TEX + color *= tex2D(tex0, input.TexCoord0.xy); +#endif + return color; +} diff --git a/src/d3d/shaders/im2d_tex_PS.h b/src/d3d/shaders/im2d_tex_PS.h new file mode 100644 index 0000000..194db9e --- /dev/null +++ b/src/d3d/shaders/im2d_tex_PS.h @@ -0,0 +1,71 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /DTEX /Fh im2d_tex_PS.h im2d_PS.hlsl +// +// +// Parameters: +// +// sampler2D tex0; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// tex0 s0 1 +// + + ps_2_0 + dcl t0.xy + dcl v0 + dcl_2d s0 + texld r0, t0, s0 + mul r0, r0, v0 + mov oC0, r0 + +// approximately 3 instruction slots used (1 texture, 2 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 33, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 79, 0, + 0, 0, 0, 2, 255, 255, + 1, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 72, 0, 0, 0, 48, 0, + 0, 0, 3, 0, 0, 0, + 1, 0, 2, 0, 56, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 48, 0, 171, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 176, 0, 8, 228, 160, + 5, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 0, 0, 228, 144, 1, 0, + 0, 2, 0, 8, 15, 128, + 0, 0, 228, 128, 255, 255, + 0, 0 +}; diff --git a/src/d3d/shaders/im3d_VS.hlsl b/src/d3d/shaders/im3d_VS.hlsl new file mode 100644 index 0000000..00d11f2 --- /dev/null +++ b/src/d3d/shaders/im3d_VS.hlsl @@ -0,0 +1,25 @@ +float4x4 combinedMat : register(c0); + +struct VS_in +{ + float4 Position : POSITION; + float2 TexCoord : TEXCOORD0; + float4 Color : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float2 TexCoord0 : TEXCOORD0; + float4 Color : COLOR0; +}; + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + output.TexCoord0 = input.TexCoord; + output.Color = input.Color; + + return output; +} diff --git a/src/d3d/shaders/lighting.h b/src/d3d/shaders/lighting.h new file mode 100644 index 0000000..2591c81 --- /dev/null +++ b/src/d3d/shaders/lighting.h @@ -0,0 +1,44 @@ +struct Light +{ + float4 color; // and radius + float4 position; // and -cos(angle) + float4 direction; // and falloff clamp +}; + +float3 DoDirLight(Light L, float3 N) +{ + float l = max(0.0, dot(N, -L.direction.xyz)); + return l*L.color.xyz; +} + +float3 DoDirLightSpec(Light L, float3 N, float3 V, float power)) +{ + return pow(saturate(dot(N, normalize(V + -L.direction.xyz))), power)*L.color.xyz; +} + +float3 DoPointLight(Light L, float3 V, float3 N) +{ + // As on PS2 + float3 dir = V - L.position.xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/L.color.w)); + float l = max(0.0, dot(N, -normalize(dir))); + return l*L.color.xyz*atten; +} + +float3 DoSpotLight(Light L, float3 V, float3 N) +{ + // As on PS2 + float3 dir = V - L.position.xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/L.color.w)); + dir /= dist; + float l = max(0.0, dot(N, -dir)); + float pcos = dot(dir, L.direction.xyz); // cos to point + float ccos = -L.position.w; // cos of cone + float falloff = (pcos-ccos)/(1.0-ccos); + if(falloff < 0) // outside of cone + l = 0; + l *= max(falloff, L.direction.w); // falloff clamp + return l*L.color.xyz*atten; +} diff --git a/src/d3d/shaders/make_default.cmd b/src/d3d/shaders/make_default.cmd new file mode 100644 index 0000000..6a533fe --- /dev/null +++ b/src/d3d/shaders/make_default.cmd @@ -0,0 +1,10 @@ +@echo off +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /Fh default_amb_VS.h default_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /Fh default_amb_dir_VS.h default_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh default_all_VS.h default_VS.hlsl + +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /Fh default_PS.h default_PS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /DTEX /Fh default_tex_PS.h default_PS.hlsl + +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /Fh im2d_PS.h im2d_PS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /DTEX /Fh im2d_tex_PS.h im2d_PS.hlsl diff --git a/src/d3d/shaders/make_matfx.cmd b/src/d3d/shaders/make_matfx.cmd new file mode 100644 index 0000000..d0bc4fa --- /dev/null +++ b/src/d3d/shaders/make_matfx.cmd @@ -0,0 +1,7 @@ +@echo off +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /Fh matfx_env_amb_VS.h matfx_env_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /Fh matfx_env_amb_dir_VS.h matfx_env_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh matfx_env_all_VS.h matfx_env_VS.hlsl + +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /Fh matfx_env_PS.h matfx_env_PS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T ps_2_0 /DTEX /Fh matfx_env_tex_PS.h matfx_env_PS.hlsl diff --git a/src/d3d/shaders/make_skin.cmd b/src/d3d/shaders/make_skin.cmd new file mode 100644 index 0000000..b7822ca --- /dev/null +++ b/src/d3d/shaders/make_skin.cmd @@ -0,0 +1,4 @@ +@echo off +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /Fh skin_amb_VS.h skin_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /Fh skin_amb_dir_VS.h skin_VS.hlsl +"%DXSDK_DIR%\utilities\bin\x86\fxc.exe" /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh skin_all_VS.h skin_VS.hlsl diff --git a/src/d3d/shaders/matfx_env_PS.h b/src/d3d/shaders/matfx_env_PS.h new file mode 100644 index 0000000..558ccdc --- /dev/null +++ b/src/d3d/shaders/matfx_env_PS.h @@ -0,0 +1,131 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /Fh matfx_env_PS.h matfx_env_PS.hlsl +// +// +// Parameters: +// +// float4 colorClamp; +// sampler2D envTex; +// float4 fogColor; +// float4 fxparams; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// fxparams c1 1 +// colorClamp c2 1 +// envTex s1 1 +// + + ps_2_0 + dcl t0.xyz + dcl t1.xy + dcl v0 + dcl_2d s1 + texld r0, t1, s1 + max r1.xyz, v0, c2 + mul r1.xyz, r1, c1.x + mul r0.xyz, r0, r1 + mul r0.xyz, r0, t0.z + max r0.w, v0.w, c1.y + mul r0.xyz, r0.w, r0 + add r1.xyz, v0, -c0 + mad r1.xyz, t0.z, r1, c0 + mad r0.xyz, r1, v0.w, r0 + mov r0.w, v0.w + mov oC0, r0 + +// approximately 12 instruction slots used (1 texture, 11 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 60, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 185, 0, + 0, 0, 0, 2, 255, 255, + 4, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 178, 0, 0, 0, 108, 0, + 0, 0, 2, 0, 2, 0, + 1, 0, 10, 0, 120, 0, + 0, 0, 0, 0, 0, 0, + 136, 0, 0, 0, 3, 0, + 1, 0, 1, 0, 6, 0, + 144, 0, 0, 0, 0, 0, + 0, 0, 160, 0, 0, 0, + 2, 0, 0, 0, 1, 0, + 2, 0, 120, 0, 0, 0, + 0, 0, 0, 0, 169, 0, + 0, 0, 2, 0, 1, 0, + 1, 0, 6, 0, 120, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 108, 111, 114, 67, + 108, 97, 109, 112, 0, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 101, 110, + 118, 84, 101, 120, 0, 171, + 4, 0, 12, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 111, + 103, 67, 111, 108, 111, 114, + 0, 102, 120, 112, 97, 114, + 97, 109, 115, 0, 112, 115, + 95, 50, 95, 48, 0, 77, + 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 57, 46, + 50, 57, 46, 57, 53, 50, + 46, 51, 49, 49, 49, 0, + 171, 171, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 7, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 1, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 0, 0, 0, 144, 1, 8, + 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 1, 0, + 228, 176, 1, 8, 228, 160, + 11, 0, 0, 3, 1, 0, + 7, 128, 0, 0, 228, 144, + 2, 0, 228, 160, 5, 0, + 0, 3, 1, 0, 7, 128, + 1, 0, 228, 128, 1, 0, + 0, 160, 5, 0, 0, 3, + 0, 0, 7, 128, 0, 0, + 228, 128, 1, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 128, + 0, 0, 170, 176, 11, 0, + 0, 3, 0, 0, 8, 128, + 0, 0, 255, 144, 1, 0, + 85, 160, 5, 0, 0, 3, + 0, 0, 7, 128, 0, 0, + 255, 128, 0, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 7, 128, 0, 0, 228, 144, + 0, 0, 228, 161, 4, 0, + 0, 4, 1, 0, 7, 128, + 0, 0, 170, 176, 1, 0, + 228, 128, 0, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 1, 0, 228, 128, + 0, 0, 255, 144, 0, 0, + 228, 128, 1, 0, 0, 2, + 0, 0, 8, 128, 0, 0, + 255, 144, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, + 228, 128, 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/matfx_env_PS.hlsl b/src/d3d/shaders/matfx_env_PS.hlsl new file mode 100644 index 0000000..ccdd5cd --- /dev/null +++ b/src/d3d/shaders/matfx_env_PS.hlsl @@ -0,0 +1,43 @@ +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; +}; + +sampler2D diffTex : register(s0); +sampler2D envTex : register(s1); + +float4 fogColor : register(c0); + +float4 fxparams : register(c1); +float4 colorClamp : register(c2); + +#define shininess (fxparams.x) +#define disableFBA (fxparams.y) + +float4 main(VS_out input) : COLOR +{ + float4 pass1 = input.Color; + float4 envColor = max(pass1, colorClamp); +#ifdef TEX + pass1 *= tex2D(diffTex, input.TexCoord0.xy); +#endif + + float4 pass2 = envColor*shininess*tex2D(envTex, input.TexCoord1.xy); + + pass1.rgb = lerp(fogColor.rgb, pass1.rgb, input.TexCoord0.z); + pass2.rgb = lerp(float3(0.0, 0.0, 0.0), pass2.rgb, input.TexCoord0.z); + + // We simulate drawing this in two passes. + // First pass with standard blending, second with addition + // We premultiply alpha so render state should be one. + // For FB alpha rendering assume that diffuse alpha (pass1.a) was + // written to framebuffer, so just multiply pass2 by it as well then. + float fba = max(pass1.a, disableFBA); + float4 color; + color.rgb = pass1.rgb*pass1.a + pass2.rgb*fba; + color.a = pass1.a; + + return color; +} diff --git a/src/d3d/shaders/matfx_env_VS.hlsl b/src/d3d/shaders/matfx_env_VS.hlsl new file mode 100644 index 0000000..73179ab --- /dev/null +++ b/src/d3d/shaders/matfx_env_VS.hlsl @@ -0,0 +1,55 @@ +#include "standardConstants.h" + +float4x4 texMat : register(c41); + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float2 TexCoord1 : TEXCOORD1; + float4 Color : COLOR0; +}; + + +VS_out main(in VS_in input) +{ + VS_out output; + + output.Position = mul(combinedMat, input.Position); + float3 V = mul(worldMat, input.Position).xyz; + float3 N = mul(normalMat, input.Normal); + + output.TexCoord0.xy = input.TexCoord; + output.TexCoord1 = mul(texMat, float4(N, 1.0)).xy; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], N)*surfDiffuse; +#endif +#ifdef POINTLIGHTS + for(i = 0; i < numPointLights; i++) + output.Color.xyz += DoPointLight(lights[i+firstPointLight], V, N)*surfDiffuse; +#endif +#ifdef SPOTLIGHTS + for(i = 0; i < numSpotLights; i++) + output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], V, N)*surfDiffuse; +#endif + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/d3d/shaders/matfx_env_all_VS.h b/src/d3d/shaders/matfx_env_all_VS.h new file mode 100644 index 0000000..c049dae --- /dev/null +++ b/src/d3d/shaders/matfx_env_all_VS.h @@ -0,0 +1,514 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh +// matfx_env_all_VS.h matfx_env_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// int numPointLights; +// int numSpotLights; +// float4 surfProps; +// float4x4 texMat; +// float4x4 worldMat; +// +// +// Registers: +// +// Name Reg Size +// -------------- ----- ---- +// numDirLights i0 1 +// numPointLights i1 1 +// numSpotLights i2 1 +// combinedMat c0 4 +// worldMat c4 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// texMat c41 4 +// + + vs_2_0 + def c11, 0, 3, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + mov oPos, r0 + mul r0.xyz, v0.y, c5 + mad r0.xyz, c4, v0.x, r0 + mad r0.xyz, c6, v0.z, r0 + mad r0.xyz, c7, v0.w, r0 + mul r1.xyz, v1.y, c9 + mad r1.xyz, c8, v1.x, r1 + mad r1.xyz, c10, v1.z, r1 + mov r2.x, c13.x + mad r2.xyz, c15, r2.x, v3 + mov r3.xyz, r2 + mov r1.w, c11.x + rep i0 + add r2.w, r1.w, c16.x + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + dp3 r2.w, r1, -c19[a0.x] + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, c17[a0.x] + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c11.z + endrep + mov r2.xyz, r3 + mov r1.w, c11.x + rep i1 + add r2.w, r1.w, c16.y + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + add r4.xyz, r0, -c18[a0.x] + dp3 r2.w, r4, r4 + rsq r2.w, r2.w + mul r4.xyz, r2.w, r4 + dp3 r3.w, r1, -r4 + max r3.w, r3.w, c11.x + mul r4.xyz, r3.w, c17[a0.x] + rcp r2.w, r2.w + rcp r3.w, c17[a0.x].w + mad r2.w, r2.w, -r3.w, c11.z + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, r4 + mad r2.xyz, r4, c13.z, r2 + add r1.w, r1.w, c11.z + endrep + mov r3.xyz, r2 + mov r1.w, c11.x + rep i2 + add r2.w, r1.w, c16.z + mul r2.w, r2.w, c11.y + mova a0.x, r2.w + add r4.xyz, r0, -c18[a0.x] + dp3 r2.w, r4, r4 + rsq r2.w, r2.w + mul r4.xyz, r2.w, r4 + dp3 r4.w, r1, -r4 + dp3 r4.x, r4, c19[a0.x] + max r4.y, r4.w, c11.x + mov r4.z, c11.z + add r4.xz, r4, c18[a0.x].w + rcp r4.z, r4.z + mul r4.x, r4.z, r4.x + slt r4.z, r4.x, c11.x + mad r4.y, r4.z, -r4.y, r4.y + max r4.x, r4.x, c19[a0.x].w + mul r4.x, r4.x, r4.y + mul r4.xyz, r4.x, c17[a0.x] + rcp r2.w, r2.w + rcp r4.w, c17[a0.x].w + mad r2.w, r2.w, -r4.w, c11.z + max r2.w, r2.w, c11.x + mul r4.xyz, r2.w, r4 + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c11.z + endrep + mov r3.w, v3.w + max r2, r3, c11.x + min r2, r2, c11.z + mul oD0, r2, c12 + mul r0.xy, r1.y, c42 + mad r0.xy, c41, r1.x, r0 + mad r0.xy, c43, r1.z, r0 + add oT1.xy, r0, c44 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c11.z + mov oT0.xy, v2 + +// approximately 99 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 165, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 94, 2, + 0, 0, 0, 2, 254, 255, + 13, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 87, 2, 0, 0, 32, 1, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 48, 1, + 0, 0, 0, 0, 0, 0, + 64, 1, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 76, 1, 0, 0, 0, 0, + 0, 0, 92, 1, 0, 0, + 2, 0, 16, 0, 1, 0, + 66, 0, 104, 1, 0, 0, + 0, 0, 0, 0, 120, 1, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 48, 1, + 0, 0, 0, 0, 0, 0, + 128, 1, 0, 0, 2, 0, + 17, 0, 24, 0, 70, 0, + 204, 1, 0, 0, 0, 0, + 0, 0, 220, 1, 0, 0, + 2, 0, 12, 0, 1, 0, + 50, 0, 48, 1, 0, 0, + 0, 0, 0, 0, 227, 1, + 0, 0, 2, 0, 8, 0, + 3, 0, 34, 0, 240, 1, + 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 1, 0, + 0, 0, 1, 0, 2, 0, + 16, 2, 0, 0, 0, 0, + 0, 0, 32, 2, 0, 0, + 1, 0, 1, 0, 1, 0, + 6, 0, 16, 2, 0, 0, + 0, 0, 0, 0, 47, 2, + 0, 0, 1, 0, 2, 0, + 1, 0, 10, 0, 16, 2, + 0, 0, 0, 0, 0, 0, + 61, 2, 0, 0, 2, 0, + 13, 0, 1, 0, 54, 0, + 48, 1, 0, 0, 0, 0, + 0, 0, 71, 2, 0, 0, + 2, 0, 41, 0, 4, 0, + 166, 0, 76, 1, 0, 0, + 0, 0, 0, 0, 78, 2, + 0, 0, 2, 0, 4, 0, + 4, 0, 18, 0, 76, 1, + 0, 0, 0, 0, 0, 0, + 97, 109, 98, 105, 101, 110, + 116, 76, 105, 103, 104, 116, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 99, 111, 109, 98, + 105, 110, 101, 100, 77, 97, + 116, 0, 3, 0, 3, 0, + 4, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 105, 114, 115, 116, 76, + 105, 103, 104, 116, 0, 171, + 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 111, + 103, 68, 97, 116, 97, 0, + 108, 105, 103, 104, 116, 115, + 0, 99, 111, 108, 111, 114, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 111, 115, 105, + 116, 105, 111, 110, 0, 100, + 105, 114, 101, 99, 116, 105, + 111, 110, 0, 171, 135, 1, + 0, 0, 144, 1, 0, 0, + 160, 1, 0, 0, 144, 1, + 0, 0, 169, 1, 0, 0, + 144, 1, 0, 0, 5, 0, + 0, 0, 1, 0, 12, 0, + 8, 0, 3, 0, 180, 1, + 0, 0, 109, 97, 116, 67, + 111, 108, 0, 110, 111, 114, + 109, 97, 108, 77, 97, 116, + 0, 171, 171, 171, 3, 0, + 3, 0, 3, 0, 3, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 110, 117, 109, 68, + 105, 114, 76, 105, 103, 104, + 116, 115, 0, 171, 171, 171, + 0, 0, 2, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 110, 117, + 109, 80, 111, 105, 110, 116, + 76, 105, 103, 104, 116, 115, + 0, 110, 117, 109, 83, 112, + 111, 116, 76, 105, 103, 104, + 116, 115, 0, 115, 117, 114, + 102, 80, 114, 111, 112, 115, + 0, 116, 101, 120, 77, 97, + 116, 0, 119, 111, 114, 108, + 100, 77, 97, 116, 0, 118, + 115, 95, 50, 95, 48, 0, + 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, + 32, 83, 104, 97, 100, 101, + 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 57, + 46, 50, 57, 46, 57, 53, + 50, 46, 51, 49, 49, 49, + 0, 171, 81, 0, 0, 5, + 11, 0, 15, 160, 0, 0, + 0, 0, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 3, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 2, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 3, 0, + 15, 144, 5, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 85, 144, 1, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 228, 160, + 0, 0, 0, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 2, 0, + 228, 160, 0, 0, 170, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 3, 0, 228, 160, 0, 0, + 255, 144, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 15, 192, 0, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 85, 144, + 5, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 7, 128, + 4, 0, 228, 160, 0, 0, + 0, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 7, 128, 6, 0, 228, 160, + 0, 0, 170, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 7, 128, 7, 0, + 228, 160, 0, 0, 255, 144, + 0, 0, 228, 128, 5, 0, + 0, 3, 1, 0, 7, 128, + 1, 0, 85, 144, 9, 0, + 228, 160, 4, 0, 0, 4, + 1, 0, 7, 128, 8, 0, + 228, 160, 1, 0, 0, 144, + 1, 0, 228, 128, 4, 0, + 0, 4, 1, 0, 7, 128, + 10, 0, 228, 160, 1, 0, + 170, 144, 1, 0, 228, 128, + 1, 0, 0, 2, 2, 0, + 1, 128, 13, 0, 0, 160, + 4, 0, 0, 4, 2, 0, + 7, 128, 15, 0, 228, 160, + 2, 0, 0, 128, 3, 0, + 228, 144, 1, 0, 0, 2, + 3, 0, 7, 128, 2, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 8, 128, 11, 0, + 0, 160, 38, 0, 0, 1, + 0, 0, 228, 240, 2, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 16, 0, + 0, 160, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 85, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 2, 0, 255, 128, + 8, 0, 0, 4, 2, 0, + 8, 128, 1, 0, 228, 128, + 19, 32, 228, 161, 0, 0, + 0, 176, 11, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 0, 160, + 5, 0, 0, 4, 4, 0, + 7, 128, 2, 0, 255, 128, + 17, 32, 228, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 3, 0, 7, 128, 4, 0, + 228, 128, 13, 0, 170, 160, + 3, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 170, 160, 39, 0, 0, 0, + 1, 0, 0, 2, 2, 0, + 7, 128, 3, 0, 228, 128, + 1, 0, 0, 2, 1, 0, + 8, 128, 11, 0, 0, 160, + 38, 0, 0, 1, 1, 0, + 228, 240, 2, 0, 0, 3, + 2, 0, 8, 128, 1, 0, + 255, 128, 16, 0, 85, 160, + 5, 0, 0, 3, 2, 0, + 8, 128, 2, 0, 255, 128, + 11, 0, 85, 160, 46, 0, + 0, 2, 0, 0, 1, 176, + 2, 0, 255, 128, 2, 0, + 0, 4, 4, 0, 7, 128, + 0, 0, 228, 128, 18, 32, + 228, 161, 0, 0, 0, 176, + 8, 0, 0, 3, 2, 0, + 8, 128, 4, 0, 228, 128, + 4, 0, 228, 128, 7, 0, + 0, 2, 2, 0, 8, 128, + 2, 0, 255, 128, 5, 0, + 0, 3, 4, 0, 7, 128, + 2, 0, 255, 128, 4, 0, + 228, 128, 8, 0, 0, 3, + 3, 0, 8, 128, 1, 0, + 228, 128, 4, 0, 228, 129, + 11, 0, 0, 3, 3, 0, + 8, 128, 3, 0, 255, 128, + 11, 0, 0, 160, 5, 0, + 0, 4, 4, 0, 7, 128, + 3, 0, 255, 128, 17, 32, + 228, 160, 0, 0, 0, 176, + 6, 0, 0, 2, 2, 0, + 8, 128, 2, 0, 255, 128, + 6, 0, 0, 3, 3, 0, + 8, 128, 17, 32, 255, 160, + 0, 0, 0, 176, 4, 0, + 0, 4, 2, 0, 8, 128, + 2, 0, 255, 128, 3, 0, + 255, 129, 11, 0, 170, 160, + 11, 0, 0, 3, 2, 0, + 8, 128, 2, 0, 255, 128, + 11, 0, 0, 160, 5, 0, + 0, 3, 4, 0, 7, 128, + 2, 0, 255, 128, 4, 0, + 228, 128, 4, 0, 0, 4, + 2, 0, 7, 128, 4, 0, + 228, 128, 13, 0, 170, 160, + 2, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 170, 160, 39, 0, 0, 0, + 1, 0, 0, 2, 3, 0, + 7, 128, 2, 0, 228, 128, + 1, 0, 0, 2, 1, 0, + 8, 128, 11, 0, 0, 160, + 38, 0, 0, 1, 2, 0, + 228, 240, 2, 0, 0, 3, + 2, 0, 8, 128, 1, 0, + 255, 128, 16, 0, 170, 160, + 5, 0, 0, 3, 2, 0, + 8, 128, 2, 0, 255, 128, + 11, 0, 85, 160, 46, 0, + 0, 2, 0, 0, 1, 176, + 2, 0, 255, 128, 2, 0, + 0, 4, 4, 0, 7, 128, + 0, 0, 228, 128, 18, 32, + 228, 161, 0, 0, 0, 176, + 8, 0, 0, 3, 2, 0, + 8, 128, 4, 0, 228, 128, + 4, 0, 228, 128, 7, 0, + 0, 2, 2, 0, 8, 128, + 2, 0, 255, 128, 5, 0, + 0, 3, 4, 0, 7, 128, + 2, 0, 255, 128, 4, 0, + 228, 128, 8, 0, 0, 3, + 4, 0, 8, 128, 1, 0, + 228, 128, 4, 0, 228, 129, + 8, 0, 0, 4, 4, 0, + 1, 128, 4, 0, 228, 128, + 19, 32, 228, 160, 0, 0, + 0, 176, 11, 0, 0, 3, + 4, 0, 2, 128, 4, 0, + 255, 128, 11, 0, 0, 160, + 1, 0, 0, 2, 4, 0, + 4, 128, 11, 0, 170, 160, + 2, 0, 0, 4, 4, 0, + 5, 128, 4, 0, 228, 128, + 18, 32, 255, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 4, 0, 4, 128, 4, 0, + 170, 128, 5, 0, 0, 3, + 4, 0, 1, 128, 4, 0, + 170, 128, 4, 0, 0, 128, + 12, 0, 0, 3, 4, 0, + 4, 128, 4, 0, 0, 128, + 11, 0, 0, 160, 4, 0, + 0, 4, 4, 0, 2, 128, + 4, 0, 170, 128, 4, 0, + 85, 129, 4, 0, 85, 128, + 11, 0, 0, 4, 4, 0, + 1, 128, 4, 0, 0, 128, + 19, 32, 255, 160, 0, 0, + 0, 176, 5, 0, 0, 3, + 4, 0, 1, 128, 4, 0, + 0, 128, 4, 0, 85, 128, + 5, 0, 0, 4, 4, 0, + 7, 128, 4, 0, 0, 128, + 17, 32, 228, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 2, 0, 8, 128, 2, 0, + 255, 128, 6, 0, 0, 3, + 4, 0, 8, 128, 17, 32, + 255, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 2, 0, + 8, 128, 2, 0, 255, 128, + 4, 0, 255, 129, 11, 0, + 170, 160, 11, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 11, 0, 0, 160, + 5, 0, 0, 3, 4, 0, + 7, 128, 2, 0, 255, 128, + 4, 0, 228, 128, 4, 0, + 0, 4, 3, 0, 7, 128, + 4, 0, 228, 128, 13, 0, + 170, 160, 3, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 3, 0, 8, 128, 3, 0, + 255, 144, 11, 0, 0, 3, + 2, 0, 15, 128, 3, 0, + 228, 128, 11, 0, 0, 160, + 10, 0, 0, 3, 2, 0, + 15, 128, 2, 0, 228, 128, + 11, 0, 170, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 2, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 0, 0, 3, 128, 1, 0, + 85, 128, 42, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 3, 128, 41, 0, 228, 160, + 1, 0, 0, 128, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 3, 128, 43, 0, + 228, 160, 1, 0, 170, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 3, 224, + 0, 0, 228, 128, 44, 0, + 228, 160, 2, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 255, 128, 14, 0, 85, 161, + 5, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 170, 160, 11, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 0, 128, 14, 0, + 255, 160, 10, 0, 0, 3, + 0, 0, 4, 224, 0, 0, + 0, 128, 11, 0, 170, 160, + 1, 0, 0, 2, 0, 0, + 3, 224, 2, 0, 228, 144, + 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/matfx_env_amb_VS.h b/src/d3d/shaders/matfx_env_amb_VS.h new file mode 100644 index 0000000..c6314fb --- /dev/null +++ b/src/d3d/shaders/matfx_env_amb_VS.h @@ -0,0 +1,204 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /Fh matfx_env_amb_VS.h matfx_env_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// float4 fogData; +// float4 matCol; +// float3x3 normalMat; +// float4 surfProps; +// float4x4 texMat; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// combinedMat c0 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// texMat c41 4 +// + + vs_2_0 + def c4, 0, 1, 0, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0.xyz, v1.y, c9 + mad r0.xyz, c8, v1.x, r0 + mad r0.xyz, c10, v1.z, r0 + mul r0.yw, r0.y, c42.xxzy + mad r0.xy, c41, r0.x, r0.ywzw + mad r0.xy, c43, r0.z, r0 + add oT1.xy, r0, c44 + mov r0.xyz, c15 + mad r0.xyz, r0, c13.x, v3 + mov r0.w, v3.w + max r0, r0, c4.x + min r0, r0, c4.y + mul oD0, r0, c12 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + add r1.x, r0.w, -c14.y + mov oPos, r0 + mul r0.x, r1.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.y + mov oT0.xy, v2 + +// approximately 23 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 88, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 40, 1, + 0, 0, 0, 2, 254, 255, + 7, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 33, 1, 0, 0, 168, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 184, 0, + 0, 0, 0, 0, 0, 0, + 200, 0, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 212, 0, 0, 0, 0, 0, + 0, 0, 228, 0, 0, 0, + 2, 0, 14, 0, 1, 0, + 58, 0, 184, 0, 0, 0, + 0, 0, 0, 0, 236, 0, + 0, 0, 2, 0, 12, 0, + 1, 0, 50, 0, 184, 0, + 0, 0, 0, 0, 0, 0, + 243, 0, 0, 0, 2, 0, + 8, 0, 3, 0, 34, 0, + 0, 1, 0, 0, 0, 0, + 0, 0, 16, 1, 0, 0, + 2, 0, 13, 0, 1, 0, + 54, 0, 184, 0, 0, 0, + 0, 0, 0, 0, 26, 1, + 0, 0, 2, 0, 41, 0, + 4, 0, 166, 0, 212, 0, + 0, 0, 0, 0, 0, 0, + 97, 109, 98, 105, 101, 110, + 116, 76, 105, 103, 104, 116, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 99, 111, 109, 98, + 105, 110, 101, 100, 77, 97, + 116, 0, 3, 0, 3, 0, + 4, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 68, 97, 116, + 97, 0, 109, 97, 116, 67, + 111, 108, 0, 110, 111, 114, + 109, 97, 108, 77, 97, 116, + 0, 171, 171, 171, 3, 0, + 3, 0, 3, 0, 3, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 115, 117, 114, 102, + 80, 114, 111, 112, 115, 0, + 116, 101, 120, 77, 97, 116, + 0, 118, 115, 95, 50, 95, + 48, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 57, 46, 50, 57, 46, + 57, 53, 50, 46, 51, 49, + 49, 49, 0, 171, 171, 171, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 3, 0, + 0, 128, 1, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 0, 128, 2, 0, 15, 144, + 31, 0, 0, 2, 10, 0, + 0, 128, 3, 0, 15, 144, + 5, 0, 0, 3, 0, 0, + 7, 128, 1, 0, 85, 144, + 9, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 7, 128, + 8, 0, 228, 160, 1, 0, + 0, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 7, 128, 10, 0, 228, 160, + 1, 0, 170, 144, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 10, 128, 0, 0, + 85, 128, 42, 0, 96, 160, + 4, 0, 0, 4, 0, 0, + 3, 128, 41, 0, 228, 160, + 0, 0, 0, 128, 0, 0, + 237, 128, 4, 0, 0, 4, + 0, 0, 3, 128, 43, 0, + 228, 160, 0, 0, 170, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 3, 224, + 0, 0, 228, 128, 44, 0, + 228, 160, 1, 0, 0, 2, + 0, 0, 7, 128, 15, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 7, 128, 0, 0, + 228, 128, 13, 0, 0, 160, + 3, 0, 228, 144, 1, 0, + 0, 2, 0, 0, 8, 128, + 3, 0, 255, 144, 11, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 4, 0, + 0, 160, 10, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 4, 0, 85, 160, + 5, 0, 0, 3, 0, 0, + 15, 208, 0, 0, 228, 128, + 12, 0, 228, 160, 5, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 85, 144, 1, 0, + 228, 160, 4, 0, 0, 4, + 0, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 15, 128, 3, 0, 228, 160, + 0, 0, 255, 144, 0, 0, + 228, 128, 2, 0, 0, 3, + 1, 0, 1, 128, 0, 0, + 255, 128, 14, 0, 85, 161, + 1, 0, 0, 2, 0, 0, + 15, 192, 0, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 1, 128, 1, 0, 0, 128, + 14, 0, 170, 160, 11, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 0, 128, 14, 0, + 255, 160, 10, 0, 0, 3, + 0, 0, 4, 224, 0, 0, + 0, 128, 4, 0, 85, 160, + 1, 0, 0, 2, 0, 0, + 3, 224, 2, 0, 228, 144, + 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/matfx_env_amb_dir_VS.h b/src/d3d/shaders/matfx_env_amb_dir_VS.h new file mode 100644 index 0000000..98d537a --- /dev/null +++ b/src/d3d/shaders/matfx_env_amb_dir_VS.h @@ -0,0 +1,295 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /Fh matfx_env_amb_dir_VS.h +// matfx_env_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// float4 surfProps; +// float4x4 texMat; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// numDirLights i0 1 +// combinedMat c0 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// texMat c41 4 +// + + vs_2_0 + def c4, 0, 3, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + mul r0, v0.y, c1 + mad r0, c0, v0.x, r0 + mad r0, c2, v0.z, r0 + mad r0, c3, v0.w, r0 + mov oPos, r0 + mul r0.xyz, v1.y, c9 + mad r0.xyz, c8, v1.x, r0 + mad r0.xyz, c10, v1.z, r0 + mov r1.x, c13.x + mad r1.xyz, c15, r1.x, v3 + mov r2.xyz, r1 + mov r1.w, c4.x + rep i0 + add r3.x, r1.w, c16.x + mul r3.x, r3.x, c4.y + mova a0.x, r3.x + dp3 r3.x, r0, -c19[a0.x] + max r3.x, r3.x, c4.x + mul r3.xyz, r3.x, c17[a0.x] + mad r2.xyz, r3, c13.z, r2 + add r1.w, r1.w, c4.z + endrep + mov r2.w, v3.w + max r1, r2, c4.x + min r1, r1, c4.z + mul oD0, r1, c12 + mul r1.xy, r0.y, c42 + mad r0.xy, c41, r0.x, r1 + mad r0.xy, c43, r0.z, r0 + add oT1.xy, r0, c44 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.z + mov oT0.xy, v2 + +// approximately 38 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 141, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 252, 1, + 0, 0, 0, 2, 254, 255, + 10, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 245, 1, 0, 0, 228, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 244, 0, + 0, 0, 0, 0, 0, 0, + 4, 1, 0, 0, 2, 0, + 0, 0, 4, 0, 2, 0, + 16, 1, 0, 0, 0, 0, + 0, 0, 32, 1, 0, 0, + 2, 0, 16, 0, 1, 0, + 66, 0, 44, 1, 0, 0, + 0, 0, 0, 0, 60, 1, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 244, 0, + 0, 0, 0, 0, 0, 0, + 68, 1, 0, 0, 2, 0, + 17, 0, 24, 0, 70, 0, + 144, 1, 0, 0, 0, 0, + 0, 0, 160, 1, 0, 0, + 2, 0, 12, 0, 1, 0, + 50, 0, 244, 0, 0, 0, + 0, 0, 0, 0, 167, 1, + 0, 0, 2, 0, 8, 0, + 3, 0, 34, 0, 180, 1, + 0, 0, 0, 0, 0, 0, + 196, 1, 0, 0, 1, 0, + 0, 0, 1, 0, 2, 0, + 212, 1, 0, 0, 0, 0, + 0, 0, 228, 1, 0, 0, + 2, 0, 13, 0, 1, 0, + 54, 0, 244, 0, 0, 0, + 0, 0, 0, 0, 238, 1, + 0, 0, 2, 0, 41, 0, + 4, 0, 166, 0, 16, 1, + 0, 0, 0, 0, 0, 0, + 97, 109, 98, 105, 101, 110, + 116, 76, 105, 103, 104, 116, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 99, 111, 109, 98, + 105, 110, 101, 100, 77, 97, + 116, 0, 3, 0, 3, 0, + 4, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 105, 114, 115, 116, 76, + 105, 103, 104, 116, 0, 171, + 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 102, 111, + 103, 68, 97, 116, 97, 0, + 108, 105, 103, 104, 116, 115, + 0, 99, 111, 108, 111, 114, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 112, 111, 115, 105, + 116, 105, 111, 110, 0, 100, + 105, 114, 101, 99, 116, 105, + 111, 110, 0, 171, 75, 1, + 0, 0, 84, 1, 0, 0, + 100, 1, 0, 0, 84, 1, + 0, 0, 109, 1, 0, 0, + 84, 1, 0, 0, 5, 0, + 0, 0, 1, 0, 12, 0, + 8, 0, 3, 0, 120, 1, + 0, 0, 109, 97, 116, 67, + 111, 108, 0, 110, 111, 114, + 109, 97, 108, 77, 97, 116, + 0, 171, 171, 171, 3, 0, + 3, 0, 3, 0, 3, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 110, 117, 109, 68, + 105, 114, 76, 105, 103, 104, + 116, 115, 0, 171, 171, 171, + 0, 0, 2, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 115, 117, + 114, 102, 80, 114, 111, 112, + 115, 0, 116, 101, 120, 77, + 97, 116, 0, 118, 115, 95, + 50, 95, 48, 0, 77, 105, + 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, + 101, 114, 32, 57, 46, 50, + 57, 46, 57, 53, 50, 46, + 51, 49, 49, 49, 0, 171, + 171, 171, 81, 0, 0, 5, + 4, 0, 15, 160, 0, 0, + 0, 0, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 3, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 2, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 3, 0, + 15, 144, 5, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 85, 144, 1, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 228, 160, + 0, 0, 0, 144, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 2, 0, + 228, 160, 0, 0, 170, 144, + 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 3, 0, 228, 160, 0, 0, + 255, 144, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 15, 192, 0, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 7, 128, 1, 0, 85, 144, + 9, 0, 228, 160, 4, 0, + 0, 4, 0, 0, 7, 128, + 8, 0, 228, 160, 1, 0, + 0, 144, 0, 0, 228, 128, + 4, 0, 0, 4, 0, 0, + 7, 128, 10, 0, 228, 160, + 1, 0, 170, 144, 0, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 1, 128, 13, 0, + 0, 160, 4, 0, 0, 4, + 1, 0, 7, 128, 15, 0, + 228, 160, 1, 0, 0, 128, + 3, 0, 228, 144, 1, 0, + 0, 2, 2, 0, 7, 128, + 1, 0, 228, 128, 1, 0, + 0, 2, 1, 0, 8, 128, + 4, 0, 0, 160, 38, 0, + 0, 1, 0, 0, 228, 240, + 2, 0, 0, 3, 3, 0, + 1, 128, 1, 0, 255, 128, + 16, 0, 0, 160, 5, 0, + 0, 3, 3, 0, 1, 128, + 3, 0, 0, 128, 4, 0, + 85, 160, 46, 0, 0, 2, + 0, 0, 1, 176, 3, 0, + 0, 128, 8, 0, 0, 4, + 3, 0, 1, 128, 0, 0, + 228, 128, 19, 32, 228, 161, + 0, 0, 0, 176, 11, 0, + 0, 3, 3, 0, 1, 128, + 3, 0, 0, 128, 4, 0, + 0, 160, 5, 0, 0, 4, + 3, 0, 7, 128, 3, 0, + 0, 128, 17, 32, 228, 160, + 0, 0, 0, 176, 4, 0, + 0, 4, 2, 0, 7, 128, + 3, 0, 228, 128, 13, 0, + 170, 160, 2, 0, 228, 128, + 2, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 4, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 2, 0, 8, 128, 3, 0, + 255, 144, 11, 0, 0, 3, + 1, 0, 15, 128, 2, 0, + 228, 128, 4, 0, 0, 160, + 10, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 228, 128, + 4, 0, 170, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 1, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 1, 0, 3, 128, 0, 0, + 85, 128, 42, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 3, 128, 41, 0, 228, 160, + 0, 0, 0, 128, 1, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 3, 128, 43, 0, + 228, 160, 0, 0, 170, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 3, 224, + 0, 0, 228, 128, 44, 0, + 228, 160, 2, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 255, 128, 14, 0, 85, 161, + 5, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 170, 160, 11, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 0, 128, 14, 0, + 255, 160, 10, 0, 0, 3, + 0, 0, 4, 224, 0, 0, + 0, 128, 4, 0, 170, 160, + 1, 0, 0, 2, 0, 0, + 3, 224, 2, 0, 228, 144, + 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/matfx_env_tex_PS.h b/src/d3d/shaders/matfx_env_tex_PS.h new file mode 100644 index 0000000..3a2d442 --- /dev/null +++ b/src/d3d/shaders/matfx_env_tex_PS.h @@ -0,0 +1,149 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T ps_2_0 /DTEX /Fh matfx_env_tex_PS.h matfx_env_PS.hlsl +// +// +// Parameters: +// +// float4 colorClamp; +// sampler2D diffTex; +// sampler2D envTex; +// float4 fogColor; +// float4 fxparams; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// fogColor c0 1 +// fxparams c1 1 +// colorClamp c2 1 +// diffTex s0 1 +// envTex s1 1 +// + + ps_2_0 + dcl t0.xyz + dcl t1.xy + dcl v0 + dcl_2d s0 + dcl_2d s1 + texld r0, t1, s1 + texld r1, t0, s0 + max r2.xyz, v0, c2 + mul r2.xyz, r2, c1.x + mul r0.xyz, r0, r2 + mul r0.xyz, r0, t0.z + mul r2.w, r1.w, v0.w + mad r1.xyz, v0, r1, -c0 + mad r1.xyz, t0.z, r1, c0 + max r0.w, r2.w, c1.y + mul r0.xyz, r0.w, r0 + mad r2.xyz, r1, r2.w, r0 + mov oC0, r2 + +// approximately 13 instruction slots used (2 texture, 11 arithmetic) +#endif + +const BYTE g_ps20_main[] = +{ + 0, 2, 255, 255, 254, 255, + 71, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 229, 0, + 0, 0, 0, 2, 255, 255, + 5, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 222, 0, 0, 0, 128, 0, + 0, 0, 2, 0, 2, 0, + 1, 0, 10, 0, 140, 0, + 0, 0, 0, 0, 0, 0, + 156, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 2, 0, + 164, 0, 0, 0, 0, 0, + 0, 0, 180, 0, 0, 0, + 3, 0, 1, 0, 1, 0, + 6, 0, 188, 0, 0, 0, + 0, 0, 0, 0, 204, 0, + 0, 0, 2, 0, 0, 0, + 1, 0, 2, 0, 140, 0, + 0, 0, 0, 0, 0, 0, + 213, 0, 0, 0, 2, 0, + 1, 0, 1, 0, 6, 0, + 140, 0, 0, 0, 0, 0, + 0, 0, 99, 111, 108, 111, + 114, 67, 108, 97, 109, 112, + 0, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 100, 105, 102, 102, 84, 101, + 120, 0, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 101, 110, 118, 84, 101, 120, + 0, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 67, 111, 108, + 111, 114, 0, 102, 120, 112, + 97, 114, 97, 109, 115, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 171, 171, 31, 0, + 0, 2, 0, 0, 0, 128, + 0, 0, 7, 176, 31, 0, + 0, 2, 0, 0, 0, 128, + 1, 0, 3, 176, 31, 0, + 0, 2, 0, 0, 0, 128, + 0, 0, 15, 144, 31, 0, + 0, 2, 0, 0, 0, 144, + 0, 8, 15, 160, 31, 0, + 0, 2, 0, 0, 0, 144, + 1, 8, 15, 160, 66, 0, + 0, 3, 0, 0, 15, 128, + 1, 0, 228, 176, 1, 8, + 228, 160, 66, 0, 0, 3, + 1, 0, 15, 128, 0, 0, + 228, 176, 0, 8, 228, 160, + 11, 0, 0, 3, 2, 0, + 7, 128, 0, 0, 228, 144, + 2, 0, 228, 160, 5, 0, + 0, 3, 2, 0, 7, 128, + 2, 0, 228, 128, 1, 0, + 0, 160, 5, 0, 0, 3, + 0, 0, 7, 128, 0, 0, + 228, 128, 2, 0, 228, 128, + 5, 0, 0, 3, 0, 0, + 7, 128, 0, 0, 228, 128, + 0, 0, 170, 176, 5, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 0, 0, + 255, 144, 4, 0, 0, 4, + 1, 0, 7, 128, 0, 0, + 228, 144, 1, 0, 228, 128, + 0, 0, 228, 161, 4, 0, + 0, 4, 1, 0, 7, 128, + 0, 0, 170, 176, 1, 0, + 228, 128, 0, 0, 228, 160, + 11, 0, 0, 3, 0, 0, + 8, 128, 2, 0, 255, 128, + 1, 0, 85, 160, 5, 0, + 0, 3, 0, 0, 7, 128, + 0, 0, 255, 128, 0, 0, + 228, 128, 4, 0, 0, 4, + 2, 0, 7, 128, 1, 0, + 228, 128, 2, 0, 255, 128, + 0, 0, 228, 128, 1, 0, + 0, 2, 0, 8, 15, 128, + 2, 0, 228, 128, 255, 255, + 0, 0 +}; diff --git a/src/d3d/shaders/skin_VS.hlsl b/src/d3d/shaders/skin_VS.hlsl new file mode 100644 index 0000000..bede50f --- /dev/null +++ b/src/d3d/shaders/skin_VS.hlsl @@ -0,0 +1,63 @@ +#include "standardConstants.h" + +float4x3 boneMatrices[64] : register(c41); + +struct VS_in +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; + float4 Prelight : COLOR0; + float4 Weights : BLENDWEIGHT; + int4 Indices : BLENDINDICES; +}; + +struct VS_out { + float4 Position : POSITION; + float3 TexCoord0 : TEXCOORD0; // also fog + float4 Color : COLOR0; +}; + + +VS_out main(in VS_in input) +{ + VS_out output; + + int j; + float3 SkinVertex = float3(0.0, 0.0, 0.0); + float3 SkinNormal = float3(0.0, 0.0, 0.0); + for(j = 0; j < 4; j++){ + SkinVertex += mul(input.Position, boneMatrices[input.Indices[j]]).xyz * input.Weights[j]; + SkinNormal += mul(input.Normal, (float3x3)boneMatrices[input.Indices[j]]).xyz * input.Weights[j]; + } + + output.Position = mul(combinedMat, float4(SkinVertex, 1.0)); + float3 Vertex = mul(worldMat, float4(SkinVertex, 1.0)).xyz; + float3 Normal = mul(normalMat, SkinNormal); + + output.TexCoord0.xy = input.TexCoord; + + output.Color = input.Prelight; + output.Color.rgb += ambientLight.rgb * surfAmbient; + + int i; +#ifdef DIRECTIONALS + for(i = 0; i < numDirLights; i++) + output.Color.xyz += DoDirLight(lights[i+firstDirLight], Normal)*surfDiffuse; +#endif +#ifdef POINTLIGHTS + for(i = 0; i < numPointLights; i++) + output.Color.xyz += DoPointLight(lights[i+firstPointLight], Vertex.xyz, Normal)*surfDiffuse; +#endif +#ifdef SPOTLIGHTS + for(i = 0; i < numSpotLights; i++) + output.Color.xyz += DoSpotLight(lights[i+firstSpotLight], Vertex.xyz, Normal)*surfDiffuse; +#endif + // PS2 clamps before material color + output.Color = clamp(output.Color, 0.0, 1.0); + output.Color *= matCol; + + output.TexCoord0.z = clamp((output.Position.w - fogEnd)*fogRange, fogDisable, 1.0); + + return output; +} diff --git a/src/d3d/shaders/skin_all_VS.h b/src/d3d/shaders/skin_all_VS.h new file mode 100644 index 0000000..67f89f3 --- /dev/null +++ b/src/d3d/shaders/skin_all_VS.h @@ -0,0 +1,664 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /DPOINTLIGHTS /DSPOTLIGHTS /Fh +// skin_all_VS.h skin_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x3 boneMatrices[64]; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// int numPointLights; +// int numSpotLights; +// float4 surfProps; +// float4x4 worldMat; +// +// +// Registers: +// +// Name Reg Size +// -------------- ----- ---- +// numDirLights i0 1 +// numPointLights i1 1 +// numSpotLights i2 1 +// combinedMat c0 4 +// worldMat c4 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// boneMatrices c41 192 +// + + vs_2_0 + def c11, 3, 0, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + dcl_blendweight v4 + dcl_blendindices v5 + mul r0, v5, c11.x + mova a0.x, r0.x + dp3 r1.z, v1, c43[a0.x] + dp4 r2.x, v0, c41[a0.x] + dp4 r2.y, v0, c42[a0.x] + dp4 r2.z, v0, c43[a0.x] + dp3 r1.x, v1, c41[a0.x] + dp3 r1.y, v1, c42[a0.x] + mova a0.x, r0.y + dp3 r3.z, v1, c43[a0.x] + dp4 r4.x, v0, c41[a0.x] + dp4 r4.y, v0, c42[a0.x] + dp4 r4.z, v0, c43[a0.x] + mul r4.xyz, r4, v4.y + mad r2.xyz, r2, v4.x, r4 + dp3 r3.x, v1, c41[a0.x] + dp3 r3.y, v1, c42[a0.x] + mul r3.xyz, r3, v4.y + mad r1.xyz, r1, v4.x, r3 + mova a0.x, r0.z + dp3 r0.z, v1, c43[a0.x] + dp4 r3.x, v0, c41[a0.x] + dp4 r3.y, v0, c42[a0.x] + dp4 r3.z, v0, c43[a0.x] + mad r2.xyz, r3, v4.z, r2 + dp3 r0.x, v1, c41[a0.x] + dp3 r0.y, v1, c42[a0.x] + mad r0.xyz, r0, v4.z, r1 + mova a0.x, r0.w + dp3 r1.z, v1, c43[a0.x] + dp4 r3.x, v0, c41[a0.x] + dp4 r3.y, v0, c42[a0.x] + dp4 r3.z, v0, c43[a0.x] + mad r2.xyz, r3, v4.w, r2 + dp3 r1.x, v1, c41[a0.x] + dp3 r1.y, v1, c42[a0.x] + mad r0.xyz, r1, v4.w, r0 + mul r1.xyz, r2.y, c5 + mad r1.xyz, c4, r2.x, r1 + mad r1.xyz, c6, r2.z, r1 + add r1.xyz, r1, c7 + mul r3.xyz, r0.y, c9 + mad r0.xyw, c8.xyzz, r0.x, r3.xyzz + mad r0.xyz, c10, r0.z, r0.xyww + mov r3.x, c13.x + mad r3.xyz, c15, r3.x, v3 + mov r4.xyz, r3 + mov r0.w, c11.y + rep i0 + add r1.w, r0.w, c16.x + mul r1.w, r1.w, c11.x + mova a0.x, r1.w + dp3 r1.w, r0, -c19[a0.x] + max r1.w, r1.w, c11.y + mul r5.xyz, r1.w, c17[a0.x] + mad r4.xyz, r5, c13.z, r4 + add r0.w, r0.w, c11.z + endrep + mov r3.xyz, r4 + mov r0.w, c11.y + rep i1 + add r1.w, r0.w, c16.y + mul r1.w, r1.w, c11.x + mova a0.x, r1.w + add r5.xyz, r1, -c18[a0.x] + dp3 r1.w, r5, r5 + rsq r1.w, r1.w + mul r5.xyz, r1.w, r5 + dp3 r2.w, r0, -r5 + max r2.w, r2.w, c11.y + mul r5.xyz, r2.w, c17[a0.x] + rcp r1.w, r1.w + rcp r2.w, c17[a0.x].w + mad r1.w, r1.w, -r2.w, c11.z + max r1.w, r1.w, c11.y + mul r5.xyz, r1.w, r5 + mad r3.xyz, r5, c13.z, r3 + add r0.w, r0.w, c11.z + endrep + mov r4.xyz, r3 + mov r0.w, c11.y + rep i2 + add r1.w, r0.w, c16.z + mul r1.w, r1.w, c11.x + mova a0.x, r1.w + add r5.xyz, r1, -c18[a0.x] + dp3 r1.w, r5, r5 + rsq r1.w, r1.w + mul r5.xyz, r1.w, r5 + dp3 r2.w, r0, -r5 + dp3 r3.w, r5, c19[a0.x] + max r2.w, r2.w, c11.y + add r3.w, r3.w, c18[a0.x].w + mov r5.z, c11.z + add r5.x, r5.z, c18[a0.x].w + rcp r5.x, r5.x + mul r3.w, r3.w, r5.x + slt r5.x, r3.w, c11.y + mad r2.w, r5.x, -r2.w, r2.w + max r3.w, r3.w, c19[a0.x].w + mul r2.w, r2.w, r3.w + mul r5.xyz, r2.w, c17[a0.x] + rcp r1.w, r1.w + rcp r2.w, c17[a0.x].w + mad r1.w, r1.w, -r2.w, c11.z + max r1.w, r1.w, c11.y + mul r5.xyz, r1.w, r5 + mad r4.xyz, r5, c13.z, r4 + add r0.w, r0.w, c11.z + endrep + mov r4.w, v3.w + max r0, r4, c11.y + min r0, r0, c11.z + mul oD0, r0, c12 + mul r0, r2.y, c1 + mad r0, c0, r2.x, r0 + mad r0, c2, r2.z, r0 + add r0, r0, c3 + mov oPos, r0 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c11.z + mov oT0.xy, v2 + +// approximately 133 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 171, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 119, 2, + 0, 0, 0, 2, 254, 255, + 13, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 112, 2, 0, 0, 32, 1, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 48, 1, + 0, 0, 0, 0, 0, 0, + 64, 1, 0, 0, 2, 0, + 41, 0, 192, 0, 166, 0, + 80, 1, 0, 0, 0, 0, + 0, 0, 96, 1, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 108, 1, 0, 0, + 0, 0, 0, 0, 124, 1, + 0, 0, 2, 0, 16, 0, + 1, 0, 66, 0, 136, 1, + 0, 0, 0, 0, 0, 0, + 152, 1, 0, 0, 2, 0, + 14, 0, 1, 0, 58, 0, + 48, 1, 0, 0, 0, 0, + 0, 0, 160, 1, 0, 0, + 2, 0, 17, 0, 24, 0, + 70, 0, 236, 1, 0, 0, + 0, 0, 0, 0, 252, 1, + 0, 0, 2, 0, 12, 0, + 1, 0, 50, 0, 48, 1, + 0, 0, 0, 0, 0, 0, + 3, 2, 0, 0, 2, 0, + 8, 0, 3, 0, 34, 0, + 16, 2, 0, 0, 0, 0, + 0, 0, 32, 2, 0, 0, + 1, 0, 0, 0, 1, 0, + 2, 0, 48, 2, 0, 0, + 0, 0, 0, 0, 64, 2, + 0, 0, 1, 0, 1, 0, + 1, 0, 6, 0, 48, 2, + 0, 0, 0, 0, 0, 0, + 79, 2, 0, 0, 1, 0, + 2, 0, 1, 0, 10, 0, + 48, 2, 0, 0, 0, 0, + 0, 0, 93, 2, 0, 0, + 2, 0, 13, 0, 1, 0, + 54, 0, 48, 1, 0, 0, + 0, 0, 0, 0, 103, 2, + 0, 0, 2, 0, 4, 0, + 4, 0, 18, 0, 108, 1, + 0, 0, 0, 0, 0, 0, + 97, 109, 98, 105, 101, 110, + 116, 76, 105, 103, 104, 116, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 98, 111, 110, 101, + 77, 97, 116, 114, 105, 99, + 101, 115, 0, 171, 171, 171, + 3, 0, 3, 0, 4, 0, + 3, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 99, 111, + 109, 98, 105, 110, 101, 100, + 77, 97, 116, 0, 3, 0, + 3, 0, 4, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 105, 114, 115, + 116, 76, 105, 103, 104, 116, + 0, 171, 1, 0, 2, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 68, 97, 116, + 97, 0, 108, 105, 103, 104, + 116, 115, 0, 99, 111, 108, + 111, 114, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 111, + 115, 105, 116, 105, 111, 110, + 0, 100, 105, 114, 101, 99, + 116, 105, 111, 110, 0, 171, + 167, 1, 0, 0, 176, 1, + 0, 0, 192, 1, 0, 0, + 176, 1, 0, 0, 201, 1, + 0, 0, 176, 1, 0, 0, + 5, 0, 0, 0, 1, 0, + 12, 0, 8, 0, 3, 0, + 212, 1, 0, 0, 109, 97, + 116, 67, 111, 108, 0, 110, + 111, 114, 109, 97, 108, 77, + 97, 116, 0, 171, 171, 171, + 3, 0, 3, 0, 3, 0, + 3, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 110, 117, + 109, 68, 105, 114, 76, 105, + 103, 104, 116, 115, 0, 171, + 171, 171, 0, 0, 2, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 110, 117, 109, 80, 111, 105, + 110, 116, 76, 105, 103, 104, + 116, 115, 0, 110, 117, 109, + 83, 112, 111, 116, 76, 105, + 103, 104, 116, 115, 0, 115, + 117, 114, 102, 80, 114, 111, + 112, 115, 0, 119, 111, 114, + 108, 100, 77, 97, 116, 0, + 118, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 57, 46, 57, + 53, 50, 46, 51, 49, 49, + 49, 0, 81, 0, 0, 5, + 11, 0, 15, 160, 0, 0, + 64, 64, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 3, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 2, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 3, 0, + 15, 144, 31, 0, 0, 2, + 1, 0, 0, 128, 4, 0, + 15, 144, 31, 0, 0, 2, + 2, 0, 0, 128, 5, 0, + 15, 144, 5, 0, 0, 3, + 0, 0, 15, 128, 5, 0, + 228, 144, 11, 0, 0, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 0, 0, 0, 128, + 8, 0, 0, 4, 1, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 2, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 2, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 2, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 8, 0, 0, 4, + 1, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 8, 0, + 0, 4, 1, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 46, 0, 0, 2, 0, 0, + 1, 176, 0, 0, 85, 128, + 8, 0, 0, 4, 3, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 4, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 4, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 4, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 5, 0, 0, 3, + 4, 0, 7, 128, 4, 0, + 228, 128, 4, 0, 85, 144, + 4, 0, 0, 4, 2, 0, + 7, 128, 2, 0, 228, 128, + 4, 0, 0, 144, 4, 0, + 228, 128, 8, 0, 0, 4, + 3, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 8, 0, + 0, 4, 3, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 5, 0, 0, 3, 3, 0, + 7, 128, 3, 0, 228, 128, + 4, 0, 85, 144, 4, 0, + 0, 4, 1, 0, 7, 128, + 1, 0, 228, 128, 4, 0, + 0, 144, 3, 0, 228, 128, + 46, 0, 0, 2, 0, 0, + 1, 176, 0, 0, 170, 128, + 8, 0, 0, 4, 0, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 3, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 3, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 3, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 2, 0, 7, 128, 3, 0, + 228, 128, 4, 0, 170, 144, + 2, 0, 228, 128, 8, 0, + 0, 4, 0, 0, 1, 128, + 1, 0, 228, 144, 41, 32, + 228, 160, 0, 0, 0, 176, + 8, 0, 0, 4, 0, 0, + 2, 128, 1, 0, 228, 144, + 42, 32, 228, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 0, 0, 7, 128, 0, 0, + 228, 128, 4, 0, 170, 144, + 1, 0, 228, 128, 46, 0, + 0, 2, 0, 0, 1, 176, + 0, 0, 255, 128, 8, 0, + 0, 4, 1, 0, 4, 128, + 1, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 3, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 3, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 3, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 2, 0, + 7, 128, 3, 0, 228, 128, + 4, 0, 255, 144, 2, 0, + 228, 128, 8, 0, 0, 4, + 1, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 0, 176, 8, 0, + 0, 4, 1, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 0, 0, + 7, 128, 1, 0, 228, 128, + 4, 0, 255, 144, 0, 0, + 228, 128, 5, 0, 0, 3, + 1, 0, 7, 128, 2, 0, + 85, 128, 5, 0, 228, 160, + 4, 0, 0, 4, 1, 0, + 7, 128, 4, 0, 228, 160, + 2, 0, 0, 128, 1, 0, + 228, 128, 4, 0, 0, 4, + 1, 0, 7, 128, 6, 0, + 228, 160, 2, 0, 170, 128, + 1, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 7, 128, + 1, 0, 228, 128, 7, 0, + 228, 160, 5, 0, 0, 3, + 3, 0, 7, 128, 0, 0, + 85, 128, 9, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 11, 128, 8, 0, 164, 160, + 0, 0, 0, 128, 3, 0, + 164, 128, 4, 0, 0, 4, + 0, 0, 7, 128, 10, 0, + 228, 160, 0, 0, 170, 128, + 0, 0, 244, 128, 1, 0, + 0, 2, 3, 0, 1, 128, + 13, 0, 0, 160, 4, 0, + 0, 4, 3, 0, 7, 128, + 15, 0, 228, 160, 3, 0, + 0, 128, 3, 0, 228, 144, + 1, 0, 0, 2, 4, 0, + 7, 128, 3, 0, 228, 128, + 1, 0, 0, 2, 0, 0, + 8, 128, 11, 0, 85, 160, + 38, 0, 0, 1, 0, 0, + 228, 240, 2, 0, 0, 3, + 1, 0, 8, 128, 0, 0, + 255, 128, 16, 0, 0, 160, + 5, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 0, 160, 46, 0, + 0, 2, 0, 0, 1, 176, + 1, 0, 255, 128, 8, 0, + 0, 4, 1, 0, 8, 128, + 0, 0, 228, 128, 19, 32, + 228, 161, 0, 0, 0, 176, + 11, 0, 0, 3, 1, 0, + 8, 128, 1, 0, 255, 128, + 11, 0, 85, 160, 5, 0, + 0, 4, 5, 0, 7, 128, + 1, 0, 255, 128, 17, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 4, 0, + 7, 128, 5, 0, 228, 128, + 13, 0, 170, 160, 4, 0, + 228, 128, 2, 0, 0, 3, + 0, 0, 8, 128, 0, 0, + 255, 128, 11, 0, 170, 160, + 39, 0, 0, 0, 1, 0, + 0, 2, 3, 0, 7, 128, + 4, 0, 228, 128, 1, 0, + 0, 2, 0, 0, 8, 128, + 11, 0, 85, 160, 38, 0, + 0, 1, 1, 0, 228, 240, + 2, 0, 0, 3, 1, 0, + 8, 128, 0, 0, 255, 128, + 16, 0, 85, 160, 5, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 0, 160, 46, 0, 0, 2, + 0, 0, 1, 176, 1, 0, + 255, 128, 2, 0, 0, 4, + 5, 0, 7, 128, 1, 0, + 228, 128, 18, 32, 228, 161, + 0, 0, 0, 176, 8, 0, + 0, 3, 1, 0, 8, 128, + 5, 0, 228, 128, 5, 0, + 228, 128, 7, 0, 0, 2, + 1, 0, 8, 128, 1, 0, + 255, 128, 5, 0, 0, 3, + 5, 0, 7, 128, 1, 0, + 255, 128, 5, 0, 228, 128, + 8, 0, 0, 3, 2, 0, + 8, 128, 0, 0, 228, 128, + 5, 0, 228, 129, 11, 0, + 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 11, 0, + 85, 160, 5, 0, 0, 4, + 5, 0, 7, 128, 2, 0, + 255, 128, 17, 32, 228, 160, + 0, 0, 0, 176, 6, 0, + 0, 2, 1, 0, 8, 128, + 1, 0, 255, 128, 6, 0, + 0, 3, 2, 0, 8, 128, + 17, 32, 255, 160, 0, 0, + 0, 176, 4, 0, 0, 4, + 1, 0, 8, 128, 1, 0, + 255, 128, 2, 0, 255, 129, + 11, 0, 170, 160, 11, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 85, 160, 5, 0, 0, 3, + 5, 0, 7, 128, 1, 0, + 255, 128, 5, 0, 228, 128, + 4, 0, 0, 4, 3, 0, + 7, 128, 5, 0, 228, 128, + 13, 0, 170, 160, 3, 0, + 228, 128, 2, 0, 0, 3, + 0, 0, 8, 128, 0, 0, + 255, 128, 11, 0, 170, 160, + 39, 0, 0, 0, 1, 0, + 0, 2, 4, 0, 7, 128, + 3, 0, 228, 128, 1, 0, + 0, 2, 0, 0, 8, 128, + 11, 0, 85, 160, 38, 0, + 0, 1, 2, 0, 228, 240, + 2, 0, 0, 3, 1, 0, + 8, 128, 0, 0, 255, 128, + 16, 0, 170, 160, 5, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 11, 0, + 0, 160, 46, 0, 0, 2, + 0, 0, 1, 176, 1, 0, + 255, 128, 2, 0, 0, 4, + 5, 0, 7, 128, 1, 0, + 228, 128, 18, 32, 228, 161, + 0, 0, 0, 176, 8, 0, + 0, 3, 1, 0, 8, 128, + 5, 0, 228, 128, 5, 0, + 228, 128, 7, 0, 0, 2, + 1, 0, 8, 128, 1, 0, + 255, 128, 5, 0, 0, 3, + 5, 0, 7, 128, 1, 0, + 255, 128, 5, 0, 228, 128, + 8, 0, 0, 3, 2, 0, + 8, 128, 0, 0, 228, 128, + 5, 0, 228, 129, 8, 0, + 0, 4, 3, 0, 8, 128, + 5, 0, 228, 128, 19, 32, + 228, 160, 0, 0, 0, 176, + 11, 0, 0, 3, 2, 0, + 8, 128, 2, 0, 255, 128, + 11, 0, 85, 160, 2, 0, + 0, 4, 3, 0, 8, 128, + 3, 0, 255, 128, 18, 32, + 255, 160, 0, 0, 0, 176, + 1, 0, 0, 2, 5, 0, + 4, 128, 11, 0, 170, 160, + 2, 0, 0, 4, 5, 0, + 1, 128, 5, 0, 170, 128, + 18, 32, 255, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 5, 0, 1, 128, 5, 0, + 0, 128, 5, 0, 0, 3, + 3, 0, 8, 128, 3, 0, + 255, 128, 5, 0, 0, 128, + 12, 0, 0, 3, 5, 0, + 1, 128, 3, 0, 255, 128, + 11, 0, 85, 160, 4, 0, + 0, 4, 2, 0, 8, 128, + 5, 0, 0, 128, 2, 0, + 255, 129, 2, 0, 255, 128, + 11, 0, 0, 4, 3, 0, + 8, 128, 3, 0, 255, 128, + 19, 32, 255, 160, 0, 0, + 0, 176, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 3, 0, 255, 128, + 5, 0, 0, 4, 5, 0, + 7, 128, 2, 0, 255, 128, + 17, 32, 228, 160, 0, 0, + 0, 176, 6, 0, 0, 2, + 1, 0, 8, 128, 1, 0, + 255, 128, 6, 0, 0, 3, + 2, 0, 8, 128, 17, 32, + 255, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 1, 0, + 8, 128, 1, 0, 255, 128, + 2, 0, 255, 129, 11, 0, + 170, 160, 11, 0, 0, 3, + 1, 0, 8, 128, 1, 0, + 255, 128, 11, 0, 85, 160, + 5, 0, 0, 3, 5, 0, + 7, 128, 1, 0, 255, 128, + 5, 0, 228, 128, 4, 0, + 0, 4, 4, 0, 7, 128, + 5, 0, 228, 128, 13, 0, + 170, 160, 4, 0, 228, 128, + 2, 0, 0, 3, 0, 0, + 8, 128, 0, 0, 255, 128, + 11, 0, 170, 160, 39, 0, + 0, 0, 1, 0, 0, 2, + 4, 0, 8, 128, 3, 0, + 255, 144, 11, 0, 0, 3, + 0, 0, 15, 128, 4, 0, + 228, 128, 11, 0, 85, 160, + 10, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 11, 0, 170, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 0, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 0, 0, 15, 128, 2, 0, + 85, 128, 1, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 228, 160, + 2, 0, 0, 128, 0, 0, + 228, 128, 4, 0, 0, 4, + 0, 0, 15, 128, 2, 0, + 228, 160, 2, 0, 170, 128, + 0, 0, 228, 128, 2, 0, + 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 3, 0, + 228, 160, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 2, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 255, 128, 14, 0, 85, 161, + 5, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 170, 160, 11, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 0, 128, 14, 0, + 255, 160, 10, 0, 0, 3, + 0, 0, 4, 224, 0, 0, + 0, 128, 11, 0, 170, 160, + 1, 0, 0, 2, 0, 0, + 3, 224, 2, 0, 228, 144, + 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/skin_amb_VS.h b/src/d3d/shaders/skin_amb_VS.h new file mode 100644 index 0000000..e0a5548 --- /dev/null +++ b/src/d3d/shaders/skin_amb_VS.h @@ -0,0 +1,253 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /Fh skin_amb_VS.h skin_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x3 boneMatrices[64]; +// float4x4 combinedMat; +// float4 fogData; +// float4 matCol; +// float4 surfProps; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// combinedMat c0 4 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// boneMatrices c41 192 +// + + vs_2_0 + def c4, 3, 0, 1, 0 + dcl_position v0 + dcl_texcoord v1 + dcl_color v2 + dcl_blendweight v3 + dcl_blendindices v4 + mov r0.xyz, c15 + mad r0.xyz, r0, c13.x, v2 + mov r0.w, v2.w + max r0, r0, c4.y + min r0, r0, c4.z + mul oD0, r0, c12 + mul r0, v4, c4.x + mova a0.x, r0.y + dp4 r1.x, v0, c41[a0.x] + dp4 r1.y, v0, c42[a0.x] + dp4 r1.z, v0, c43[a0.x] + mul r1.xyz, r1, v3.y + mova a0.x, r0.x + dp4 r2.x, v0, c41[a0.x] + dp4 r2.y, v0, c42[a0.x] + dp4 r2.z, v0, c43[a0.x] + mad r1.xyz, r2, v3.x, r1 + mova a0.xy, r0.zwzw + dp4 r0.x, v0, c41[a0.x] + dp4 r0.y, v0, c42[a0.x] + dp4 r0.z, v0, c43[a0.x] + mad r0.xyz, r0, v3.z, r1 + dp4 r1.x, v0, c41[a0.y] + dp4 r1.y, v0, c42[a0.y] + dp4 r1.z, v0, c43[a0.y] + mad r0.xyz, r1, v3.w, r0 + mul r1, r0.y, c1 + mad r1, c0, r0.x, r1 + mad r0, c2, r0.z, r1 + add r0, r0, c3 + add r1.x, r0.w, -c14.y + mov oPos, r0 + mul r0.x, r1.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.z + mov oT0.xy, v1 + +// approximately 36 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 82, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 16, 1, + 0, 0, 0, 2, 254, 255, + 6, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 9, 1, 0, 0, 148, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 164, 0, + 0, 0, 0, 0, 0, 0, + 180, 0, 0, 0, 2, 0, + 41, 0, 192, 0, 166, 0, + 196, 0, 0, 0, 0, 0, + 0, 0, 212, 0, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 224, 0, 0, 0, + 0, 0, 0, 0, 240, 0, + 0, 0, 2, 0, 14, 0, + 1, 0, 58, 0, 164, 0, + 0, 0, 0, 0, 0, 0, + 248, 0, 0, 0, 2, 0, + 12, 0, 1, 0, 50, 0, + 164, 0, 0, 0, 0, 0, + 0, 0, 255, 0, 0, 0, + 2, 0, 13, 0, 1, 0, + 54, 0, 164, 0, 0, 0, + 0, 0, 0, 0, 97, 109, + 98, 105, 101, 110, 116, 76, + 105, 103, 104, 116, 0, 171, + 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 98, 111, 110, 101, 77, 97, + 116, 114, 105, 99, 101, 115, + 0, 171, 171, 171, 3, 0, + 3, 0, 4, 0, 3, 0, + 64, 0, 0, 0, 0, 0, + 0, 0, 99, 111, 109, 98, + 105, 110, 101, 100, 77, 97, + 116, 0, 3, 0, 3, 0, + 4, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 68, 97, 116, + 97, 0, 109, 97, 116, 67, + 111, 108, 0, 115, 117, 114, + 102, 80, 114, 111, 112, 115, + 0, 118, 115, 95, 50, 95, + 48, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, + 32, 57, 46, 50, 57, 46, + 57, 53, 50, 46, 51, 49, + 49, 49, 0, 171, 171, 171, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 64, 64, + 0, 0, 0, 0, 0, 0, + 128, 63, 0, 0, 0, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 144, + 31, 0, 0, 2, 5, 0, + 0, 128, 1, 0, 15, 144, + 31, 0, 0, 2, 10, 0, + 0, 128, 2, 0, 15, 144, + 31, 0, 0, 2, 1, 0, + 0, 128, 3, 0, 15, 144, + 31, 0, 0, 2, 2, 0, + 0, 128, 4, 0, 15, 144, + 1, 0, 0, 2, 0, 0, + 7, 128, 15, 0, 228, 160, + 4, 0, 0, 4, 0, 0, + 7, 128, 0, 0, 228, 128, + 13, 0, 0, 160, 2, 0, + 228, 144, 1, 0, 0, 2, + 0, 0, 8, 128, 2, 0, + 255, 144, 11, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 128, 4, 0, 85, 160, + 10, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 4, 0, 170, 160, 5, 0, + 0, 3, 0, 0, 15, 208, + 0, 0, 228, 128, 12, 0, + 228, 160, 5, 0, 0, 3, + 0, 0, 15, 128, 4, 0, + 228, 144, 4, 0, 0, 160, + 46, 0, 0, 2, 0, 0, + 1, 176, 0, 0, 85, 128, + 9, 0, 0, 4, 1, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 1, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 1, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 0, 176, + 5, 0, 0, 3, 1, 0, + 7, 128, 1, 0, 228, 128, + 3, 0, 85, 144, 46, 0, + 0, 2, 0, 0, 1, 176, + 0, 0, 0, 128, 9, 0, + 0, 4, 2, 0, 1, 128, + 0, 0, 228, 144, 41, 32, + 228, 160, 0, 0, 0, 176, + 9, 0, 0, 4, 2, 0, + 2, 128, 0, 0, 228, 144, + 42, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 2, 0, 4, 128, 0, 0, + 228, 144, 43, 32, 228, 160, + 0, 0, 0, 176, 4, 0, + 0, 4, 1, 0, 7, 128, + 2, 0, 228, 128, 3, 0, + 0, 144, 1, 0, 228, 128, + 46, 0, 0, 2, 0, 0, + 3, 176, 0, 0, 238, 128, + 9, 0, 0, 4, 0, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 0, 176, 9, 0, 0, 4, + 0, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 0, 176, 9, 0, + 0, 4, 0, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 0, 176, + 4, 0, 0, 4, 0, 0, + 7, 128, 0, 0, 228, 128, + 3, 0, 170, 144, 1, 0, + 228, 128, 9, 0, 0, 4, + 1, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 85, 176, 9, 0, + 0, 4, 1, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 85, 176, + 9, 0, 0, 4, 1, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 85, 176, 4, 0, 0, 4, + 0, 0, 7, 128, 1, 0, + 228, 128, 3, 0, 255, 144, + 0, 0, 228, 128, 5, 0, + 0, 3, 1, 0, 15, 128, + 0, 0, 85, 128, 1, 0, + 228, 160, 4, 0, 0, 4, + 1, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 128, + 1, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 128, 1, 0, 228, 128, + 2, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 3, 0, 228, 160, 2, 0, + 0, 3, 1, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 1, 0, 0, 2, + 0, 0, 15, 192, 0, 0, + 228, 128, 5, 0, 0, 3, + 0, 0, 1, 128, 1, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 4, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 1, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/skin_amb_dir_VS.h b/src/d3d/shaders/skin_amb_dir_VS.h new file mode 100644 index 0000000..7949749 --- /dev/null +++ b/src/d3d/shaders/skin_amb_dir_VS.h @@ -0,0 +1,503 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.29.952.3111 +// +// fxc /nologo /T vs_2_0 /DDIRECTIONALS /Fh skin_amb_dir_VS.h skin_VS.hlsl +// +// +// Parameters: +// +// float4 ambientLight; +// float4x3 boneMatrices[64]; +// float4x4 combinedMat; +// int4 firstLight; +// float4 fogData; +// +// struct +// { +// float4 color; +// float4 position; +// float4 direction; +// +// } lights[8]; +// +// float4 matCol; +// float3x3 normalMat; +// int numDirLights; +// float4 surfProps; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// numDirLights i0 1 +// combinedMat c0 4 +// normalMat c8 3 +// matCol c12 1 +// surfProps c13 1 +// fogData c14 1 +// ambientLight c15 1 +// firstLight c16 1 +// lights c17 24 +// boneMatrices c41 192 +// + + vs_2_0 + def c4, 3, 0, 1, 0 + dcl_position v0 + dcl_normal v1 + dcl_texcoord v2 + dcl_color v3 + dcl_blendweight v4 + dcl_blendindices v5 + mul r0, v5, c4.x + mova a0.w, r0.x + dp3 r1.z, v1, c43[a0.w] + mova a0.w, r0.x + dp3 r1.x, v1, c41[a0.w] + mova a0.w, r0.x + dp3 r1.y, v1, c42[a0.w] + mova a0.w, r0.y + dp3 r2.z, v1, c43[a0.w] + mova a0.w, r0.y + dp3 r2.x, v1, c41[a0.w] + mova a0.w, r0.y + dp3 r2.y, v1, c42[a0.w] + mul r2.xyz, r2, v4.y + mad r1.xyz, r1, v4.x, r2 + mova a0.w, r0.z + dp3 r2.z, v1, c43[a0.w] + mova a0.w, r0.z + dp3 r2.x, v1, c41[a0.w] + mova a0.w, r0.z + dp3 r2.y, v1, c42[a0.w] + mad r1.xyz, r2, v4.z, r1 + mova a0.w, r0.w + dp3 r2.z, v1, c43[a0.w] + mova a0.w, r0.w + dp3 r2.x, v1, c41[a0.w] + mova a0.w, r0.w + dp3 r2.y, v1, c42[a0.w] + mad r1.xyz, r2, v4.w, r1 + mul r2.xyz, r1.y, c9 + mad r1.xyw, c8.xyzz, r1.x, r2.xyzz + mad r1.xyz, c10, r1.z, r1.xyww + mov r2.x, c13.x + mad r2.xyz, c15, r2.x, v3 + mov r3.xyz, r2 + mov r1.w, c4.y + rep i0 + add r2.w, r1.w, c16.x + mul r2.w, r2.w, c4.x + mova a0.w, r2.w + dp3 r4.x, r1, -c19[a0.w] + max r4.x, r4.x, c4.y + mova a0.w, r2.w + mul r4.xyz, r4.x, c17[a0.w] + mad r3.xyz, r4, c13.z, r3 + add r1.w, r1.w, c4.z + endrep + mov r3.w, v3.w + max r1, r3, c4.y + min r1, r1, c4.z + mul oD0, r1, c12 + mova a0.w, r0.y + dp4 r1.x, v0, c41[a0.w] + mova a0.w, r0.y + dp4 r1.y, v0, c42[a0.w] + mova a0.w, r0.y + dp4 r1.z, v0, c43[a0.w] + mul r1.xyz, r1, v4.y + mova a0.w, r0.x + dp4 r2.x, v0, c41[a0.w] + mova a0.w, r0.x + dp4 r2.y, v0, c42[a0.w] + mova a0.w, r0.x + dp4 r2.z, v0, c43[a0.w] + mad r1.xyz, r2, v4.x, r1 + mova a0.w, r0.z + dp4 r2.x, v0, c41[a0.w] + mova a0.w, r0.z + dp4 r2.y, v0, c42[a0.w] + mova a0.w, r0.z + dp4 r2.z, v0, c43[a0.w] + mad r0.xyz, r2, v4.z, r1 + mova a0.w, r0.w + dp4 r1.x, v0, c41[a0.w] + mova a0.w, r0.w + dp4 r1.y, v0, c42[a0.w] + mova a0.w, r0.w + dp4 r1.z, v0, c43[a0.w] + mad r0.xyz, r1, v4.w, r0 + mul r1, r0.y, c1 + mad r1, c0, r0.x, r1 + mad r0, c2, r0.z, r1 + add r0, r0, c3 + mov oPos, r0 + add r0.x, r0.w, -c14.y + mul r0.x, r0.x, c14.z + max r0.x, r0.x, c14.w + min oT0.z, r0.x, c4.z + mov oT0.xy, v2 + +// approximately 92 instruction slots used +#endif + +const BYTE g_vs20_main[] = +{ + 0, 2, 254, 255, 254, 255, + 147, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 21, 2, + 0, 0, 0, 2, 254, 255, + 10, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 14, 2, 0, 0, 228, 0, + 0, 0, 2, 0, 15, 0, + 1, 0, 62, 0, 244, 0, + 0, 0, 0, 0, 0, 0, + 4, 1, 0, 0, 2, 0, + 41, 0, 192, 0, 166, 0, + 20, 1, 0, 0, 0, 0, + 0, 0, 36, 1, 0, 0, + 2, 0, 0, 0, 4, 0, + 2, 0, 48, 1, 0, 0, + 0, 0, 0, 0, 64, 1, + 0, 0, 2, 0, 16, 0, + 1, 0, 66, 0, 76, 1, + 0, 0, 0, 0, 0, 0, + 92, 1, 0, 0, 2, 0, + 14, 0, 1, 0, 58, 0, + 244, 0, 0, 0, 0, 0, + 0, 0, 100, 1, 0, 0, + 2, 0, 17, 0, 24, 0, + 70, 0, 176, 1, 0, 0, + 0, 0, 0, 0, 192, 1, + 0, 0, 2, 0, 12, 0, + 1, 0, 50, 0, 244, 0, + 0, 0, 0, 0, 0, 0, + 199, 1, 0, 0, 2, 0, + 8, 0, 3, 0, 34, 0, + 212, 1, 0, 0, 0, 0, + 0, 0, 228, 1, 0, 0, + 1, 0, 0, 0, 1, 0, + 2, 0, 244, 1, 0, 0, + 0, 0, 0, 0, 4, 2, + 0, 0, 2, 0, 13, 0, + 1, 0, 54, 0, 244, 0, + 0, 0, 0, 0, 0, 0, + 97, 109, 98, 105, 101, 110, + 116, 76, 105, 103, 104, 116, + 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 98, 111, 110, 101, + 77, 97, 116, 114, 105, 99, + 101, 115, 0, 171, 171, 171, + 3, 0, 3, 0, 4, 0, + 3, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 99, 111, + 109, 98, 105, 110, 101, 100, + 77, 97, 116, 0, 3, 0, + 3, 0, 4, 0, 4, 0, + 1, 0, 0, 0, 0, 0, + 0, 0, 102, 105, 114, 115, + 116, 76, 105, 103, 104, 116, + 0, 171, 1, 0, 2, 0, + 1, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 102, 111, 103, 68, 97, 116, + 97, 0, 108, 105, 103, 104, + 116, 115, 0, 99, 111, 108, + 111, 114, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 111, + 115, 105, 116, 105, 111, 110, + 0, 100, 105, 114, 101, 99, + 116, 105, 111, 110, 0, 171, + 107, 1, 0, 0, 116, 1, + 0, 0, 132, 1, 0, 0, + 116, 1, 0, 0, 141, 1, + 0, 0, 116, 1, 0, 0, + 5, 0, 0, 0, 1, 0, + 12, 0, 8, 0, 3, 0, + 152, 1, 0, 0, 109, 97, + 116, 67, 111, 108, 0, 110, + 111, 114, 109, 97, 108, 77, + 97, 116, 0, 171, 171, 171, + 3, 0, 3, 0, 3, 0, + 3, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 110, 117, + 109, 68, 105, 114, 76, 105, + 103, 104, 116, 115, 0, 171, + 171, 171, 0, 0, 2, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 115, 117, 114, 102, 80, 114, + 111, 112, 115, 0, 118, 115, + 95, 50, 95, 48, 0, 77, + 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 57, 46, + 50, 57, 46, 57, 53, 50, + 46, 51, 49, 49, 49, 0, + 171, 171, 81, 0, 0, 5, + 4, 0, 15, 160, 0, 0, + 64, 64, 0, 0, 0, 0, + 0, 0, 128, 63, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 15, 144, 31, 0, 0, 2, + 3, 0, 0, 128, 1, 0, + 15, 144, 31, 0, 0, 2, + 5, 0, 0, 128, 2, 0, + 15, 144, 31, 0, 0, 2, + 10, 0, 0, 128, 3, 0, + 15, 144, 31, 0, 0, 2, + 1, 0, 0, 128, 4, 0, + 15, 144, 31, 0, 0, 2, + 2, 0, 0, 128, 5, 0, + 15, 144, 5, 0, 0, 3, + 0, 0, 15, 128, 5, 0, + 228, 144, 4, 0, 0, 160, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 0, 128, + 8, 0, 0, 4, 1, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 0, 128, 8, 0, 0, 4, + 1, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 0, 128, 8, 0, + 0, 4, 1, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 255, 176, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 85, 128, + 8, 0, 0, 4, 2, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 85, 128, 8, 0, 0, 4, + 2, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 85, 128, 8, 0, + 0, 4, 2, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 255, 176, + 5, 0, 0, 3, 2, 0, + 7, 128, 2, 0, 228, 128, + 4, 0, 85, 144, 4, 0, + 0, 4, 1, 0, 7, 128, + 1, 0, 228, 128, 4, 0, + 0, 144, 2, 0, 228, 128, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 170, 128, + 8, 0, 0, 4, 2, 0, + 4, 128, 1, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 170, 128, 8, 0, 0, 4, + 2, 0, 1, 128, 1, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 170, 128, 8, 0, + 0, 4, 2, 0, 2, 128, + 1, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 255, 176, + 4, 0, 0, 4, 1, 0, + 7, 128, 2, 0, 228, 128, + 4, 0, 170, 144, 1, 0, + 228, 128, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 255, 128, 8, 0, 0, 4, + 2, 0, 4, 128, 1, 0, + 228, 144, 43, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 255, 128, 8, 0, + 0, 4, 2, 0, 1, 128, + 1, 0, 228, 144, 41, 32, + 228, 160, 0, 0, 255, 176, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 255, 128, + 8, 0, 0, 4, 2, 0, + 2, 128, 1, 0, 228, 144, + 42, 32, 228, 160, 0, 0, + 255, 176, 4, 0, 0, 4, + 1, 0, 7, 128, 2, 0, + 228, 128, 4, 0, 255, 144, + 1, 0, 228, 128, 5, 0, + 0, 3, 2, 0, 7, 128, + 1, 0, 85, 128, 9, 0, + 228, 160, 4, 0, 0, 4, + 1, 0, 11, 128, 8, 0, + 164, 160, 1, 0, 0, 128, + 2, 0, 164, 128, 4, 0, + 0, 4, 1, 0, 7, 128, + 10, 0, 228, 160, 1, 0, + 170, 128, 1, 0, 244, 128, + 1, 0, 0, 2, 2, 0, + 1, 128, 13, 0, 0, 160, + 4, 0, 0, 4, 2, 0, + 7, 128, 15, 0, 228, 160, + 2, 0, 0, 128, 3, 0, + 228, 144, 1, 0, 0, 2, + 3, 0, 7, 128, 2, 0, + 228, 128, 1, 0, 0, 2, + 1, 0, 8, 128, 4, 0, + 85, 160, 38, 0, 0, 1, + 0, 0, 228, 240, 2, 0, + 0, 3, 2, 0, 8, 128, + 1, 0, 255, 128, 16, 0, + 0, 160, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, + 255, 128, 4, 0, 0, 160, + 46, 0, 0, 2, 0, 0, + 8, 176, 2, 0, 255, 128, + 8, 0, 0, 4, 4, 0, + 1, 128, 1, 0, 228, 128, + 19, 32, 228, 161, 0, 0, + 255, 176, 11, 0, 0, 3, + 4, 0, 1, 128, 4, 0, + 0, 128, 4, 0, 85, 160, + 46, 0, 0, 2, 0, 0, + 8, 176, 2, 0, 255, 128, + 5, 0, 0, 4, 4, 0, + 7, 128, 4, 0, 0, 128, + 17, 32, 228, 160, 0, 0, + 255, 176, 4, 0, 0, 4, + 3, 0, 7, 128, 4, 0, + 228, 128, 13, 0, 170, 160, + 3, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 8, 128, + 1, 0, 255, 128, 4, 0, + 170, 160, 39, 0, 0, 0, + 1, 0, 0, 2, 3, 0, + 8, 128, 3, 0, 255, 144, + 11, 0, 0, 3, 1, 0, + 15, 128, 3, 0, 228, 128, + 4, 0, 85, 160, 10, 0, + 0, 3, 1, 0, 15, 128, + 1, 0, 228, 128, 4, 0, + 170, 160, 5, 0, 0, 3, + 0, 0, 15, 208, 1, 0, + 228, 128, 12, 0, 228, 160, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 85, 128, + 9, 0, 0, 4, 1, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 85, 128, 9, 0, 0, 4, + 1, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 85, 128, 9, 0, + 0, 4, 1, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 255, 176, + 5, 0, 0, 3, 1, 0, + 7, 128, 1, 0, 228, 128, + 4, 0, 85, 144, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 0, 128, 9, 0, + 0, 4, 2, 0, 1, 128, + 0, 0, 228, 144, 41, 32, + 228, 160, 0, 0, 255, 176, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 0, 128, + 9, 0, 0, 4, 2, 0, + 2, 128, 0, 0, 228, 144, + 42, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 0, 128, 9, 0, 0, 4, + 2, 0, 4, 128, 0, 0, + 228, 144, 43, 32, 228, 160, + 0, 0, 255, 176, 4, 0, + 0, 4, 1, 0, 7, 128, + 2, 0, 228, 128, 4, 0, + 0, 144, 1, 0, 228, 128, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 170, 128, + 9, 0, 0, 4, 2, 0, + 1, 128, 0, 0, 228, 144, + 41, 32, 228, 160, 0, 0, + 255, 176, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 170, 128, 9, 0, 0, 4, + 2, 0, 2, 128, 0, 0, + 228, 144, 42, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 170, 128, 9, 0, + 0, 4, 2, 0, 4, 128, + 0, 0, 228, 144, 43, 32, + 228, 160, 0, 0, 255, 176, + 4, 0, 0, 4, 0, 0, + 7, 128, 2, 0, 228, 128, + 4, 0, 170, 144, 1, 0, + 228, 128, 46, 0, 0, 2, + 0, 0, 8, 176, 0, 0, + 255, 128, 9, 0, 0, 4, + 1, 0, 1, 128, 0, 0, + 228, 144, 41, 32, 228, 160, + 0, 0, 255, 176, 46, 0, + 0, 2, 0, 0, 8, 176, + 0, 0, 255, 128, 9, 0, + 0, 4, 1, 0, 2, 128, + 0, 0, 228, 144, 42, 32, + 228, 160, 0, 0, 255, 176, + 46, 0, 0, 2, 0, 0, + 8, 176, 0, 0, 255, 128, + 9, 0, 0, 4, 1, 0, + 4, 128, 0, 0, 228, 144, + 43, 32, 228, 160, 0, 0, + 255, 176, 4, 0, 0, 4, + 0, 0, 7, 128, 1, 0, + 228, 128, 4, 0, 255, 144, + 0, 0, 228, 128, 5, 0, + 0, 3, 1, 0, 15, 128, + 0, 0, 85, 128, 1, 0, + 228, 160, 4, 0, 0, 4, + 1, 0, 15, 128, 0, 0, + 228, 160, 0, 0, 0, 128, + 1, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, + 2, 0, 228, 160, 0, 0, + 170, 128, 1, 0, 228, 128, + 2, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, + 3, 0, 228, 160, 1, 0, + 0, 2, 0, 0, 15, 192, + 0, 0, 228, 128, 2, 0, + 0, 3, 0, 0, 1, 128, + 0, 0, 255, 128, 14, 0, + 85, 161, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 14, 0, 170, 160, + 11, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, + 14, 0, 255, 160, 10, 0, + 0, 3, 0, 0, 4, 224, + 0, 0, 0, 128, 4, 0, + 170, 160, 1, 0, 0, 2, + 0, 0, 3, 224, 2, 0, + 228, 144, 255, 255, 0, 0 +}; diff --git a/src/d3d/shaders/standardConstants.h b/src/d3d/shaders/standardConstants.h new file mode 100644 index 0000000..088df7d --- /dev/null +++ b/src/d3d/shaders/standardConstants.h @@ -0,0 +1,28 @@ +float4x4 combinedMat : register(c0); +float4x4 worldMat : register(c4); +float3x3 normalMat : register(c8); +float4 matCol : register(c12); +float4 surfProps : register(c13); +float4 fogData : register(c14); +float4 ambientLight : register(c15); + +#define surfAmbient (surfProps.x) +#define surfSpecular (surfProps.y) +#define surfDiffuse (surfProps.z) + +#define fogStart (fogData.x) +#define fogEnd (fogData.y) +#define fogRange (fogData.z) +#define fogDisable (fogData.w) + +#include "lighting.h" + +int numDirLights : register(i0); +int numPointLights : register(i1); +int numSpotLights : register(i2); +int4 firstLight : register(c16); +Light lights[8] : register(c17); + +#define firstDirLight (firstLight.x) +#define firstPointLight (firstLight.y) +#define firstSpotLight (firstLight.z) diff --git a/src/d3d/xbox.cpp b/src/d3d/xbox.cpp new file mode 100644 index 0000000..35c065e --- /dev/null +++ b/src/d3d/xbox.cpp @@ -0,0 +1,940 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwxbox.h" + +#include "rwxboximpl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace xbox { + +static void* +driverOpen(void *o, int32, int32) +{ + engine->driver[PLATFORM_XBOX]->defaultPipeline = makeDefaultPipeline(); + + engine->driver[PLATFORM_XBOX]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_XBOX]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_XBOX]->rasterLock = rasterLock; + engine->driver[PLATFORM_XBOX]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_XBOX]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_XBOX]->rasterToImage = rasterToImage; + + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_XBOX, 0, PLATFORM_XBOX, + driverOpen, driverClose); + registerNativeRaster(); +} + +void* +destroyNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_XBOX) + return object; + InstanceDataHeader *header = + (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + rwFree(header->vertexBuffer); + rwFree(header->begin); + rwFree(header->data); + rwFree(header); + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 vers; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, &vers)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_XBOX){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + if(vers < 0x35000){ + RWERROR((ERR_VERSION, vers)); + return nil; + } + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_XBOX; + + int32 size = stream->readI32(); + // The 0x18 byte are the resentryheader. + // We don't have it but it's used for alignment. + header->data = rwNewT(uint8, size + 0x18, MEMDUR_EVENT | ID_GEOMETRY); + uint8 *p = header->data+0x18+4; + stream->read8(p, size-4); + + header->size = size; + header->serialNumber = *(uint16*)p; p += 2; + header->numMeshes = *(uint16*)p; p += 2; + header->primType = *(uint32*)p; p += 4; + header->numVertices = *(uint32*)p; p += 4; + header->stride = *(uint32*)p; p += 4; + // RxXboxVertexFormat in 3.3 here + p += 4; // skip vertexBuffer pointer + header->vertexAlpha = *(bool32*)p; p += 4; + p += 8; // skip begin, end pointers + + InstanceData *inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + header->begin = inst; + for(int i = 0; i < header->numMeshes; i++){ + inst->minVert = *(uint32*)p; p += 4; + inst->numVertices = *(int32*)p; p += 4; + inst->numIndices = *(int32*)p; p += 4; + inst->indexBuffer = header->data + *(uint32*)p; p += 4; + p += 8; // skip material and vertexShader + inst->vertexShader = 0; + // pixelShader in 3.3 here + inst++; + } + header->end = inst; + + header->vertexBuffer = rwNewT(uint8, header->stride*header->numVertices, MEMDUR_EVENT | ID_GEOMETRY); + stream->read8(header->vertexBuffer, header->stride*header->numVertices); + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_STRUCT, len-12); + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_XBOX) + return stream; + stream->writeU32(PLATFORM_XBOX); + assert(rw::version >= 0x35000 && "can't write native Xbox data < 0x35000"); + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + + // we just fill header->data and write that + uint8 *p = header->data+0x18; + *(int32*)p = header->size; p += 4; + *(uint16*)p = header->serialNumber; p += 2; + *(uint16*)p = header->numMeshes; p += 2; + *(uint32*)p = header->primType; p += 4; + *(uint32*)p = header->numVertices; p += 4; + *(uint32*)p = header->stride; p += 4; + // RxXboxVertexFormat in 3.3 here + p += 4; // skip vertexBuffer pointer + *(bool32*)p = header->vertexAlpha; p += 4; + p += 8; // skip begin, end pointers + + InstanceData *inst = header->begin; + for(int i = 0; i < header->numMeshes; i++){ + *(uint32*)p = inst->minVert; p += 4; + *(int32*)p = inst->numVertices; p += 4; + *(int32*)p = inst->numIndices; p += 4; + *(uint32*)p = (uint8*)inst->indexBuffer - header->data; p += 4; + p += 8; // skip material and vertexShader + // pixelShader in 3.3 here + inst++; + } + + stream->write8(header->data+0x18, header->size); + stream->write8(header->vertexBuffer, header->stride*header->numVertices); + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_XBOX) + return 0; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + return 12 + 4 + header->size + header->stride*header->numVertices; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +enum { + D3DPT_TRIANGLELIST = 5, + D3DPT_TRIANGLESTRIP = 6 +}; + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE (or not, xbox can't render) + if(geo->instData) + return; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + MeshHeader *meshh = geo->meshHeader; + geo->instData = header; + header->platform = PLATFORM_XBOX; + + header->size = 0x24 + meshh->numMeshes*0x18 + 0x10; + Mesh *mesh = meshh->getMeshes(); + for(uint32 i = 0; i < meshh->numMeshes; i++) + header->size += (mesh++->numIndices*2 + 0xF) & ~0xF; + // The 0x18 byte are the resentryheader. + // We don't have it but it's used for alignment. + header->data = rwNewT(uint8, header->size + 0x18, MEMDUR_EVENT | ID_GEOMETRY); + header->serialNumber = meshh->serialNum; + header->numMeshes = meshh->numMeshes; + header->primType = meshh->flags == MeshHeader::TRISTRIP ? + D3DPT_TRIANGLESTRIP : D3DPT_TRIANGLELIST; + header->numVertices = geo->numVertices; + header->vertexAlpha = 0; + // set by the instanceCB + header->stride = 0; + header->vertexBuffer = nil; + + InstanceData *inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + header->begin = inst; + mesh = meshh->getMeshes(); + uint8 *indexbuf = (uint8*)header->data + ((0x18 + 0x24 + header->numMeshes*0x18 + 0xF)&~0xF); + for(uint32 i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &inst->minVert, &inst->numVertices); + inst->numIndices = mesh->numIndices; + inst->indexBuffer = indexbuf; + memcpy(inst->indexBuffer, mesh->indices, inst->numIndices*sizeof(uint16)); + indexbuf += (inst->numIndices*2 + 0xF) & ~0xF; + inst->material = mesh->material; + inst->vertexShader = 0; // TODO? + mesh++; + inst++; + } + header->end = inst; + + pipe->instanceCB(geo, header); +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_XBOX); + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + InstanceData *inst = header->begin; + Mesh *mesh = geo->meshHeader->getMeshes(); + // For some reason numIndices in mesh and instance data are not always equal + // And primitive isn't always correct either. Maybe some internal conversion... + geo->meshHeader->totalIndices = 0; + for(uint32 i = 0; i < header->numMeshes; i++){ + mesh[i].numIndices = inst[i].numIndices; + geo->meshHeader->totalIndices += mesh[i].numIndices; + } + geo->meshHeader->flags = header->primType == D3DPT_TRIANGLESTRIP ? + MeshHeader::TRISTRIP : 0; + + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + geo->allocateMeshes(geo->meshHeader->numMeshes, geo->meshHeader->totalIndices, 0); + + mesh = geo->meshHeader->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + uint16 *indices = (uint16*)inst->indexBuffer; + memcpy(mesh->indices, indices, inst->numIndices*2); + mesh++; + inst++; + } + + pipe->uninstanceCB(geo, header); + geo->generateTriangles(); + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_XBOX); + this->impl.instance = xbox::instance; + this->impl.uninstance = xbox::uninstance; + this->instanceCB = nil; + this->uninstanceCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +int v3dFormatMap[] = { + -1, VERT_BYTE3, VERT_SHORT3, VERT_NORMSHORT3, VERT_COMPNORM, VERT_FLOAT3 +}; + +int v2dFormatMap[] = { + -1, VERT_BYTE2, VERT_SHORT2, VERT_NORMSHORT2, VERT_COMPNORM, VERT_FLOAT2 +}; + +void +defaultInstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + uint32 *vertexFmt = getVertexFmt(geo); + if(*vertexFmt == 0) + *vertexFmt = makeVertexFmt(geo->flags, geo->numTexCoordSets); + header->stride = getVertexFmtStride(*vertexFmt); + header->vertexBuffer = rwNewT(uint8, header->stride*header->numVertices, MEMDUR_EVENT | ID_GEOMETRY); + uint8 *dst = (uint8*)header->vertexBuffer; + + uint32 fmt = *vertexFmt; + uint32 sel = fmt & 0xF; + instV3d(v3dFormatMap[sel], dst, geo->morphTargets[0].vertices, + header->numVertices, header->stride); + dst += sel == 4 ? 4 : 3*vertexFormatSizes[sel]; + + sel = (fmt >> 4) & 0xF; + if(sel){ + instV3d(v3dFormatMap[sel], dst, geo->morphTargets[0].normals, + header->numVertices, header->stride); + dst += sel == 4 ? 4 : 3*vertexFormatSizes[sel]; + } + + if(fmt & 0x1000000){ + header->vertexAlpha = instColor(VERT_ARGB, dst, geo->colors, + header->numVertices, header->stride); + dst += 4; + } + + for(int i = 0; i < 4; i++){ + sel = (fmt >> (i*4 + 8)) & 0xF; + if(sel == 0) + break; + instTexCoords(v2dFormatMap[sel], dst, geo->texCoords[i], + header->numVertices, header->stride); + dst += sel == 4 ? 4 : 2*vertexFormatSizes[sel]; + } + + if(fmt & 0xE000000) + assert(0 && "can't instance tangents or whatever it is"); +} + +void +defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + uint32 *vertexFmt = getVertexFmt(geo); + uint32 fmt = *vertexFmt; + assert(fmt != 0); + uint8 *src = (uint8*)header->vertexBuffer; + + uint32 sel = fmt & 0xF; + uninstV3d(v3dFormatMap[sel], geo->morphTargets[0].vertices, src, + header->numVertices, header->stride); + src += sel == 4 ? 4 : 3*vertexFormatSizes[sel]; + + sel = (fmt >> 4) & 0xF; + if(sel){ + uninstV3d(v3dFormatMap[sel], geo->morphTargets[0].normals, src, + header->numVertices, header->stride); + src += sel == 4 ? 4 : 3*vertexFormatSizes[sel]; + } + + if(fmt & 0x1000000){ + uninstColor(VERT_ARGB, geo->colors, src, + header->numVertices, header->stride); + src += 4; + } + + for(int i = 0; i < 4; i++){ + sel = (fmt >> (i*4 + 8)) & 0xF; + if(sel == 0) + break; + uninstTexCoords(v2dFormatMap[sel], geo->texCoords[i], src, + header->numVertices, header->stride); + src += sel == 4 ? 4 : 2*vertexFormatSizes[sel]; + } +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + return pipe; +} + +// Native Texture and Raster + +int32 nativeRasterOffset; + +static uint32 +calculateTextureSize(uint32 width, uint32 height, uint32 depth, uint32 format) +{ +#define D3DFMT_W11V11U10 65 + switch(format){ + default: + case D3DFMT_UNKNOWN: + return 0; + case D3DFMT_A8: + case D3DFMT_P8: + case D3DFMT_L8: + case D3DFMT_AL8: + case D3DFMT_LIN_A8: + case D3DFMT_LIN_AL8: + case D3DFMT_LIN_L8: + return width * height * depth; + case D3DFMT_R5G6B5: + case D3DFMT_R6G5B5: + case D3DFMT_X1R5G5B5: + case D3DFMT_A1R5G5B5: + case D3DFMT_A4R4G4B4: + case D3DFMT_R4G4B4A4: + case D3DFMT_R5G5B5A1: + case D3DFMT_R8B8: + case D3DFMT_G8B8: + case D3DFMT_A8L8: + case D3DFMT_L16: + //case D3DFMT_V8U8: + //case D3DFMT_L6V5U5: + case D3DFMT_D16_LOCKABLE: + //case D3DFMT_D16: + case D3DFMT_F16: + case D3DFMT_YUY2: + case D3DFMT_UYVY: + case D3DFMT_LIN_A1R5G5B5: + case D3DFMT_LIN_A4R4G4B4: + case D3DFMT_LIN_G8B8: + case D3DFMT_LIN_R4G4B4A4: + case D3DFMT_LIN_R5G5B5A1: + case D3DFMT_LIN_R5G6B5: + case D3DFMT_LIN_R6G5B5: + case D3DFMT_LIN_R8B8: + case D3DFMT_LIN_X1R5G5B5: + case D3DFMT_LIN_A8L8: + case D3DFMT_LIN_L16: + //case D3DFMT_LIN_V8U8: + //case D3DFMT_LIN_L6V5U5: + case D3DFMT_LIN_D16: + case D3DFMT_LIN_F16: + return width * 2 * height * depth; + case D3DFMT_A8R8G8B8: + case D3DFMT_X8R8G8B8: + case D3DFMT_A8B8G8R8: + case D3DFMT_B8G8R8A8: + case D3DFMT_R8G8B8A8: + //case D3DFMT_X8L8V8U8: + //case D3DFMT_Q8W8V8U8: + case D3DFMT_V16U16: + case D3DFMT_D24S8: + case D3DFMT_F24S8: + case D3DFMT_LIN_A8B8G8R8: + case D3DFMT_LIN_A8R8G8B8: + case D3DFMT_LIN_B8G8R8A8: + case D3DFMT_LIN_R8G8B8A8: + case D3DFMT_LIN_X8R8G8B8: + case D3DFMT_LIN_V16U16: + //case D3DFMT_LIN_X8L8V8U8: + //case D3DFMT_LIN_Q8W8V8U8: + case D3DFMT_LIN_D24S8: + case D3DFMT_LIN_F24S8: + return width * 4 * height * depth; + case D3DFMT_DXT1: + assert(depth <= 1); + return ((width + 3) >> 2) * ((height + 3) >> 2) * 8; + //case D3DFMT_DXT2: + case D3DFMT_DXT3: + //case D3DFMT_DXT4: + case D3DFMT_DXT5: + assert(depth <= 1); + return ((width + 3) >> 2) * ((height + 3) >> 2) * 16; + } +} + +static void* +createTexture(int32 width, int32 height, int32 numlevels, uint32 format) +{ + int32 w = width; + int32 h = height; + int32 size = 0; + for(int32 i = 0; i < numlevels; i++){ + size += calculateTextureSize(w, h, 1, format); + w /= 2; + if(w == 0) w = 1; + h /= 2; + if(h == 0) h = 1; + } + size = (size+3)&~3; + uint8 *data = (uint8*)rwNew(sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(numlevels-1)+size, + MEMDUR_EVENT | ID_DRIVER); + RasterLevels *levels = (RasterLevels*)data; + data += sizeof(RasterLevels)+sizeof(RasterLevels::Level)*(numlevels-1); + levels->numlevels = numlevels; + levels->format = format; + w = width; + h = height; + for(int32 i = 0; i < numlevels; i++){ + levels->levels[i].width = w; + levels->levels[i].height = h; + levels->levels[i].data = data; + levels->levels[i].size = calculateTextureSize(w, h, 1, format); + data += levels->levels[i].size; + w /= 2; + if(w == 0) w = 1; + h /= 2; + if(h == 0) h = 1; + } + return levels; +} + +Raster* +rasterCreate(Raster *raster) +{ + static uint32 formatMap[] = { + D3DFMT_UNKNOWN, + D3DFMT_A1R5G5B5, + D3DFMT_R5G6B5, + D3DFMT_A4R4G4B4, + D3DFMT_L8, + D3DFMT_A8R8G8B8, + D3DFMT_X8R8G8B8, + D3DFMT_UNKNOWN, + D3DFMT_UNKNOWN, + D3DFMT_UNKNOWN, + D3DFMT_X1R5G5B5, + D3DFMT_UNKNOWN, + D3DFMT_UNKNOWN, + D3DFMT_UNKNOWN, + D3DFMT_UNKNOWN, + D3DFMT_UNKNOWN + }; + static bool32 alphaMap[] = { + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 0, 0, 0, + 0, + 0, 0, 0, 0, 0 + }; + + XboxRaster *natras = PLUGINOFFSET(XboxRaster, raster, nativeRasterOffset); + uint32 format; + int32 levels; + + // Dummy to use as subraster + if(raster->width == 0 || raster->height == 0){ + raster->flags |= Raster::DONTALLOCATE; + raster->stride = 0; + return raster; + } + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + if(raster->flags & Raster::DONTALLOCATE) + return raster; + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + format = D3DFMT_P8; + natras->palette = (uint8*)rwNew(4*256, MEMDUR_EVENT | ID_DRIVER); + }else + format = formatMap[(raster->format >> 8) & 0xF]; + natras->format = 0; + natras->hasAlpha = alphaMap[(raster->format >> 8) & 0xF]; + levels = Raster::calculateNumLevels(raster->width, raster->height); + natras->texture = createTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? levels : 1, + format); + if(natras->texture == nil){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + return raster; + } + // unsupported + return nil; +} + +uint8* +rasterLock(Raster *raster, int32 level, int32 lockMode) +{ + // TODO? + (void)lockMode; + + XboxRaster *natras = PLUGINOFFSET(XboxRaster, raster, nativeRasterOffset); + RasterLevels *levels = (RasterLevels*)natras->texture; + return levels->levels[level].data; +} + +void +rasterUnlock(Raster*, int32) +{ +} + +int32 +rasterNumLevels(Raster *raster) +{ + XboxRaster *natras = PLUGINOFFSET(XboxRaster, raster, nativeRasterOffset); + RasterLevels *levels = (RasterLevels*)natras->texture; + return levels->numlevels; +} + +static void +unswizzle(uint8 *dst, uint8 *src, int32 w, int32 h, int32 bpp) +{ + uint32 maskU = 0; + uint32 maskV = 0; + int32 i = 1; + int32 j = 1; + int32 c; + do{ + c = 0; + if(i < w){ + maskU |= j; + j <<= 1; + c = j; + } + if(i < h){ + maskV |= j; + j <<= 1; + c = j; + } + i <<= 1; + }while(c); + int32 x, y, u, v; + v = 0; + for(y = 0; y < h; y++){ + u = 0; + for(x = 0; x < w; x++){ + memcpy(&dst[(y*w + x)*bpp], &src[(u|v)*bpp], bpp); + u = (u - maskU) & maskU; + } + v = (v - maskV) & maskV; + } +} + +Image* +rasterToImage(Raster *raster) +{ + int32 depth; + Image *image; + XboxRaster *natras = PLUGINOFFSET(XboxRaster, raster, nativeRasterOffset); + + if(natras->format){ + image = Image::create(raster->width, raster->height, 32); + image->allocate(); + uint8 *pix = raster->lock(0, Raster::LOCKREAD); + switch(natras->format){ + case D3DFMT_DXT1: + image->setPixelsDXT(1, pix); + // TODO: is this correct? + if(!natras->hasAlpha) + image->removeMask(); + break; + case D3DFMT_DXT3: + image->setPixelsDXT(3, pix); + break; + case D3DFMT_DXT5: + image->setPixelsDXT(5, pix); + break; + default: + assert(0 && "unknown format"); + raster->unlock(0); + image->destroy(); + return nil; + } + raster->unlock(0); + return image; + } + + switch(raster->format & 0xF00){ + case Raster::C1555: + depth = 16; + break; + case Raster::C8888: + depth = 32; + break; + case Raster::C888: + depth = 24; + break; + case Raster::C555: + depth = 16; + break; + + default: + case Raster::C565: + case Raster::C4444: + case Raster::LUM8: + assert(0 && "unsupported raster format"); + } + int32 pallength = 0; + if((raster->format & Raster::PAL4) == Raster::PAL4){ + depth = 4; + pallength = 16; + }else if((raster->format & Raster::PAL8) == Raster::PAL8){ + depth = 8; + pallength = 256; + } + + uint8 *in, *out; + image = Image::create(raster->width, raster->height, depth); + image->allocate(); + + if(pallength){ + out = image->palette; + in = (uint8*)natras->palette; + for(int32 i = 0; i < pallength; i++){ + out[0] = in[2]; + out[1] = in[1]; + out[2] = in[0]; + out[3] = in[3]; + in += 4; + out += 4; + } + } + + out = image->pixels; + in = raster->lock(0, Raster::LOCKREAD); + + unswizzle(out, in, image->width, image->height, depth < 8 ? 1 : depth/8); + // Fix RGB order + // TODO: stride + uint8 tmp; + if(depth > 8) + for(int32 y = 0; y < image->height; y++) + for(int32 x = 0; x < image->width; x++) + switch(raster->format & 0xF00){ + case Raster::C8888: + tmp = out[0]; + out[0] = out[2]; + out[2] = tmp; + out += 4; + break; + case Raster::C888: + tmp = out[0]; + out[0] = out[2]; + out[2] = tmp; + out += 3; + break; + } + raster->unlock(0); + + return image; +} + +int32 +getLevelSize(Raster *raster, int32 level) +{ + XboxRaster *ras = PLUGINOFFSET(XboxRaster, raster, nativeRasterOffset); + RasterLevels *levels = (RasterLevels*)ras->texture; + return levels->levels[level].size; +} + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + XboxRaster *raster = PLUGINOFFSET(XboxRaster, object, offset); + raster->texture = nil; + raster->palette = nil; + raster->format = 0; + raster->hasAlpha = 0; + raster->unknownFlag = 0; + return object; +} + +static void* +destroyNativeRaster(void *object, int32, int32) +{ + // TODO: + return object; +} + +static void* +copyNativeRaster(void *dst, void *, int32 offset, int32) +{ + XboxRaster *raster = PLUGINOFFSET(XboxRaster, dst, offset); + raster->texture = nil; + raster->palette = nil; + raster->format = 0; + raster->hasAlpha = 0; + raster->unknownFlag = 0; + return dst; +} + +void +registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(XboxRaster), + ID_RASTERXBOX, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); +} + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 vers, platform; + if(!findChunk(stream, ID_STRUCT, nil, &vers)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_XBOX){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + if(version < 0x34001){ + RWERROR((ERR_VERSION, version)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + + // Texture + tex->filterAddressing = stream->readU32(); + stream->read8(tex->name, 32); + stream->read8(tex->mask, 32); + + // Raster + int32 format = stream->readI32(); + bool32 hasAlpha = stream->readI16(); + bool32 unknownFlag = stream->readI16(); + int32 width = stream->readU16(); + int32 height = stream->readU16(); + int32 depth = stream->readU8(); + int32 numLevels = stream->readU8(); + int32 type = stream->readU8(); + int32 compression = stream->readU8(); + int32 totalSize = stream->readI32(); + + // isn't this the cube map flag? + assert(unknownFlag == 0); + Raster *raster; + if(compression){ + raster = Raster::create(width, height, depth, format | type | Raster::DONTALLOCATE, PLATFORM_XBOX); + XboxRaster *ras = PLUGINOFFSET(XboxRaster, raster, nativeRasterOffset); + ras->format = compression; + ras->hasAlpha = hasAlpha; + ras->texture = createTexture(raster->width, raster->height, + raster->format & Raster::MIPMAP ? numLevels : 1, + ras->format); + raster->flags &= ~Raster::DONTALLOCATE; + }else + raster = Raster::create(width, height, depth, format | type, PLATFORM_XBOX); + XboxRaster *ras = PLUGINOFFSET(XboxRaster, raster, nativeRasterOffset); + tex->raster = raster; + + if(raster->format & Raster::PAL4) + stream->read8(ras->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->read8(ras->palette, 4*256); + + // exploit the fact that mipmaps are allocated consecutively + uint8 *data = raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + stream->read8(data, totalSize); + raster->unlock(0); + + return tex; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + int32 chunksize = getSizeNativeTexture(tex); + writeChunkHeader(stream, ID_STRUCT, chunksize-12); + stream->writeU32(PLATFORM_XBOX); + + // Texture + stream->writeU32(tex->filterAddressing); + stream->write8(tex->name, 32); + stream->write8(tex->mask, 32); + + // Raster + Raster *raster = tex->raster; + XboxRaster *ras = PLUGINOFFSET(XboxRaster, raster, nativeRasterOffset); + int32 numLevels = raster->getNumLevels(); + stream->writeI32(raster->format); + stream->writeI16(ras->hasAlpha); + stream->writeI16(ras->unknownFlag); + stream->writeU16(raster->width); + stream->writeU16(raster->height); + stream->writeU8(raster->depth); + stream->writeU8(numLevels); + stream->writeU8(raster->type); + stream->writeU8(ras->format); + + int32 totalSize = 0; + for(int32 i = 0; i < numLevels; i++) + totalSize += getLevelSize(tex->raster, i); + totalSize = (totalSize+3)&~3; + stream->writeI32(totalSize); + + if(raster->format & Raster::PAL4) + stream->write8(ras->palette, 4*32); + else if(raster->format & Raster::PAL8) + stream->write8(ras->palette, 4*256); + + // exploit the fact that mipmaps are allocated consecutively + uint8 *data = raster->lock(0, Raster::LOCKREAD); + stream->write8(data, totalSize); + raster->unlock(0); +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 72 + 16 + 4; + int32 levels = tex->raster->getNumLevels(); + for(int32 i = 0; i < levels; i++) + size += getLevelSize(tex->raster, i); + size = (size+3)&~3; + if(tex->raster->format & Raster::PAL4) + size += 4*32; + else if(tex->raster->format & Raster::PAL8) + size += 4*256; + return size; +} + +} +} diff --git a/src/d3d/xboxmatfx.cpp b/src/d3d/xboxmatfx.cpp new file mode 100644 index 0000000..a6b8718 --- /dev/null +++ b/src/d3d/xboxmatfx.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwxbox.h" + +namespace rw { +namespace xbox { + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_XBOX] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_XBOX])->destroy(); + matFXGlobals.pipelines[PLATFORM_XBOX] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_XBOX, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +} +} diff --git a/src/d3d/xboxskin.cpp b/src/d3d/xboxskin.cpp new file mode 100644 index 0000000..0ce5cba --- /dev/null +++ b/src/d3d/xboxskin.cpp @@ -0,0 +1,252 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwxbox.h" + +#define PLUGIN_ID ID_SKIN + +namespace rw { +namespace xbox { + +struct NativeSkin +{ + int32 table1[256]; // maps indices to bones + int32 table2[256]; // maps bones to indices + int32 numUsedBones; + void *vertexBuffer; + int32 stride; +}; + +Stream* +readNativeSkin(Stream *stream, int32, void *object, int32 offset) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 vers, platform; + if(!findChunk(stream, ID_STRUCT, nil, &vers)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_XBOX){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + if(vers < 0x35000){ + RWERROR((ERR_VERSION, vers)); + return nil; + } + + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, offset) = skin; + + int32 numBones = stream->readI32(); + skin->init(numBones, 0, 0); + NativeSkin *natskin = rwNewT(NativeSkin, 1, MEMDUR_EVENT | ID_SKIN); + skin->platformData = natskin; + stream->read32(natskin->table1, 256*sizeof(int32)); + stream->read32(natskin->table2, 256*sizeof(int32)); + natskin->numUsedBones = stream->readI32(); + skin->numWeights = stream->readI32(); + stream->seek(4); // skip pointer to vertexBuffer + natskin->stride = stream->readI32(); + int32 size = geometry->numVertices*natskin->stride; + natskin->vertexBuffer = rwNewT(uint8, size, MEMDUR_EVENT | ID_SKIN); + stream->read8(natskin->vertexBuffer, size); + stream->read32(skin->inverseMatrices, skin->numBones*64); + + readSkinSplitData(stream, skin); + return stream; +} + +Stream* +writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + assert(skin->platformData); + assert(rw::version >= 0x35000 && "can't handle native xbox skin < 0x35000"); + NativeSkin *natskin = (NativeSkin*)skin->platformData; + + writeChunkHeader(stream, ID_STRUCT, len-12); + stream->writeU32(PLATFORM_XBOX); + stream->writeI32(skin->numBones); + stream->write32(natskin->table1, 256*sizeof(int32)); + stream->write32(natskin->table2, 256*sizeof(int32)); + stream->writeI32(natskin->numUsedBones); + stream->writeI32(skin->numWeights); + stream->writeU32(0xBADEAFFE); // pointer to vertexBuffer + stream->writeI32(natskin->stride); + stream->write8(natskin->vertexBuffer, + geometry->numVertices*natskin->stride); + stream->write32(skin->inverseMatrices, skin->numBones*64); + + writeSkinSplitData(stream, skin); + return stream; +} + +int32 +getSizeNativeSkin(void *object, int32 offset) +{ + Geometry *geometry = (Geometry*)object; + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin == nil) + return -1; + if(skin->platformData == nil) + return -1; + NativeSkin *natskin = (NativeSkin*)skin->platformData; + return 12 + 8 + 2*256*4 + 4*4 + + natskin->stride*geometry->numVertices + skin->numBones*64 + + skinSplitDataSize(skin); +} + +void +skinInstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + defaultInstanceCB(geo, header); + + Skin *skin = Skin::get(geo); + if(skin == nil) + return; + NativeSkin *natskin = rwNewT(NativeSkin, 1, MEMDUR_EVENT | ID_SKIN); + skin->platformData = natskin; + + natskin->numUsedBones = skin->numUsedBones; + memset(natskin->table1, 0xFF, sizeof(natskin->table1)); + memset(natskin->table2, 0x00, sizeof(natskin->table2)); + for(int32 i = 0; i < skin->numUsedBones; i++){ + natskin->table1[i] = skin->usedBones[i]; + natskin->table2[skin->usedBones[i]] = i; + } + + natskin->stride = 3*skin->numWeights; + uint8 *vbuf = rwNewT(uint8, header->numVertices*natskin->stride, MEMDUR_EVENT | ID_SKIN); + natskin->vertexBuffer = vbuf; + + int32 w[4]; + int sum; + float32 *weights = skin->weights; + uint8 *p = vbuf; + int32 numVertices = header->numVertices; + while(numVertices--){ + sum = 0; + for(int i = 1; i < skin->numWeights; i++){ + w[i] = (int32)(weights[i]*255.0f + 0.5f); + sum += w[i]; + } + w[0] = 255 - sum; + for(int i = 0; i < skin->numWeights; i++) + p[i] = w[i]; + p += natskin->stride; + weights += 4; + } + + numVertices = header->numVertices; + p = vbuf + skin->numWeights; + uint8 *indices = skin->indices; + uint16 *idx; + while(numVertices--){ + idx = (uint16*)p; + for(int i = 0; i < skin->numWeights; i++) + idx[i] = 3*natskin->table2[indices[i]]; + p += natskin->stride; + indices += 4; + } +} + +void +skinUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + defaultUninstanceCB(geo, header); + + Skin *skin = Skin::get(geo); + if(skin == nil) + return; + NativeSkin *natskin = (NativeSkin*)skin->platformData; + + uint8 *data = skin->data; + float *invMats = skin->inverseMatrices; + skin->init(skin->numBones, natskin->numUsedBones, geo->numVertices); + memcpy(skin->inverseMatrices, invMats, skin->numBones*64); + rwFree(data); + + for(int32 j = 0; j < skin->numUsedBones; j++) + skin->usedBones[j] = natskin->table1[j]; + + float *weights = skin->weights; + uint8 *indices = skin->indices; + uint8 *p = (uint8*)natskin->vertexBuffer; + int32 numVertices = header->numVertices; + float w[4]; + uint8 i[4]; + uint16 *ip; + while(numVertices--){ + w[0] = w[1] = w[2] = w[3] = 0.0f; + i[0] = i[1] = i[2] = i[3] = 0; + + for(int32 j = 0; j < skin->numWeights; j++) + w[j] = *p++/255.0f; + + ip = (uint16*)p; + for(int32 j = 0; j < skin->numWeights; j++){ + i[j] = natskin->table1[*ip++/3]; + if(w[j] == 0.0f) i[j] = 0; // clean up a bit + } + p = (uint8*)ip; + + for(int32 j = 0; j < 4; j++){ + *weights++ = w[j]; + *indices++ = i[j]; + } + } + + rwFree(natskin->vertexBuffer); + rwFree(natskin); +} + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_XBOX] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_XBOX])->destroy(); + skinGlobals.pipelines[PLATFORM_XBOX] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_XBOX, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = skinUninstanceCB; + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + return pipe; +} + +} +} diff --git a/src/d3d/xboxvfmt.cpp b/src/d3d/xboxvfmt.cpp new file mode 100644 index 0000000..b6569dc --- /dev/null +++ b/src/d3d/xboxvfmt.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwxbox.h" + +namespace rw { +namespace xbox { + +static int32 vertexFmtOffset; + +uint32 vertexFormatSizes[6] = { + 0, 1, 2, 2, 4, 4 +}; + +uint32* +getVertexFmt(Geometry *g) +{ + return PLUGINOFFSET(uint32, g, vertexFmtOffset); +} + +uint32 +makeVertexFmt(int32 flags, uint32 numTexSets) +{ + if(numTexSets > 4) + numTexSets = 4; + uint32 fmt = 0x5; // FLOAT3 + if(flags & Geometry::NORMALS) + fmt |= 0x40; // NORMPACKED3 + for(uint32 i = 0; i < numTexSets; i++) + fmt |= 0x500 << i*4; // FLOAT2 + if(flags & Geometry::PRELIT) + fmt |= 0x1000000; // D3DCOLOR + return fmt; +} + +uint32 +getVertexFmtStride(uint32 fmt) +{ + uint32 stride = 0; + uint32 v = fmt & 0xF; + uint32 n = (fmt >> 4) & 0xF; + stride += v == 4 ? 4 : 3*vertexFormatSizes[v]; + stride += n == 4 ? 4 : 3*vertexFormatSizes[n]; + if(fmt & 0x1000000) + stride += 4; + for(int i = 0; i < 4; i++){ + uint32 t = (fmt >> (i*4 + 8)) & 0xF; + stride += t == 4 ? 4 : 2*vertexFormatSizes[t]; + } + if(fmt & 0xE000000) + stride += 8; + return stride; +} + +static void* +createVertexFmt(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(uint32, object, offset) = 0; + return object; +} + +static void* +copyVertexFmt(void *dst, void *src, int32 offset, int32) +{ + *PLUGINOFFSET(uint32, dst, offset) = *PLUGINOFFSET(uint32, src, offset); + return dst; +} + +static Stream* +readVertexFmt(Stream *stream, int32, void *object, int32 offset, int32) +{ + uint32 fmt = stream->readU32(); + *PLUGINOFFSET(uint32, object, offset) = fmt; + // TODO: ? create and attach "vertex shader" + return stream; +} + +static Stream* +writeVertexFmt(Stream *stream, int32, void *object, int32 offset, int32) +{ + stream->writeI32(*PLUGINOFFSET(uint32, object, offset)); + return stream; +} + +static int32 +getSizeVertexFmt(void*, int32, int32) +{ + if(rw::platform != PLATFORM_XBOX) + return -1; + return 4; +} + +void +registerVertexFormatPlugin(void) +{ + vertexFmtOffset = Geometry::registerPlugin(sizeof(uint32), ID_VERTEXFMT, + createVertexFmt, nil, copyVertexFmt); + Geometry::registerPluginStream(ID_VERTEXFMT, + readVertexFmt, + writeVertexFmt, + getSizeVertexFmt); +} + +} +} diff --git a/src/engine.cpp b/src/engine.cpp new file mode 100644 index 0000000..f1fabc5 --- /dev/null +++ b/src/engine.cpp @@ -0,0 +1,524 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "ps2/rwps2.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwgl3.h" +#include "gl/rwwdgl.h" + +#define PLUGIN_ID 0 + +// on windows +//#ifdef DEBUG +//#include +//#define free(p) _free_dbg(p, _NORMAL_BLOCK); +//#define malloc(sz) _malloc_dbg(sz, _NORMAL_BLOCK, __FILE__, __LINE__) +//#define realloc(p, sz) _realloc_dbg(p, sz, _NORMAL_BLOCK, __FILE__, __LINE__) +//#endif + +namespace rw { + +Engine *engine; +PluginList Engine::s_plglist(sizeof(Engine)); +Engine::State Engine::state = Dead; +MemoryFunctions Engine::memfuncs; +PluginList Driver::s_plglist[NUM_PLATFORMS]; + +const char *allocLocation; + +void *malloc_h(size_t sz, uint32 hint) { if(sz == 0) return nil; return malloc(sz); } +void *realloc_h(void *p, size_t sz, uint32 hint) { return realloc(p, sz); } + +struct MemoryBlock +{ + size_t sz; + uint32 hint; + void *origPtr; + const char *codeline; + LLLink inAllocList; +}; +LinkList allocations; +size_t totalMemoryAllocated; + +// We align managed memory blocks on a 16 byte boundary + +#define ALIGN16(x) ((x) + 0xF & ~0xF) +void* +malloc_managed(size_t sz, uint32 hint) +{ + void *origPtr; + uint8 *data; + MemoryBlock *mem; + + if(sz == 0) return nil; + origPtr = malloc(sz + sizeof(MemoryBlock) + 15); + if(origPtr == nil) + return nil; + totalMemoryAllocated += sz; + data = (uint8*)origPtr; + data += sizeof(MemoryBlock); + data = (uint8*)ALIGN16((uintptr)data); + mem = (MemoryBlock*)(data-sizeof(MemoryBlock)); + + mem->sz = sz; + mem->hint = hint; + mem->origPtr = origPtr; + mem->codeline = allocLocation; + allocations.add(&mem->inAllocList); + + return data; +} + +void* +realloc_managed(void *p, size_t sz, uint32 hint) +{ + void *origPtr; + MemoryBlock *mem; + uint32 offset; + + if(p == nil) + return malloc_managed(sz, hint); + + mem = (MemoryBlock*)((uint8*)p-sizeof(MemoryBlock)); + offset = (uint8*)p - (uint8*)mem->origPtr; + + mem->inAllocList.remove(); + + origPtr = realloc(mem->origPtr, sz + sizeof(MemoryBlock) + 15); + if(origPtr == nil){ + allocations.add(&mem->inAllocList); + return nil; + } + p = (uint8*)origPtr + offset; + mem = (MemoryBlock*)((uint8*)p-sizeof(MemoryBlock)); + totalMemoryAllocated -= mem->sz; + mem->sz = sz; + mem->hint = hint; + mem->origPtr = origPtr; + mem->codeline = allocLocation; + allocations.add(&mem->inAllocList); + totalMemoryAllocated += mem->sz; + + return p; +} + +void +free_managed(void *p) +{ + MemoryBlock *mem; + if(p == nil) + return; + mem = (MemoryBlock*)((uint8*)p-sizeof(MemoryBlock)); + mem->inAllocList.remove(); + totalMemoryAllocated -= mem->sz; + free(mem->origPtr); +} + +// TODO: make the debug out configurable +void *mustmalloc_h(size_t sz, uint32 hint) +{ + void *ret; + ret = Engine::memfuncs.rwmalloc(sz, hint); + if(ret || sz == 0) + return ret; + fprintf(stderr, "Error: out of memory\n"); + exit(1); + return nil; +} +void *mustrealloc_h(void *p, size_t sz, uint32 hint) +{ + void *ret; + ret = Engine::memfuncs.rwrealloc(p, sz, hint); + if(ret || sz == 0) + return ret; + fprintf(stderr, "Error: out of memory\n"); + exit(1); + return nil; +} + +//#define TRACK_ALLOCATIONS + +// This function mainly registers engine plugins +bool32 +Engine::init(void) +{ + if(engine || Engine::state != Dead){ + RWERROR((ERR_ENGINEINIT)); + return 0; + } + + totalMemoryAllocated = 0; + allocations.init(); + + // TODO: make this an argument +#ifdef TRACK_ALLOCATIONS + memfuncs.rwmalloc = malloc_managed; + memfuncs.rwrealloc = realloc_managed; + memfuncs.rwfree = free_managed; +#else + memfuncs.rwmalloc = malloc_h; + memfuncs.rwrealloc = realloc_h; + memfuncs.rwfree = free; +#endif + + memfuncs.rwmustmalloc = mustmalloc_h; + memfuncs.rwmustrealloc = mustrealloc_h; + + PluginList::open(); + + for(uint i = 0; i < NUM_PLATFORMS; i++) + new (&Driver::s_plglist[i]) PluginList(sizeof(Driver)); + + // core plugin attach here + Frame::registerModule(); + Image::registerModule(); + Raster::registerModule(); + Texture::registerModule(); + + // TODO: reset all allocation counts here. or maybe do that in modules? + Frame::numAllocated = 0; + Image::numAllocated = 0; + Raster::numAllocated = 0; + Texture::numAllocated = 0; + TexDictionary::numAllocated = 0; + Geometry::numAllocated = 0; + Material::numAllocated = 0; + Atomic::numAllocated = 0; + Light::numAllocated = 0; + Camera::numAllocated = 0; + Clump::numAllocated = 0; + World::numAllocated = 0; + + // driver plugin attach + ps2::registerPlatformPlugins(); + xbox::registerPlatformPlugins(); + d3d8::registerPlatformPlugins(); + d3d9::registerPlatformPlugins(); + wdgl::registerPlatformPlugins(); + gl3::registerPlatformPlugins(); + + Engine::state = Initialized; + return 1; +} + +// This is where RW allocates the engine and e.g. opens d3d +bool32 +Engine::open(EngineOpenParams *p) +{ + if(engine || Engine::state != Initialized){ + RWERROR((ERR_ENGINEOPEN)); + return 0; + } + + // Allocate engine + engine = (Engine*)rwNew(Engine::s_plglist.size, MEMDUR_GLOBAL); + engine->currentCamera = nil; + engine->currentWorld = nil; + + // Initialize device + // Device and possibly OS specific! +#ifdef RW_PS2 + engine->device = ps2::renderdevice; +#elif RW_GL3 + engine->device = gl3::renderdevice; +#elif RW_D3D9 + engine->device = d3d::renderdevice; +#else + engine->device = null::renderdevice; +#endif + + engine->device.system(DEVICEOPEN, (void*)p, 0); + + engine->dummyDefaultPipeline = ObjPipeline::create(); + for(uint i = 0; i < NUM_PLATFORMS; i++){ + rw::engine->driver[i] = (Driver*)rwNew(Driver::s_plglist[i].size, + MEMDUR_GLOBAL); + + engine->driver[i]->defaultPipeline = engine->dummyDefaultPipeline; + + engine->driver[i]->rasterCreate = null::rasterCreate; + engine->driver[i]->rasterLock = null::rasterLock; + engine->driver[i]->rasterUnlock = null::rasterUnlock; + engine->driver[i]->rasterLockPalette = null::rasterLockPalette; + engine->driver[i]->rasterUnlockPalette = null::rasterUnlockPalette; + engine->driver[i]->rasterNumLevels = null::rasterNumLevels; + engine->driver[i]->imageFindRasterFormat = null::imageFindRasterFormat; + engine->driver[i]->rasterFromImage = null::rasterFromImage; + engine->driver[i]->rasterToImage = null::rasterToImage; + } + + Engine::state = Opened; + return 1; +} + +// This is where RW creates the actual rendering device +// and calls the engine plugin ctors +bool32 +Engine::start(void) +{ + if(engine == nil || Engine::state != Opened){ + RWERROR((ERR_ENGINESTART)); + return 0; + } + + engine->device.system(DEVICEINIT, nil, 0); + + Engine::s_plglist.construct(engine); + for(uint i = 0; i < NUM_PLATFORMS; i++) + Driver::s_plglist[i].construct(rw::engine->driver[i]); + + engine->device.system(DEVICEFINALIZE, nil, 0); + + // Register some image formats. Or should we leave that to the user? + Image::registerFileFormat("tga", readTGA, writeTGA); + Image::registerFileFormat("bmp", readBMP, writeBMP); + Image::registerFileFormat("png", readPNG, writePNG); + + Engine::state = Started; + return 1; +} + +void +Engine::term(void) +{ + if(engine || Engine::state != Initialized){ + RWERROR((ERR_GENERAL)); + return; + } + + PluginList::close(); + + // This has to be reset because it won't be opened again otherwise + // TODO: maybe reset more stuff here? + d3d::nativeRasterOffset = 0; + +#ifdef TRACK_ALLOCATIONS + FORLIST(lnk, allocations){ + MemoryBlock *mem = LLLinkGetData(lnk, MemoryBlock, inAllocList); + printf("sz %d hint %X\n %s\n", mem->sz, mem->hint, mem->codeline); + } +#endif + + Engine::state = Dead; +} + +void +Engine::close(void) +{ + if(engine == nil || Engine::state != Opened){ + RWERROR((ERR_GENERAL)); + return; + } + + engine->device.system(DEVICECLOSE, nil, 0); + for(uint i = 0; i < NUM_PLATFORMS; i++) + rwFree(rw::engine->driver[i]); + engine->dummyDefaultPipeline->destroy(); + rwFree(engine); + engine = nil; + Engine::state = Initialized; +} + +void +Engine::stop(void) +{ + if(engine == nil || Engine::state != Started){ + RWERROR((ERR_GENERAL)); + return; + } + + for(uint i = 0; i < NUM_PLATFORMS; i++) + Driver::s_plglist[i].destruct(rw::engine->driver[i]); + Engine::s_plglist.destruct(engine); + + engine->device.system(DEVICETERM, nil, 0); + + Engine::state = Opened; +} + + +int32 +Engine::getNumSubSystems(void) +{ + return engine->device.system(DEVICEGETNUMSUBSYSTEMS, nil, 0); +} + +int32 +Engine::getCurrentSubSystem(void) +{ + return engine->device.system(DEVICEGETCURRENTSUBSYSTEM, nil, 0); +} + +bool32 +Engine::setSubSystem(int32 subsys) +{ + return engine->device.system(DEVICESETSUBSYSTEM, nil, subsys); +} + +SubSystemInfo* +Engine::getSubSystemInfo(SubSystemInfo *info, int32 subsys) +{ + if(engine->device.system(DEVICEGETSUBSSYSTEMINFO, info, subsys)) + return info; + return nil; +} + + +int32 +Engine::getNumVideoModes(void) +{ + return engine->device.system(DEVICEGETNUMVIDEOMODES, nil, 0); +} + +int32 +Engine::getCurrentVideoMode(void) +{ + return engine->device.system(DEVICEGETCURRENTVIDEOMODE, nil, 0); +} + +bool32 +Engine::setVideoMode(int32 mode) +{ + return engine->device.system(DEVICESETVIDEOMODE, nil, mode); +} + +VideoMode* +Engine::getVideoModeInfo(VideoMode *info, int32 mode) +{ + if(engine->device.system(DEVICEGETVIDEOMODEINFO, info, mode)) + return info; + return nil; +} + + +namespace null { + +void beginUpdate(Camera*) { } +void endUpdate(Camera*) { } +void clearCamera(Camera*,RGBA*,uint32) { } +void showRaster(Raster*,uint32) { } + +void setRenderState(int32, void*) { } +void *getRenderState(int32) { return 0; } + +bool32 rasterRenderFast(Raster *raster, int32 x, int32 y) { return 0; } + +void im2DRenderLine(void*, int32, int32, int32) { } +void im2DRenderTriangle(void*, int32, int32, int32, int32) { } +void im2DRenderPrimitive(PrimitiveType, void*, int32) { } +void im2DRenderIndexedPrimitive(PrimitiveType, void*, int32, void*, int32) { } + +void im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags) { } +void im3DRenderPrimitive(PrimitiveType primType) { } +void im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices) { } +void im3DEnd(void) { } + +Raster* +rasterCreate(Raster*) +{ + assert(0 && "rasterCreate not implemented"); + return nil; +} + +uint8* +rasterLock(Raster*, int32, int32) +{ + assert(0 && "lockRaster not implemented"); + return nil; +} + +void +rasterUnlock(Raster*, int32) +{ + assert(0 && "unlockRaster not implemented"); +} + +uint8* +rasterLockPalette(Raster*, int32) +{ + assert(0 && "rasterLockPalette not implemented"); + return nil; +} + +void +rasterUnlockPalette(Raster*) +{ + assert(0 && "rasterUnlockPalette not implemented"); +} + +int32 +rasterNumLevels(Raster*) +{ + assert(0 && "rasterNumLevels not implemented"); + return 0; +} + +bool32 +imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format) +{ + assert(0 && "imageFindRasterFormat not implemented"); + return 0; +} + +bool32 +rasterFromImage(Raster*, Image*) +{ + assert(0 && "rasterFromImage not implemented"); + return 0; +} + +Image* +rasterToImage(Raster*) +{ + assert(0 && "rasterToImage not implemented"); + return nil; +} + +int +deviceSystem(DeviceReq req, void *arg0, int32 n) +{ + switch(req){ + case DEVICEGETNUMSUBSYSTEMS: + return 0; + case DEVICEGETCURRENTSUBSYSTEM: + return 0; + case DEVICEGETSUBSSYSTEMINFO: + return 0; + } + return 1; +} + +Device renderdevice = { + 0.0f, 1.0f, + null::beginUpdate, + null::endUpdate, + null::clearCamera, + null::showRaster, + null::rasterRenderFast, + null::setRenderState, + null::getRenderState, + null::im2DRenderLine, + null::im2DRenderTriangle, + null::im2DRenderPrimitive, + null::im2DRenderIndexedPrimitive, + null::im3DTransform, + null::im3DRenderPrimitive, + null::im3DRenderIndexedPrimitive, + null::im3DEnd, + null::deviceSystem +}; + +} +} diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000..bdf4080 --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,49 @@ +#include +#include + +#include "rwbase.h" +#include "rwerror.h" + +namespace rw { + +static Error error; + +void +setError(Error *e) +{ + error = *e; +} + +Error* +getError(Error *e) +{ + *e = error; + error.plugin = 0; + error.code = 0; + return e; +} + +#define ECODE(c, s) s + +const char *errstrs[] = { + "No error", +#include "base.err" +}; + +#undef ECODE + +char* +dbgsprint(uint32 code, ...) +{ + va_list ap; + static char strbuf[512]; + + if(code & 0x80000000) + code &= ~0x80000000; + va_start(ap, code); + vsprintf(strbuf, errstrs[code], ap); + va_end(ap); + return strbuf; +} + +} diff --git a/src/frame.cpp b/src/frame.cpp new file mode 100644 index 0000000..c8dabd5 --- /dev/null +++ b/src/frame.cpp @@ -0,0 +1,463 @@ +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_FRAMELIST + +namespace rw { + +int32 Frame::numAllocated; + +PluginList Frame::s_plglist(sizeof(Frame)); +static void *frameOpen(void *object, int32 offset, int32 size) { engine->frameDirtyList.init(); return object; } +static void *frameClose(void *object, int32 offset, int32 size) { return object; } + +void +Frame::registerModule(void) +{ + Engine::registerPlugin(0, ID_FRAMEMODULE, frameOpen, frameClose); +} + +Frame* +Frame::create(void) +{ + Frame *f = (Frame*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_FRAMELIST); + if(f == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + f->object.init(Frame::ID, 0); + f->objectList.init(); + f->child = nil; + f->next = nil; + f->root = f; + f->matrix.setIdentity(); + f->ltm.setIdentity(); + s_plglist.construct(f); + return f; +} + +Frame* +Frame::cloneHierarchy(void) +{ + Frame *frame = this->cloneAndLink(); + this->purgeClone(); + return frame; +} +void +Frame::destroy(void) +{ + FORLIST(lnk, this->objectList) + ObjectWithFrame::fromFrame(lnk)->setFrame(nil); + s_plglist.destruct(this); + if(this->getParent()) + this->removeChild(); + if(this->object.privateFlags & Frame::HIERARCHYSYNC) + this->inDirtyList.remove(); + for(Frame *f = this->child; f; f = f->next) + f->object.parent = nil; + rwFree(this); + numAllocated--; +} + +void +Frame::destroyHierarchy(void) +{ + Frame *next; + for(Frame *child = this->child; child; child = next){ + next = child->next; + child->destroyHierarchy(); + } + assert(this->objectList.isEmpty()); + s_plglist.destruct(this); + if(this->object.privateFlags & Frame::HIERARCHYSYNC) + this->inDirtyList.remove(); + rwFree(this); +} + +Frame* +Frame::addChild(Frame *child, bool32 append) +{ + Frame *c; + if(child->getParent()) + child->removeChild(); + if(append){ + if(this->child == nil) + this->child = child; + else{ + for(c = this->child; c->next; c = c->next); + c->next = child; + } + child->next = nil; + }else{ + child->next = this->child; + this->child = child; + } + child->object.parent = this; + child->root = this->root; + for(c = child->child; c; c = c->next) + c->setHierarchyRoot(this); + // If the child was a root, remove from dirty list + if(child->object.privateFlags & Frame::HIERARCHYSYNC){ + child->inDirtyList.remove(); + child->object.privateFlags &= ~Frame::HIERARCHYSYNC; + } + this->updateObjects(); + return this; +} + +Frame* +Frame::removeChild(void) +{ + Frame *parent = this->getParent(); + Frame *child = parent->child; + if(child == this) + parent->child = this->next; + else{ + while(child->next != this) + child = child->next; + child->next = this->next; + } + this->object.parent = this->next = nil; + // give the hierarchy a new root + this->setHierarchyRoot(this); + this->updateObjects(); + return this; +} + +Frame* +Frame::forAllChildren(Callback cb, void *data) +{ + Frame *next; + for(Frame *f = this->child; f; f = next){ + next = f->next; + if(cb(f, data) == nil) + return this; + } + return this; +} + +static Frame* +countCB(Frame *f, void *count) +{ + (*(int32*)count)++; + f->forAllChildren(countCB, count); + return f; +} + +int32 +Frame::count(void) +{ + int32 count = 1; + this->forAllChildren(countCB, (void*)&count); + return count; +} + +/* + * Synching is a bit complicated. If anything in the hierarchy is not synched, + * the root of the hierarchy is marked with the HIERARCHYSYNC flags. + * Every unsynched frame is marked with the SUBTREESYNC flags. + * If the LTM is not synched, the LTM flags are set. + * If attached objects need synching, the OBJ flags are set. + */ + +/* Synch just LTM matrices in a hierarchy */ +static void +syncLTMRecurse(Frame *frame, uint8 hierarchyFlags) +{ + for(; frame; frame = frame->next){ + // If frame is dirty or any parent was dirty, update LTM + hierarchyFlags |= frame->object.privateFlags; + if(hierarchyFlags & Frame::SUBTREESYNCLTM){ + Matrix::mult(&frame->ltm, &frame->matrix, + &frame->getParent()->ltm); + frame->object.privateFlags &= ~Frame::SUBTREESYNCLTM; + } + // And synch all children + syncLTMRecurse(frame->child, hierarchyFlags); + } +} + +/* Synch just objects in a hierarchy */ +static void +syncObjRecurse(Frame *frame) +{ + for(; frame; frame = frame->next){ + // Synch attached objects + FORLIST(lnk, frame->objectList) + ObjectWithFrame::fromFrame(lnk)->sync(); + frame->object.privateFlags &= ~Frame::SUBTREESYNCOBJ; + // And synch all children + syncObjRecurse(frame->child); + } +} + +/* Synch LTM and objects */ +static void +syncRecurse(Frame *frame, uint8 hierarchyFlags) +{ + for(; frame; frame = frame->next){ + // If frame is dirty or any parent was dirty, update LTM + hierarchyFlags |= frame->object.privateFlags; + if(hierarchyFlags & Frame::SUBTREESYNCLTM) + Matrix::mult(&frame->ltm, &frame->matrix, + &frame->getParent()->ltm); + // Synch attached objects + FORLIST(lnk, frame->objectList) + ObjectWithFrame::fromFrame(lnk)->sync(); + frame->object.privateFlags &= ~Frame::SUBTREESYNC; + // And synch all children + syncRecurse(frame->child, hierarchyFlags); + } +} + +/* Sync the LTMs of the hierarchy of which 'this' is the root */ +void +Frame::syncHierarchyLTM(void) +{ + // Sync root's LTM + if(this->object.privateFlags & Frame::SUBTREESYNCLTM) + this->ltm = this->matrix; + // ...and children + syncLTMRecurse(this->child, this->object.privateFlags); + // all clean now + this->object.privateFlags &= ~Frame::SYNCLTM; +} + +Matrix* +Frame::getLTM(void) +{ + if(this->root->object.privateFlags & Frame::HIERARCHYSYNCLTM) + this->root->syncHierarchyLTM(); + return &this->ltm; +} + +/* Synch all dirty frames; LTMs and objects */ +void +Frame::syncDirty(void) +{ + Frame *frame; + FORLIST(lnk, engine->frameDirtyList){ + frame = LLLinkGetData(lnk, Frame, inDirtyList); + if(frame->object.privateFlags & Frame::HIERARCHYSYNCLTM){ + // Sync root's LTM + if(frame->object.privateFlags & Frame::SUBTREESYNCLTM) + frame->ltm = frame->matrix; + // Synch attached objects + FORLIST(lnk, frame->objectList) + ObjectWithFrame::fromFrame(lnk)->sync(); + // ...and children + syncRecurse(frame->child, frame->object.privateFlags); + }else{ + // LTMs are clean, just synch objects + FORLIST(lnk, frame->objectList) + ObjectWithFrame::fromFrame(lnk)->sync(); + syncObjRecurse(frame->child); + } + // all clean now + frame->object.privateFlags &= ~(Frame::SYNCLTM | Frame::SYNCOBJ); + } + engine->frameDirtyList.init(); +} + +void +Frame::rotate(const V3d *axis, float32 angle, CombineOp op) +{ + this->matrix.rotate(axis, angle, op); + updateObjects(); +} + +void +Frame::translate(const V3d *trans, CombineOp op) +{ + this->matrix.translate(trans, op); + updateObjects(); +} + +void +Frame::scale(const V3d *scl, CombineOp op) +{ + this->matrix.scale(scl, op); + updateObjects(); +} + +void +Frame::transform(const Matrix *mat, CombineOp op) +{ + this->matrix.transform(mat, op); + updateObjects(); +} + +void +Frame::updateObjects(void) +{ + // Mark root as dirty and insert into dirty list if necessary + if((this->root->object.privateFlags & HIERARCHYSYNC) == 0) + engine->frameDirtyList.add(&this->root->inDirtyList); + this->root->object.privateFlags |= HIERARCHYSYNC; + // Mark subtree as dirty as well + this->object.privateFlags |= SUBTREESYNC; +} + +void +Frame::setHierarchyRoot(Frame *root) +{ + this->root = root; + for(Frame *child = this->child; child; child = child->next) + child->setHierarchyRoot(root); +} + +static Frame* +cloneRecurse(Frame *old, Frame *newroot) +{ + Frame *frame = Frame::create(); + if(newroot == nil) + newroot = frame; + frame->object.copy(&old->object); + frame->matrix = old->matrix; + frame->root = newroot; + old->root = frame; // Remember cloned frame + for(Frame *child = old->child; child; child = child->next){ + Frame *clonedchild = cloneRecurse(child, newroot); + clonedchild->next = frame->child; + frame->child = clonedchild; + clonedchild->object.parent = frame; + } + Frame::s_plglist.copy(frame, old); + return frame; +} + +// Clone a frame hierarchy. Link cloned frames into Frame::root of the originals. +Frame* +Frame::cloneAndLink(void) +{ + Frame *newhier = cloneRecurse(this, nil); + if(newhier){ + // frame is not in dirty list so important to get this flag right + newhier->object.privateFlags &= ~HIERARCHYSYNC; + newhier->updateObjects(); + } + return newhier; +} + +// Remove links to cloned frames from hierarchy. +void +Frame::purgeClone(void) +{ + Frame *parent = this->getParent(); + this->setHierarchyRoot(parent ? parent->root : this); +} + +struct FrameStreamData +{ + V3d right, up, at, pos; + int32 parent; + int32 matflag; +}; + +FrameList_* +FrameList_::streamRead(Stream *stream) +{ + FrameStreamData buf; + this->numFrames = 0; + this->frames = nil; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + this->numFrames = stream->readI32(); + this->frames = (Frame**)rwMalloc(this->numFrames*sizeof(Frame*), MEMDUR_EVENT | ID_FRAMELIST); + if(this->frames == nil){ + RWERROR((ERR_ALLOC, this->numFrames*sizeof(Frame*))); + return nil; + } + for(int32 i = 0; i < this->numFrames; i++){ + Frame *f; + stream->read32(&buf, sizeof(buf)); + this->frames[i] = f = Frame::create(); + if(f == nil){ + // TODO: clean up frames? + rwFree(this->frames); + return nil; + } + f->matrix.right = buf.right; + f->matrix.up = buf.up; + f->matrix.at = buf.at; + f->matrix.pos = buf.pos; + f->matrix.optimize(); + // RW always removes identity flag + f->matrix.flags &= ~Matrix::IDENTITY; + if(buf.parent >= 0) + this->frames[buf.parent]->addChild(f, rw::streamAppendFrames); + } + for(int32 i = 0; i < this->numFrames; i++) + Frame::s_plglist.streamRead(stream, this->frames[i]); + return this; +} + +void +FrameList_::streamWrite(Stream *stream) +{ + FrameStreamData buf; + + int size = 0, structsize = 0; + structsize = 4 + this->numFrames*sizeof(FrameStreamData); + size += 12 + structsize; + for(int32 i = 0; i < this->numFrames; i++) + size += 12 + Frame::s_plglist.streamGetSize(this->frames[i]); + + writeChunkHeader(stream, ID_FRAMELIST, size); + writeChunkHeader(stream, ID_STRUCT, structsize); + stream->writeU32(this->numFrames); + for(int32 i = 0; i < this->numFrames; i++){ + Frame *f = this->frames[i]; + buf.right = f->matrix.right; + buf.up = f->matrix.up; + buf.at = f->matrix.at; + buf.pos = f->matrix.pos; + buf.parent = findPointer(f->getParent(), (void**)this->frames, + this->numFrames); + buf.matflag = 0; //f->matflag; + stream->write32(&buf, sizeof(buf)); + } + for(int32 i = 0; i < this->numFrames; i++) + Frame::s_plglist.streamWrite(stream, this->frames[i]); +} + +static Frame* +sizeCB(Frame *f, void *size) +{ + *(int32*)size += Frame::s_plglist.streamGetSize(f); + f->forAllChildren(sizeCB, size); + return f; +} + +uint32 +FrameList_::streamGetSize(Frame *f) +{ + int32 numFrames = f->count(); + uint32 size = 12 + 12 + 4 + numFrames*(sizeof(FrameStreamData)+12); + sizeCB(f, (void*)&size); + return size; +} + +Frame** +makeFrameList(Frame *frame, Frame **flist) +{ + *flist++ = frame; + if(frame->next) + flist = makeFrameList(frame->next, flist); + if(frame->child) + flist = makeFrameList(frame->child, flist); + return flist; +} + +} diff --git a/src/geometry.cpp b/src/geometry.cpp new file mode 100644 index 0000000..e62bc5d --- /dev/null +++ b/src/geometry.cpp @@ -0,0 +1,1047 @@ +#include +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_GEOMETRY + +namespace rw { + +int32 Geometry::numAllocated; +int32 Material::numAllocated; + +PluginList Geometry::s_plglist(sizeof(Geometry)); +PluginList Material::s_plglist(sizeof(Material)); + +static SurfaceProperties defaultSurfaceProps = { 1.0f, 1.0f, 1.0f }; + +// We allocate twice because we have to allocate the data separately for uninstancing +Geometry* +Geometry::create(int32 numVerts, int32 numTris, uint32 flags) +{ + Geometry *geo = (Geometry*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_GEOMETRY); + if(geo == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + geo->object.init(Geometry::ID, 0); + geo->flags = flags & 0xFF00FFFF; + geo->numTexCoordSets = (flags & 0xFF0000) >> 16; + if(geo->numTexCoordSets == 0) + geo->numTexCoordSets = (geo->flags & TEXTURED) ? 1 : + (geo->flags & TEXTURED2) ? 2 : 0; + geo->numTriangles = numTris; + geo->numVertices = numVerts; + + geo->colors = nil; + for(int32 i = 0; i < 8; i++) + geo->texCoords[i] = nil; + geo->triangles = nil; + // Allocate all attributes at once. The triangle pointer + // will hold the first address (even when there are no triangles) + // so we can free easily. + if(!(geo->flags & NATIVE)){ + int32 sz = geo->numTriangles*sizeof(Triangle); + if(geo->flags & PRELIT) + sz += geo->numVertices*sizeof(RGBA); + sz += geo->numTexCoordSets*geo->numVertices*sizeof(TexCoords); + + uint8 *data = (uint8*)rwNew(sz, MEMDUR_EVENT | ID_GEOMETRY); + geo->triangles = (Triangle*)data; + data += geo->numTriangles*sizeof(Triangle); + if(geo->flags & PRELIT && geo->numVertices){ + geo->colors = (RGBA*)data; + data += geo->numVertices*sizeof(RGBA); + } + if(geo->numVertices) + for(int32 i = 0; i < geo->numTexCoordSets; i++){ + geo->texCoords[i] = (TexCoords*)data; + data += geo->numVertices*sizeof(TexCoords); + } + + // init triangles + for(int32 i = 0; i < geo->numTriangles; i++) + geo->triangles[i].matId = 0xFFFF; + } + geo->numMorphTargets = 0; + geo->morphTargets = nil; + geo->addMorphTargets(1); + + geo->matList.init(); + geo->lockedSinceInst = 0; + geo->meshHeader = nil; + geo->instData = nil; + geo->refCount = 1; + + s_plglist.construct(geo); + return geo; +} + +void +Geometry::destroy(void) +{ + this->refCount--; + if(this->refCount <= 0){ + s_plglist.destruct(this); + // Also frees colors and tex coords + rwFree(this->triangles); + // Also frees their data + rwFree(this->morphTargets); + // Also frees indices + rwFree(this->meshHeader); + this->matList.deinit(); + rwFree(this); + numAllocated--; + } +} + +void +Geometry::lock(int32 lockFlags) +{ + lockedSinceInst |= lockFlags; + if(lockFlags & LOCKPOLYGONS){ + rwFree(this->meshHeader); + this->meshHeader = nil; + } +} + +void +Geometry::unlock(void) +{ + if(this->meshHeader == nil) + this->buildMeshes(); + +} + +struct GeoStreamData +{ + uint32 flags; + int32 numTriangles; + int32 numVertices; + int32 numMorphTargets; +}; + +Geometry* +Geometry::streamRead(Stream *stream) +{ + uint32 version; + GeoStreamData buf; + SurfaceProperties surfProps; + MaterialList *ret; + static SurfaceProperties reset = { 1.0f, 1.0f, 1.0f }; + + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(&buf, sizeof(buf)); + Geometry *geo = Geometry::create(buf.numVertices, + buf.numTriangles, buf.flags); + if(geo == nil) + return nil; + geo->addMorphTargets(buf.numMorphTargets-1); + if(version < 0x34000) + stream->read32(&surfProps, 12); + + if(!(geo->flags & NATIVE)){ + if(geo->flags & PRELIT) + stream->read8(geo->colors, 4*geo->numVertices); + for(int32 i = 0; i < geo->numTexCoordSets; i++) + stream->read32(geo->texCoords[i], + 2*geo->numVertices*4); + for(int32 i = 0; i < geo->numTriangles; i++){ + uint32 tribuf[2]; + stream->read32(tribuf, 8); + geo->triangles[i].v[0] = tribuf[0] >> 16; + geo->triangles[i].v[1] = tribuf[0]; + geo->triangles[i].v[2] = tribuf[1] >> 16; + geo->triangles[i].matId = tribuf[1]; + } + } + + for(int32 i = 0; i < geo->numMorphTargets; i++){ + MorphTarget *m = &geo->morphTargets[i]; + stream->read32(&m->boundingSphere, 4*4); + int32 hasVertices = stream->readI32(); + int32 hasNormals = stream->readI32(); + if(hasVertices) + stream->read32(m->vertices, 3*geo->numVertices*4); + if(hasNormals) + stream->read32(m->normals, 3*geo->numVertices*4); + } + + if(!findChunk(stream, ID_MATLIST, nil, nil)){ + RWERROR((ERR_CHUNK, "MATLIST")); + goto fail; + } + if(version < 0x34000) + defaultSurfaceProps = surfProps; + + ret = MaterialList::streamRead(stream, &geo->matList); + if(version < 0x34000) + defaultSurfaceProps = reset; + if(ret == nil) + goto fail; + if(s_plglist.streamRead(stream, geo)) + return geo; + +fail: + geo->destroy(); + return nil; +} + +static uint32 +geoStructSize(Geometry *geo) +{ + uint32 size = 0; + size += sizeof(GeoStreamData); + if(version < 0x34000) + size += 12; // surface properties + if(!(geo->flags & Geometry::NATIVE)){ + if(geo->flags&geo->PRELIT) + size += 4*geo->numVertices; + for(int32 i = 0; i < geo->numTexCoordSets; i++) + size += 2*geo->numVertices*4; + size += 4*geo->numTriangles*2; + } + for(int32 i = 0; i < geo->numMorphTargets; i++){ + MorphTarget *m = &geo->morphTargets[i]; + size += 4*4 + 2*4; // bounding sphere and bools + if(!(geo->flags & Geometry::NATIVE)){ + if(m->vertices) + size += 3*geo->numVertices*4; + if(m->normals) + size += 3*geo->numVertices*4; + } + } + return size; +} + +bool +Geometry::streamWrite(Stream *stream) +{ + GeoStreamData buf; + static float32 fbuf[3] = { 1.0f, 1.0f, 1.0f }; + + writeChunkHeader(stream, ID_GEOMETRY, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, geoStructSize(this)); + + buf.flags = this->flags | this->numTexCoordSets << 16; + buf.numTriangles = this->numTriangles; + buf.numVertices = this->numVertices; + buf.numMorphTargets = this->numMorphTargets; + stream->write32(&buf, sizeof(buf)); + if(version < 0x34000) + stream->write32(fbuf, sizeof(fbuf)); + + if(!(this->flags & NATIVE)){ + if(this->flags & PRELIT) + stream->write8(this->colors, 4*this->numVertices); + for(int32 i = 0; i < this->numTexCoordSets; i++) + stream->write32(this->texCoords[i], + 2*this->numVertices*4); + for(int32 i = 0; i < this->numTriangles; i++){ + uint32 tribuf[2]; + tribuf[0] = this->triangles[i].v[0] << 16 | + this->triangles[i].v[1]; + tribuf[1] = this->triangles[i].v[2] << 16 | + this->triangles[i].matId; + stream->write32(tribuf, 8); + } + } + + for(int32 i = 0; i < this->numMorphTargets; i++){ + MorphTarget *m = &this->morphTargets[i]; + stream->write32(&m->boundingSphere, 4*4); + if(!(this->flags & NATIVE)){ + stream->writeI32(m->vertices != nil); + stream->writeI32(m->normals != nil); + if(m->vertices) + stream->write32(m->vertices, + 3*this->numVertices*4); + if(m->normals) + stream->write32(m->normals, + 3*this->numVertices*4); + }else{ + stream->writeI32(0); + stream->writeI32(0); + } + } + + this->matList.streamWrite(stream); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Geometry::streamGetSize(void) +{ + uint32 size = 0; + size += 12 + geoStructSize(this); + size += 12 + this->matList.streamGetSize(); + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +void +Geometry::addMorphTargets(int32 n) +{ + if(n == 0) + return; + n += this->numMorphTargets; + + int32 sz; + sz = sizeof(MorphTarget); + if(!(this->flags & NATIVE)){ + sz += this->numVertices*sizeof(V3d); + if(this->flags & NORMALS) + sz += this->numVertices*sizeof(V3d); + } + + // Memory layout: MorphTarget[n]; (vertices and normals)[n] + MorphTarget *mts; + if(this->numMorphTargets){ + mts = (MorphTarget*)rwResize(this->morphTargets, n*sz, MEMDUR_EVENT | ID_GEOMETRY); + this->morphTargets = mts; + // Since we now have more morph targets than before, move the vertex data up + uint8 *src = (uint8*)mts + sz*this->numMorphTargets; + uint8 *dst = (uint8*)mts + sz*n; + uint32 len = (sz-sizeof(MorphTarget))*this->numMorphTargets; + while(len--) + *--dst = *--src; + }else{ + mts = (MorphTarget*)rwNew(n*sz, MEMDUR_EVENT | ID_GEOMETRY); + this->morphTargets = mts; + } + + // Set up everything and initialize the bounding sphere for new morph targets + V3d *data = (V3d*)&mts[n]; + for(int32 i = 0; i < n; i++){ + mts->parent = this; + mts->vertices = nil; + mts->normals = nil; + if(i >= this->numMorphTargets){ + mts->boundingSphere.center.x = 0.0f; + mts->boundingSphere.center.y = 0.0f; + mts->boundingSphere.center.z = 0.0f; + mts->boundingSphere.radius = 0.0f; + } + if(!(this->flags & NATIVE) && this->numVertices){ + mts->vertices = data; + data += this->numVertices; + if(this->flags & NORMALS){ + mts->normals = data; + data += this->numVertices; + } + } + mts++; + } + this->numMorphTargets = n; +} + +void +Geometry::calculateBoundingSphere(void) +{ + for(int32 i = 0; i < this->numMorphTargets; i++){ + MorphTarget *m = &this->morphTargets[i]; + m->boundingSphere = m->calculateBoundingSphere(); + } +} + +bool32 +Geometry::hasColoredMaterial(void) +{ + for(int32 i = 0; i < this->matList.numMaterials; i++) + if(this->matList.materials[i]->color.red != 255 || + this->matList.materials[i]->color.green != 255 || + this->matList.materials[i]->color.blue != 255 || + this->matList.materials[i]->color.alpha != 255) + return 1; + return 0; +} + +// Force allocate data, even when native flag is set +void +Geometry::allocateData(void) +{ + // Geometry data + // Pretty much copy pasted from ::create above + int32 sz = this->numTriangles*sizeof(Triangle); + if(this->flags & PRELIT) + sz += this->numVertices*sizeof(RGBA); + sz += this->numTexCoordSets*this->numVertices*sizeof(TexCoords); + + uint8 *data = (uint8*)rwNew(sz, MEMDUR_EVENT | ID_GEOMETRY); + this->triangles = (Triangle*)data; + data += this->numTriangles*sizeof(Triangle); + for(int32 i = 0; i < this->numTriangles; i++) + this->triangles[i].matId = 0xFFFF; + if(this->flags & PRELIT){ + this->colors = (RGBA*)data; + data += this->numVertices*sizeof(RGBA); + } + for(int32 i = 0; i < this->numTexCoordSets; i++){ + this->texCoords[i] = (TexCoords*)data; + data += this->numVertices*sizeof(TexCoords); + } + + // MorphTarget data + // Bounding sphere is copied by realloc. + sz = sizeof(MorphTarget) + this->numVertices*sizeof(V3d); + if(this->flags & NORMALS) + sz += this->numVertices*sizeof(V3d); + + MorphTarget *mt = (MorphTarget*)rwResize(this->morphTargets, + sz*this->numMorphTargets, MEMDUR_EVENT | ID_GEOMETRY); + this->morphTargets = mt; + V3d *vdata = (V3d*)&mt[this->numMorphTargets]; + for(int32 i = 0; i < this->numMorphTargets; i++){ + mt->parent = this; + mt->vertices = nil; + mt->normals = nil; + if(this->numVertices){ + mt->vertices = vdata; + vdata += this->numVertices; + if(this->flags & NORMALS){ + mt->normals = vdata; + vdata += this->numVertices; + } + } + mt++; + } +} + +static int +isDegenerate(uint16 *idx) +{ + return idx[0] == idx[1] || + idx[0] == idx[2] || + idx[1] == idx[2]; +} + +// This functions assumes there is enough space allocated +// for triangles. Use MeshHeader::guessNumTriangles() and +// Geometry::allocateData() +void +Geometry::generateTriangles(int8 *adc) +{ + MeshHeader *header = this->meshHeader; + assert(header != nil); + + this->numTriangles = 0; + Mesh *m = header->getMeshes(); + int8 *adcbits = adc; + for(uint32 i = 0; i < header->numMeshes; i++){ + if(m->numIndices < 3){ + // shouldn't happen but it does + adcbits += m->numIndices; + m++; + continue; + } + if(header->flags == MeshHeader::TRISTRIP){ + for(uint32 j = 0; j < m->numIndices-2; j++){ + if(!(adc && adcbits[j+2]) && + !isDegenerate(&m->indices[j])) + this->numTriangles++; + } + }else + this->numTriangles += m->numIndices/3; + adcbits += m->numIndices; + m++; + } + + Triangle *tri = this->triangles; + m = header->getMeshes(); + adcbits = adc; + for(uint32 i = 0; i < header->numMeshes; i++){ + if(m->numIndices < 3){ + adcbits += m->numIndices; + m++; + continue; + } + int32 matid = this->matList.findIndex(m->material); + if(header->flags == MeshHeader::TRISTRIP) + for(uint32 j = 0; j < m->numIndices-2; j++){ + if(adc && adcbits[j+2] || + isDegenerate(&m->indices[j])) + continue; + tri->v[0] = m->indices[j+0]; + tri->v[1] = m->indices[j+1 + (j%2)]; + tri->v[2] = m->indices[j+2 - (j%2)]; + tri->matId = matid; + tri++; + } + else + for(uint32 j = 0; j < m->numIndices-2; j+=3){ + tri->v[0] = m->indices[j+0]; + tri->v[1] = m->indices[j+1]; + tri->v[2] = m->indices[j+2]; + tri->matId = matid; + tri++; + } + adcbits += m->numIndices; + m++; + } +} + +static void +dumpMesh(Mesh *m) +{ + for(uint32 i = 0; i < m->numIndices-2; i++) +// if(i % 2) +// printf("%3d %3d %3d\n", +// m->indices[i+1], +// m->indices[i], +// m->indices[i+2]); +// else + printf("%d %d %d\n", + m->indices[i], + m->indices[i+1], + m->indices[i+2]); +} + +void +Geometry::buildMeshes(void) +{ + Triangle *tri; + Mesh *mesh; + + if(this->flags & Geometry::NATIVE){ + fprintf(stderr, "WARNING: trying Geometry::buildMeshes() on pre-instanced geometry\n"); + return; + } + + rwFree(this->meshHeader); + this->meshHeader = nil; + int32 numMeshes = this->matList.numMaterials; + if((this->flags & Geometry::TRISTRIP) == 0){ + int32 *numIndices = rwNewT(int32, numMeshes, + MEMDUR_FUNCTION | ID_GEOMETRY); + memset(numIndices, 0, numMeshes*sizeof(int32)); + + // count indices per mesh + tri = this->triangles; + for(int32 i = 0; i < this->numTriangles; i++){ + assert(tri->matId < numMeshes); + numIndices[tri->matId] += 3; + tri++; + } + // setup meshes + this->allocateMeshes(numMeshes, this->numTriangles*3, 0); + mesh = this->meshHeader->getMeshes(); + for(int32 i = 0; i < numMeshes; i++){ + mesh[i].material = this->matList.materials[i]; + mesh[i].numIndices = numIndices[i]; + } + this->meshHeader->setupIndices(); + rwFree(numIndices); + + // now fill in the indices + for(int32 i = 0; i < numMeshes; i++) + mesh[i].numIndices = 0; + tri = this->triangles; + for(int32 i = 0; i < this->numTriangles; i++){ + uint32 idx = mesh[tri->matId].numIndices; + mesh[tri->matId].indices[idx++] = tri->v[0]; + mesh[tri->matId].indices[idx++] = tri->v[1]; + mesh[tri->matId].indices[idx++] = tri->v[2]; + mesh[tri->matId].numIndices = idx; + tri++; + } + }else + this->buildTristrips(); +} + +/* The idea is that even in meshes where winding is not preserved + * every tristrip starts at an even vertex. So find the start of + * strips and insert duplicate vertices if necessary. */ +void +Geometry::correctTristripWinding(void) +{ + MeshHeader *header = this->meshHeader; + if(this->flags & NATIVE || header == nil || + header->flags != MeshHeader::TRISTRIP) + return; + this->meshHeader = nil; + // Allocate no indices, we realloc later + MeshHeader *newhead = this->allocateMeshes(header->numMeshes, 0, 1); + newhead->flags = header->flags; + /* get a temporary working buffer */ + uint16 *indices = rwNewT(uint16, header->totalIndices*2, + MEMDUR_FUNCTION | ID_GEOMETRY); + + Mesh *mesh = header->getMeshes(); + Mesh *newmesh = newhead->getMeshes(); + for(uint16 i = 0; i < header->numMeshes; i++){ + newmesh->numIndices = 0; + newmesh->indices = &indices[newhead->totalIndices]; + newmesh->material = mesh->material; + + bool inStrip = 0; + uint32 j; + for(j = 0; j < mesh->numIndices-2; j++){ + /* Duplicate vertices indicate end of strip */ + if(mesh->indices[j] == mesh->indices[j+1] || + mesh->indices[j+1] == mesh->indices[j+2]) + inStrip = 0; + else if(!inStrip){ + /* Entering strip now, + * make sure winding is correct */ + inStrip = 1; + if(newmesh->numIndices % 2){ + newmesh->indices[newmesh->numIndices] = + newmesh->indices[newmesh->numIndices-1]; + newmesh->numIndices++; + } + } + newmesh->indices[newmesh->numIndices++] = mesh->indices[j]; + } + for(; j < mesh->numIndices; j++) + newmesh->indices[newmesh->numIndices++] = mesh->indices[j]; + newhead->totalIndices += newmesh->numIndices; + + mesh++; + newmesh++; + } + rwFree(header); + // Now allocate indices and copy them + this->allocateMeshes(newhead->numMeshes, newhead->totalIndices, 0); + memcpy(this->meshHeader->getMeshes()->indices, indices, this->meshHeader->totalIndices*2); + rwFree(indices); +} + +void +Geometry::removeUnusedMaterials(void) +{ + if(this->meshHeader == nil) + return; + MeshHeader *mh = this->meshHeader; + Mesh *m = mh->getMeshes(); + for(uint32 i = 0; i < mh->numMeshes; i++) + if(m[i].indices == nil) + return; + + int32 *map = rwNewT(int32, this->matList.numMaterials, + MEMDUR_FUNCTION | ID_GEOMETRY); + Material **materials = rwNewT(Material*,this->matList.numMaterials, + MEMDUR_EVENT | ID_MATERIAL); + int32 numMaterials = 0; + /* Build new material list and map */ + for(uint32 i = 0; i < mh->numMeshes; i++){ + if(m[i].numIndices <= 0) + continue; + materials[numMaterials] = m[i].material; + m[i].material->addRef(); + int32 oldid = this->matList.findIndex(m[i].material); + map[oldid] = numMaterials; + numMaterials++; + } + for(int32 i = 0; i < this->matList.numMaterials; i++) + this->matList.materials[i]->destroy(); + rwFree(this->matList.materials); + this->matList.materials = materials; + this->matList.space = this->matList.numMaterials; + this->matList.numMaterials = numMaterials; + + /* Build new meshes */ + this->meshHeader = nil; + MeshHeader *newmh = this->allocateMeshes(numMaterials, mh->totalIndices, 0); + newmh->flags = mh->flags; + Mesh *newm = newmh->getMeshes(); + for(uint32 i = 0; i < mh->numMeshes; i++){ + if(m[i].numIndices <= 0) + continue; + newm->numIndices = m[i].numIndices; + newm->material = m[i].material; + newm++; + } + newmh->setupIndices(); + /* Copy indices */ + newm = newmh->getMeshes();; + for(uint32 i = 0; i < mh->numMeshes; i++){ + if(m[i].numIndices <= 0) + continue; + memcpy(newm->indices, m[i].indices, + m[i].numIndices*sizeof(*m[i].indices)); + newm++; + } + rwFree(mh); + + /* Remap triangle material IDs */ + for(int32 i = 0; i < this->numTriangles; i++) + this->triangles[i].matId = map[this->triangles[i].matId]; + rwFree(map); +} + +Sphere +MorphTarget::calculateBoundingSphere(void) const +{ + Sphere sphere; + V3d min = { 1000000.0f, 1000000.0f, 1000000.0f }; + V3d max = { -1000000.0f, -1000000.0f, -1000000.0f }; + V3d *v = this->vertices; + for(int32 j = 0; j < this->parent->numVertices; j++){ + if(v->x > max.x) max.x = v->x; + if(v->x < min.x) min.x = v->x; + if(v->y > max.y) max.y = v->y; + if(v->y < min.y) min.y = v->y; + if(v->z > max.z) max.z = v->z; + if(v->z < min.z) min.z = v->z; + v++; + } + sphere.center = scale(add(min, max), 1/2.0f); + max = sub(max, sphere.center); + sphere.radius = length(max); + return sphere; +} + + +// +// MaterialList +// +#undef PLUGIN_ID +#define PLUGIN_ID ID_MATERIAL + +void +MaterialList::init(void) +{ + this->materials = nil; + this->numMaterials = 0; + this->space = 0; +} + +void +MaterialList::deinit(void) +{ + if(this->materials){ + for(int32 i = 0; i < this->numMaterials; i++) + this->materials[i]->destroy(); + rwFree(this->materials); + } +} + +int32 +MaterialList::appendMaterial(Material *mat) +{ + Material **ml; + int32 space; + if(this->numMaterials >= this->space){ + space = this->space + 20; + if(this->materials) + ml = rwReallocT(Material*, this->materials, space, + MEMDUR_EVENT | ID_MATERIAL); + else + ml = rwMallocT(Material*, space, MEMDUR_EVENT | ID_MATERIAL); + if(ml == nil) + return -1; + this->space = space; + this->materials = ml; + } + this->materials[this->numMaterials++] = mat; + mat->addRef(); + return this->numMaterials-1; +} + +int32 +MaterialList::findIndex(Material *mat) +{ + for(int32 i = 0; i < this->numMaterials; i++) + if(this->materials[i] == mat) + return i; + return -1; +} + +MaterialList* +MaterialList::streamRead(Stream *stream, MaterialList *matlist) +{ + int32 *indices = nil; + int32 numMat; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + matlist->init(); + numMat = stream->readI32(); + if(numMat == 0) + return matlist; + matlist->materials = rwMallocT(Material*,numMat, MEMDUR_EVENT | ID_MATERIAL); + if(matlist->materials == nil) + goto fail; + matlist->space = numMat; + + indices = (int32*)rwMalloc(numMat*4, MEMDUR_FUNCTION | ID_MATERIAL); + stream->read32(indices, numMat*4); + + Material *m; + for(int32 i = 0; i < numMat; i++){ + if(indices[i] >= 0){ + m = matlist->materials[indices[i]]; + m->addRef(); + }else{ + if(!findChunk(stream, ID_MATERIAL, nil, nil)){ + RWERROR((ERR_CHUNK, "MATERIAL")); + goto fail; + } + m = Material::streamRead(stream); + if(m == nil) + goto fail; + } + matlist->appendMaterial(m); + m->destroy(); + } + rwFree(indices); + return matlist; +fail: + rwFree(indices); + matlist->deinit(); + return nil; +} + +bool +MaterialList::streamWrite(Stream *stream) +{ + uint32 size = this->streamGetSize(); + writeChunkHeader(stream, ID_MATLIST, size); + writeChunkHeader(stream, ID_STRUCT, 4 + this->numMaterials*4); + stream->writeI32(this->numMaterials); + + int32 idx; + for(int32 i = 0; i < this->numMaterials; i++){ + idx = -1; + for(int32 j = i-1; j >= 0; j--) + if(this->materials[i] == this->materials[j]){ + idx = j; + break; + } + stream->writeI32(idx); + } + for(int32 i = 0; i < this->numMaterials; i++){ + for(int32 j = i-1; j >= 0; j--) + if(this->materials[i] == this->materials[j]) + goto found; + this->materials[i]->streamWrite(stream); + found:; + } + return true; +} + +uint32 +MaterialList::streamGetSize(void) +{ + uint32 size = 12 + 4 + this->numMaterials*4; + for(int32 i = 0; i < this->numMaterials; i++){ + for(int32 j = i-1; j >= 0; j--) + if(this->materials[i] == this->materials[j]) + goto found; + size += 12 + this->materials[i]->streamGetSize(); + found:; + } + return size; +} + +// +// Material +// + +Material* +Material::create(void) +{ + Material *mat = (Material*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_MATERIAL); + if(mat == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + mat->texture = nil; + memset(&mat->color, 0xFF, 4); + mat->surfaceProps = defaultSurfaceProps; + mat->pipeline = nil; + mat->refCount = 1; + s_plglist.construct(mat); + return mat; +} + +Material* +Material::clone(void) +{ + Material *mat = Material::create(); + if(mat == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + mat->color = this->color; + mat->surfaceProps = this->surfaceProps; + if(this->texture) + mat->setTexture(this->texture); + mat->pipeline = this->pipeline; + s_plglist.copy(mat, this); + return mat; +} + +void +Material::destroy(void) +{ + this->refCount--; + if(this->refCount <= 0){ + s_plglist.destruct(this); + if(this->texture) + this->texture->destroy(); + rwFree(this); + numAllocated--; + } +} + +void +Material::setTexture(Texture *tex) +{ + if(this->texture) + this->texture->destroy(); + if(tex) + tex->addRef(); + this->texture = tex; +} + +struct MatStreamData +{ + int32 flags; // unused according to RW + RGBA color; + int32 unused; + int32 textured; +}; + +static uint32 materialRights[2]; + +Material* +Material::streamRead(Stream *stream) +{ + uint32 length, version; + MatStreamData buf; + + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(&buf, sizeof(buf)); + Material *mat = Material::create(); + if(mat == nil) + return nil; + mat->color = buf.color; + if(version < 0x30400) + mat->surfaceProps = defaultSurfaceProps; + else + stream->read32(&mat->surfaceProps, sizeof(SurfaceProperties)); + if(buf.textured){ + if(!findChunk(stream, ID_TEXTURE, &length, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + goto fail; + } + mat->texture = Texture::streamRead(stream); + } + + materialRights[0] = 0; + if(!s_plglist.streamRead(stream, mat)) + goto fail; + if(materialRights[0]) + s_plglist.assertRights(mat, materialRights[0], materialRights[1]); + return mat; + +fail: + mat->destroy(); + return nil; +} + +bool +Material::streamWrite(Stream *stream) +{ + MatStreamData buf; + + writeChunkHeader(stream, ID_MATERIAL, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, sizeof(MatStreamData) + + (rw::version >= 0x30400 ? 12 : 0)); + + buf.color = this->color; + buf.flags = 0; + buf.unused = 0; + buf.textured = this->texture != nil; + stream->write32(&buf, sizeof(buf)); + + if(rw::version >= 0x30400){ + float32 surfaceProps[3]; + surfaceProps[0] = this->surfaceProps.ambient; + surfaceProps[1] = this->surfaceProps.specular; + surfaceProps[2] = this->surfaceProps.diffuse; + stream->write32(surfaceProps, sizeof(surfaceProps)); + } + + if(this->texture) + this->texture->streamWrite(stream); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Material::streamGetSize(void) +{ + uint32 size = 0; + size += 12 + sizeof(MatStreamData); + if(rw::version >= 0x30400) + size += 12; + if(this->texture) + size += 12 + this->texture->streamGetSize(); + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +// Material Rights plugin + +static Stream* +readMaterialRights(Stream *stream, int32, void *, int32, int32) +{ + stream->read32(materialRights, 8); +// printf("materialrights: %X %X\n", materialRights[0], materialRights[1]); + return stream; +} + +static Stream* +writeMaterialRights(Stream *stream, int32, void *object, int32, int32) +{ + Material *material = (Material*)object; + uint32 buffer[2]; + buffer[0] = material->pipeline->pluginID; + buffer[1] = material->pipeline->pluginData; + stream->write32(buffer, 8); + return stream; +} + +static int32 +getSizeMaterialRights(void *object, int32, int32) +{ + Material *material = (Material*)object; + if(material->pipeline == nil || material->pipeline->pluginID == 0) + return 0; + return 8; +} + +void +registerMaterialRightsPlugin(void) +{ + Material::registerPlugin(0, ID_RIGHTTORENDER, nil, nil, nil); + Material::registerPluginStream(ID_RIGHTTORENDER, + readMaterialRights, + writeMaterialRights, + getSizeMaterialRights); +} + + +} diff --git a/src/geoplg.cpp b/src/geoplg.cpp new file mode 100644 index 0000000..c1e1356 --- /dev/null +++ b/src/geoplg.cpp @@ -0,0 +1,339 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" +#include "ps2/rwps2.h" +#include "ps2/rwps2plg.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwwdgl.h" +#include "gl/rwgl3.h" + +#define PLUGIN_ID 2 + +namespace rw { + +static uint16 nextSerialNum = 1; + +// Mesh + +// Allocate a mesh header, meshes and optionally indices. +// If existing meshes already exist, retain their information. +MeshHeader* +Geometry::allocateMeshes(int32 numMeshes, uint32 numIndices, bool32 noIndices) +{ + uint32 sz; + MeshHeader *mh; + Mesh *m; + uint16 *indices; + int32 oldNumMeshes; + int32 i; + sz = sizeof(MeshHeader) + numMeshes*sizeof(Mesh); + if(!noIndices) + sz += numIndices*sizeof(uint16); + if(this->meshHeader){ + oldNumMeshes = this->meshHeader->numMeshes; + mh = (MeshHeader*)rwResize(this->meshHeader, sz, MEMDUR_EVENT | ID_GEOMETRY); + this->meshHeader = mh; + }else{ + oldNumMeshes = 0; + mh = (MeshHeader*)rwNew(sz, MEMDUR_EVENT | ID_GEOMETRY); + mh->flags = 0; + this->meshHeader = mh; + } + mh->numMeshes = numMeshes; + mh->serialNum = nextSerialNum++; + mh->totalIndices = numIndices; + m = mh->getMeshes(); + indices = (uint16*)&m[numMeshes]; + for(i = 0; i < mh->numMeshes; i++){ + // keep these + if(i >= oldNumMeshes){ + m->material = nil; + m->numIndices = 0; + } + // always init indices + if(noIndices) + m->indices = nil; + else{ + m->indices = indices; + indices += m->numIndices; + } + m++; + } + return mh; +} + +void +MeshHeader::setupIndices(void) +{ + int32 i; + uint16 *indices; + Mesh *m; + m = this->getMeshes(); + indices = m->indices; + // return if native + if(indices == nil) + return; + for(i = 0; i < this->numMeshes; i++){ + m->indices = indices; + indices += m->numIndices; + m++; + } +} + +struct MeshHeaderStream +{ + uint32 flags; + uint32 numMeshes; + uint32 totalIndices; +}; + +struct MeshStream +{ + uint32 numIndices; + int32 matIndex; +}; + +static Stream* +readMesh(Stream *stream, int32 len, void *object, int32, int32) +{ + MeshHeaderStream mhs; + MeshStream ms; + MeshHeader *mh; + Mesh *mesh; + int32 indbuf[256]; + uint16 *indices; + Geometry *geo = (Geometry*)object; + + stream->read32(&mhs, sizeof(MeshHeaderStream)); + // Have to do this dance for War Drum's meshes + bool32 hasData = len > int32(sizeof(MeshHeaderStream)+mhs.numMeshes*sizeof(MeshStream)); + assert(geo->meshHeader == nil); + geo->meshHeader = nil; + mh = geo->allocateMeshes(mhs.numMeshes, mhs.totalIndices, + geo->flags & Geometry::NATIVE && !hasData); + mh->flags = mhs.flags; + + mesh = mh->getMeshes(); + indices = mesh->indices; + for(uint32 i = 0; i < mh->numMeshes; i++){ + stream->read32(&ms, sizeof(MeshStream)); + mesh->numIndices = ms.numIndices; + mesh->material = geo->matList.materials[ms.matIndex]; + if(geo->flags & Geometry::NATIVE){ + // War Drum OpenGL stores uint16 indices here + if(hasData){ + mesh->indices = indices; + indices += mesh->numIndices; + stream->read16(mesh->indices, + mesh->numIndices*2); + } + }else{ + mesh->indices = indices; + indices += mesh->numIndices; + uint16 *ind = mesh->indices; + int32 numIndices = mesh->numIndices; + for(; numIndices > 0; numIndices -= 256){ + int32 n = numIndices < 256 ? numIndices : 256; + stream->read32(indbuf, n*4); + for(int32 j = 0; j < n; j++) + ind[j] = indbuf[j]; + ind += n; + } + } + mesh++; + } + return stream; +} + +static Stream* +writeMesh(Stream *stream, int32, void *object, int32, int32) +{ + MeshHeaderStream mhs; + MeshStream ms; + int32 indbuf[256]; + Geometry *geo = (Geometry*)object; + mhs.flags = geo->meshHeader->flags; + mhs.numMeshes = geo->meshHeader->numMeshes; + mhs.totalIndices = geo->meshHeader->totalIndices; + stream->write32(&mhs, sizeof(MeshHeaderStream)); + Mesh *mesh = geo->meshHeader->getMeshes(); + for(uint32 i = 0; i < geo->meshHeader->numMeshes; i++){ + ms.numIndices = mesh->numIndices; + ms.matIndex = geo->matList.findIndex(mesh->material); + stream->write32(&ms, sizeof(MeshStream)); + if(geo->flags & Geometry::NATIVE){ + assert(geo->instData != nil); + if(geo->instData->platform == PLATFORM_WDGL) + stream->write16(mesh->indices, + mesh->numIndices*2); + }else{ + uint16 *ind = mesh->indices; + int32 numIndices = mesh->numIndices; + for(; numIndices > 0; numIndices -= 256){ + int32 n = numIndices < 256 ? numIndices : 256; + for(int32 j = 0; j < n; j++) + indbuf[j] = ind[j]; + stream->write32(indbuf, n*4); + ind += n; + } + } + mesh++; + } + return stream; +} + +static int32 +getSizeMesh(void *object, int32, int32) +{ + Geometry *geo = (Geometry*)object; + if(geo->meshHeader == nil) + return -1; + int32 size = 12 + geo->meshHeader->numMeshes*8; + if(geo->flags & Geometry::NATIVE){ + assert(geo->instData != nil); + if(geo->instData->platform == PLATFORM_WDGL) + size += geo->meshHeader->totalIndices*2; + }else{ + size += geo->meshHeader->totalIndices*4; + } + return size; +} + +void +registerMeshPlugin(void) +{ + Geometry::registerPlugin(0, ID_MESH, nil, nil, nil); + Geometry::registerPluginStream(ID_MESH, readMesh, writeMesh, getSizeMesh); +} + +// Returns the maximum number of triangles. Just so +// we can allocate enough before instancing. This does not +// take into account degerate triangles or ADC bits as +// we don't look at the data. +uint32 +MeshHeader::guessNumTriangles(void) +{ + if(this->flags == MeshHeader::TRISTRIP) + return this->totalIndices - 2*this->numMeshes; + else + return this->totalIndices/3; +} + +// Native Data + +static void* +destroyNativeData(void *object, int32 offset, int32 size) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil) + return object; + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_D3D8) + return d3d8::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_D3D9) + return d3d9::destroyNativeData(object, offset, size); + if(geometry->instData->platform == PLATFORM_GL3) + return gl3::destroyNativeData(object, offset, size); + return object; +} + +static Stream* +readNativeData(Stream *stream, int32 len, void *object, int32 o, int32 s) +{ + ChunkHeaderInfo header; + uint32 libid; + uint32 platform; + // ugly hack to find out platform + stream->seek(-4); + libid = stream->readU32(); + readChunkHeaderInfo(stream, &header); + if(header.type == ID_STRUCT && + libraryIDPack(header.version, header.build) == libid){ + platform = stream->readU32(); + stream->seek(-16); + if(platform == PLATFORM_PS2) + return ps2::readNativeData(stream, len, object, o, s); + else if(platform == PLATFORM_XBOX) + return xbox::readNativeData(stream, len, object, o, s); + else if(platform == PLATFORM_D3D8) + return d3d8::readNativeData(stream, len, object, o, s); + else if(platform == PLATFORM_D3D9) + return d3d9::readNativeData(stream, len, object, o, s); + else{ + fprintf(stderr, "unknown platform %d\n", platform); + stream->seek(len); + } + }else{ + stream->seek(-12); + wdgl::readNativeData(stream, len, object, o, s); + } + return stream; +} + +static Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32 o, int32 s) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil) + return stream; + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::writeNativeData(stream, len, object, o, s); + else if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::writeNativeData(stream, len, object, o, s); + else if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::writeNativeData(stream, len, object, o, s); + else if(geometry->instData->platform == PLATFORM_D3D8) + return d3d8::writeNativeData(stream, len, object, o, s); + else if(geometry->instData->platform == PLATFORM_D3D9) + return d3d9::writeNativeData(stream, len, object, o, s); + return stream; +} + +static int32 +getSizeNativeData(void *object, int32 offset, int32 size) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil) + return 0; + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::getSizeNativeData(object, offset, size); + else if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::getSizeNativeData(object, offset, size); + else if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::getSizeNativeData(object, offset, size); + else if(geometry->instData->platform == PLATFORM_D3D8) + return d3d8::getSizeNativeData(object, offset, size); + else if(geometry->instData->platform == PLATFORM_D3D9) + return d3d9::getSizeNativeData(object, offset, size); + return 0; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +} diff --git a/src/gl/gl2_shaders/Makefile b/src/gl/gl2_shaders/Makefile new file mode 100644 index 0000000..76d83a5 --- /dev/null +++ b/src/gl/gl2_shaders/Makefile @@ -0,0 +1,45 @@ +all: header_vs.inc header_fs.inc im2d_gl2.inc im3d_gl2.inc default_vs_gl2.inc simple_fs_gl2.inc matfx_gl2.inc skin_gl2.inc + +header_vs.inc: header.vert + (echo 'const char *header_vert_src =';\ + sed 's/..*/"&\\n"/' header.vert;\ + echo ';') >header_vs.inc + +header_fs.inc: header.frag + (echo 'const char *header_frag_src =';\ + sed 's/..*/"&\\n"/' header.frag;\ + echo ';') >header_fs.inc + +im2d_gl2.inc: im2d.vert + (echo 'const char *im2d_vert_src =';\ + sed 's/..*/"&\\n"/' im2d.vert;\ + echo ';') >im2d_gl2.inc + +im3d_gl2.inc: im3d.vert + (echo 'const char *im3d_vert_src =';\ + sed 's/..*/"&\\n"/' im3d.vert;\ + echo ';') >im3d_gl2.inc + +default_vs_gl2.inc: default.vert + (echo 'const char *default_vert_src =';\ + sed 's/..*/"&\\n"/' default.vert;\ + echo ';') >default_vs_gl2.inc + +simple_fs_gl2.inc: simple.frag + (echo 'const char *simple_frag_src =';\ + sed 's/..*/"&\\n"/' simple.frag;\ + echo ';') >simple_fs_gl2.inc + +matfx_gl2.inc: matfx_env.frag matfx_env.vert + (echo 'const char *matfx_env_vert_src =';\ + sed 's/..*/"&\\n"/' matfx_env.vert;\ + echo ';';\ + echo 'const char *matfx_env_frag_src =';\ + sed 's/..*/"&\\n"/' matfx_env.frag;\ + echo ';') >matfx_gl2.inc + +skin_gl2.inc: skin.vert + (echo 'const char *skin_vert_src =';\ + sed 's/..*/"&\\n"/' skin.vert;\ + echo ';') >skin_gl2.inc + diff --git a/src/gl/gl2_shaders/default.vert b/src/gl/gl2_shaders/default.vert new file mode 100644 index 0000000..6b2c9d7 --- /dev/null +++ b/src/gl/gl2_shaders/default.vert @@ -0,0 +1,26 @@ +attribute vec3 in_pos; +attribute vec3 in_normal; +attribute vec4 in_color; +attribute vec2 in_tex0; + +varying vec4 v_color; +varying vec2 v_tex0; +varying float v_fog; + +void +main(void) +{ + vec4 V = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * V; + vec3 N = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(V.xyz, N)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/gl/gl2_shaders/default_vs_gl2.inc b/src/gl/gl2_shaders/default_vs_gl2.inc new file mode 100644 index 0000000..7d2b77a --- /dev/null +++ b/src/gl/gl2_shaders/default_vs_gl2.inc @@ -0,0 +1,28 @@ +const char *default_vert_src = +"attribute vec3 in_pos;\n" +"attribute vec3 in_normal;\n" +"attribute vec4 in_color;\n" +"attribute vec2 in_tex0;\n" + +"varying vec4 v_color;\n" +"varying vec2 v_tex0;\n" +"varying float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 V = u_world * vec4(in_pos, 1.0);\n" +" gl_Position = u_proj * u_view * V;\n" +" vec3 N = mat3(u_world) * in_normal;\n" + +" v_tex0 = in_tex0;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(V.xyz, N)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; diff --git a/src/gl/gl2_shaders/header.frag b/src/gl/gl2_shaders/header.frag new file mode 100644 index 0000000..b29654d --- /dev/null +++ b/src/gl/gl2_shaders/header.frag @@ -0,0 +1,15 @@ +uniform vec2 u_alphaRef; + +uniform vec4 u_fogData; +uniform vec4 u_fogColor; + +#define u_fogStart (u_fogData.x) +#define u_fogEnd (u_fogData.y) +#define u_fogRange (u_fogData.z) +#define u_fogDisable (u_fogData.w) + +void DoAlphaTest(float a) +{ + if(a < u_alphaRef.x || a >= u_alphaRef.y) + discard; +} diff --git a/src/gl/gl2_shaders/header.vert b/src/gl/gl2_shaders/header.vert new file mode 100644 index 0000000..11894ee --- /dev/null +++ b/src/gl/gl2_shaders/header.vert @@ -0,0 +1,72 @@ +// State +uniform vec4 u_fogData; +//uniform vec4 u_fogColor; + +#define u_fogStart (u_fogData.x) +#define u_fogEnd (u_fogData.y) +#define u_fogRange (u_fogData.z) +#define u_fogDisable (u_fogData.w) + +// Scene +uniform mat4 u_proj; +uniform mat4 u_view; + +#define MAX_LIGHTS 8 + +// Object +uniform mat4 u_world; +uniform vec4 u_ambLight; +uniform vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot +uniform vec4 u_lightPosition[MAX_LIGHTS]; +uniform vec4 u_lightDirection[MAX_LIGHTS]; +uniform vec4 u_lightColor[MAX_LIGHTS]; + +uniform vec4 u_matColor; +uniform vec4 u_surfProps; // amb, spec, diff, extra + +#define surfAmbient (u_surfProps.x) +#define surfSpecular (u_surfProps.y) +#define surfDiffuse (u_surfProps.z) + +vec3 DoDynamicLight(vec3 V, vec3 N) +{ + vec3 color = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < MAX_LIGHTS; i++){ + if(u_lightParams[i].x == 0.0) + break; + if(u_lightParams[i].x == 1.0){ + // direct + float l = max(0.0, dot(N, -u_lightDirection[i].xyz)); + color += l*u_lightColor[i].rgb; +/* + }else if(u_lightParams[i].x == 2.0){ + // point + vec3 dir = V - u_lightPosition[i].xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/u_lightParams[i].y)); + float l = max(0.0, dot(N, -normalize(dir))); + color += l*u_lightColor[i].rgb*atten; + }else if(u_lightParams[i].x == 3.0){ + // spot + vec3 dir = V - u_lightPosition[i].xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/u_lightParams[i].y)); + dir /= dist; + float l = max(0.0, dot(N, -dir)); + float pcos = dot(dir, u_lightDirection[i].xyz); // cos to point + float ccos = -u_lightParams[i].z; + float falloff = (pcos-ccos)/(1.0-ccos); + if(falloff < 0.0) // outside of cone + l = 0.0; + l *= max(falloff, u_lightParams[i].w); + return l*u_lightColor[i].rgb*atten; +*/ + } + } + return color; +} + +float DoFog(float w) +{ + return clamp((w - u_fogEnd)*u_fogRange, u_fogDisable, 1.0); +} diff --git a/src/gl/gl2_shaders/header_fs.inc b/src/gl/gl2_shaders/header_fs.inc new file mode 100644 index 0000000..2f12037 --- /dev/null +++ b/src/gl/gl2_shaders/header_fs.inc @@ -0,0 +1,17 @@ +const char *header_frag_src = +"uniform vec2 u_alphaRef;\n" + +"uniform vec4 u_fogData;\n" +"uniform vec4 u_fogColor;\n" + +"#define u_fogStart (u_fogData.x)\n" +"#define u_fogEnd (u_fogData.y)\n" +"#define u_fogRange (u_fogData.z)\n" +"#define u_fogDisable (u_fogData.w)\n" + +"void DoAlphaTest(float a)\n" +"{\n" +" if(a < u_alphaRef.x || a >= u_alphaRef.y)\n" +" discard;\n" +"}\n" +; diff --git a/src/gl/gl2_shaders/header_vs.inc b/src/gl/gl2_shaders/header_vs.inc new file mode 100644 index 0000000..12360c1 --- /dev/null +++ b/src/gl/gl2_shaders/header_vs.inc @@ -0,0 +1,74 @@ +const char *header_vert_src = +"// State\n" +"uniform vec4 u_fogData;\n" +"//uniform vec4 u_fogColor;\n" + +"#define u_fogStart (u_fogData.x)\n" +"#define u_fogEnd (u_fogData.y)\n" +"#define u_fogRange (u_fogData.z)\n" +"#define u_fogDisable (u_fogData.w)\n" + +"// Scene\n" +"uniform mat4 u_proj;\n" +"uniform mat4 u_view;\n" + +"#define MAX_LIGHTS 8\n" + +"// Object\n" +"uniform mat4 u_world;\n" +"uniform vec4 u_ambLight;\n" +"uniform vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot\n" +"uniform vec4 u_lightPosition[MAX_LIGHTS];\n" +"uniform vec4 u_lightDirection[MAX_LIGHTS];\n" +"uniform vec4 u_lightColor[MAX_LIGHTS];\n" + +"uniform vec4 u_matColor;\n" +"uniform vec4 u_surfProps; // amb, spec, diff, extra\n" + +"#define surfAmbient (u_surfProps.x)\n" +"#define surfSpecular (u_surfProps.y)\n" +"#define surfDiffuse (u_surfProps.z)\n" + +"vec3 DoDynamicLight(vec3 V, vec3 N)\n" +"{\n" +" vec3 color = vec3(0.0, 0.0, 0.0);\n" +" for(int i = 0; i < MAX_LIGHTS; i++){\n" +" if(u_lightParams[i].x == 0.0)\n" +" break;\n" +" if(u_lightParams[i].x == 1.0){\n" +" // direct\n" +" float l = max(0.0, dot(N, -u_lightDirection[i].xyz));\n" +" color += l*u_lightColor[i].rgb;\n" +"/*\n" +" }else if(u_lightParams[i].x == 2.0){\n" +" // point\n" +" vec3 dir = V - u_lightPosition[i].xyz;\n" +" float dist = length(dir);\n" +" float atten = max(0.0, (1.0 - dist/u_lightParams[i].y));\n" +" float l = max(0.0, dot(N, -normalize(dir)));\n" +" color += l*u_lightColor[i].rgb*atten;\n" +" }else if(u_lightParams[i].x == 3.0){\n" +" // spot\n" +" vec3 dir = V - u_lightPosition[i].xyz;\n" +" float dist = length(dir);\n" +" float atten = max(0.0, (1.0 - dist/u_lightParams[i].y));\n" +" dir /= dist;\n" +" float l = max(0.0, dot(N, -dir));\n" +" float pcos = dot(dir, u_lightDirection[i].xyz); // cos to point\n" +" float ccos = -u_lightParams[i].z;\n" +" float falloff = (pcos-ccos)/(1.0-ccos);\n" +" if(falloff < 0.0) // outside of cone\n" +" l = 0.0;\n" +" l *= max(falloff, u_lightParams[i].w);\n" +" return l*u_lightColor[i].rgb*atten;\n" +"*/\n" +" }\n" +" }\n" +" return color;\n" +"}\n" + +"float DoFog(float w)\n" +"{\n" +" return clamp((w - u_fogEnd)*u_fogRange, u_fogDisable, 1.0);\n" +"}\n" +; diff --git a/src/gl/gl2_shaders/im2d.vert b/src/gl/gl2_shaders/im2d.vert new file mode 100644 index 0000000..1947785 --- /dev/null +++ b/src/gl/gl2_shaders/im2d.vert @@ -0,0 +1,20 @@ +uniform vec4 u_xform; + +attribute vec4 in_pos; +attribute vec4 in_color; +attribute vec2 in_tex0; + +varying vec4 v_color; +varying vec2 v_tex0; +varying float v_fog; + +void +main(void) +{ + gl_Position = in_pos; + gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw; + v_fog = DoFog(gl_Position.z); + gl_Position.xyz *= gl_Position.w; + v_color = in_color; + v_tex0 = in_tex0; +} diff --git a/src/gl/gl2_shaders/im2d_gl2.inc b/src/gl/gl2_shaders/im2d_gl2.inc new file mode 100644 index 0000000..6e8798b --- /dev/null +++ b/src/gl/gl2_shaders/im2d_gl2.inc @@ -0,0 +1,22 @@ +const char *im2d_vert_src = +"uniform vec4 u_xform;\n" + +"attribute vec4 in_pos;\n" +"attribute vec4 in_color;\n" +"attribute vec2 in_tex0;\n" + +"varying vec4 v_color;\n" +"varying vec2 v_tex0;\n" +"varying float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" gl_Position = in_pos;\n" +" gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw;\n" +" v_fog = DoFog(gl_Position.z);\n" +" gl_Position.xyz *= gl_Position.w;\n" +" v_color = in_color;\n" +" v_tex0 = in_tex0;\n" +"}\n" +; diff --git a/src/gl/gl2_shaders/im3d.vert b/src/gl/gl2_shaders/im3d.vert new file mode 100644 index 0000000..f436a1a --- /dev/null +++ b/src/gl/gl2_shaders/im3d.vert @@ -0,0 +1,18 @@ +attribute vec3 in_pos; +attribute vec4 in_color; +attribute vec2 in_tex0; + +varying vec4 v_color; +varying vec2 v_tex0; +varying float v_fog; + +void +main(void) +{ + vec4 V = u_world * vec4(in_pos, 1.0); + vec4 cV = u_view * V; + gl_Position = u_proj * cV; + v_color = in_color; + v_tex0 = in_tex0; + v_fog = DoFog(gl_Position.w); +} diff --git a/src/gl/gl2_shaders/im3d_gl2.inc b/src/gl/gl2_shaders/im3d_gl2.inc new file mode 100644 index 0000000..eb33fe5 --- /dev/null +++ b/src/gl/gl2_shaders/im3d_gl2.inc @@ -0,0 +1,20 @@ +const char *im3d_vert_src = +"attribute vec3 in_pos;\n" +"attribute vec4 in_color;\n" +"attribute vec2 in_tex0;\n" + +"varying vec4 v_color;\n" +"varying vec2 v_tex0;\n" +"varying float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 V = u_world * vec4(in_pos, 1.0);\n" +" vec4 cV = u_view * V;\n" +" gl_Position = u_proj * cV;\n" +" v_color = in_color;\n" +" v_tex0 = in_tex0;\n" +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; diff --git a/src/gl/gl2_shaders/matfx_env.frag b/src/gl/gl2_shaders/matfx_env.frag new file mode 100644 index 0000000..0bb5091 --- /dev/null +++ b/src/gl/gl2_shaders/matfx_env.frag @@ -0,0 +1,36 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +uniform vec2 u_fxparams; +uniform vec4 u_colorClamp; + +#define shininess (u_fxparams.x) +#define disableFBA (u_fxparams.y) + +varying vec4 v_color; +varying vec2 v_tex0; +varying vec2 v_tex1; +varying float v_fog; + +void +main(void) +{ + vec4 color; + + vec4 pass1 = v_color; + vec4 envColor = max(pass1, u_colorClamp); + pass1 *= texture2D(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + + vec4 pass2 = envColor*shininess*texture2D(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)); + + pass1.rgb = mix(u_fogColor.rgb, pass1.rgb, v_fog); + pass2.rgb = mix(vec3(0.0, 0.0, 0.0), pass2.rgb, v_fog); + + float fba = max(pass1.a, disableFBA); + color.rgb = pass1.rgb*pass1.a + pass2.rgb*fba; + color.a = pass1.a; + + DoAlphaTest(color.a); + + gl_FragColor = color; +} diff --git a/src/gl/gl2_shaders/matfx_env.vert b/src/gl/gl2_shaders/matfx_env.vert new file mode 100644 index 0000000..7e8e45f --- /dev/null +++ b/src/gl/gl2_shaders/matfx_env.vert @@ -0,0 +1,30 @@ +uniform mat4 u_texMatrix; + +attribute vec3 in_pos; +attribute vec3 in_normal; +attribute vec4 in_color; +attribute vec2 in_tex0; + +varying vec4 v_color; +varying vec2 v_tex0; +varying vec2 v_tex1; +varying float v_fog; + +void +main(void) +{ + vec4 V = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * V; + vec3 N = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + v_tex1 = (u_texMatrix * vec4(N, 1.0)).xy; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(V.xyz, N)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/gl/gl2_shaders/matfx_gl2.inc b/src/gl/gl2_shaders/matfx_gl2.inc new file mode 100644 index 0000000..7a357bd --- /dev/null +++ b/src/gl/gl2_shaders/matfx_gl2.inc @@ -0,0 +1,70 @@ +const char *matfx_env_vert_src = +"uniform mat4 u_texMatrix;\n" + +"attribute vec3 in_pos;\n" +"attribute vec3 in_normal;\n" +"attribute vec4 in_color;\n" +"attribute vec2 in_tex0;\n" + +"varying vec4 v_color;\n" +"varying vec2 v_tex0;\n" +"varying vec2 v_tex1;\n" +"varying float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 V = u_world * vec4(in_pos, 1.0);\n" +" gl_Position = u_proj * u_view * V;\n" +" vec3 N = mat3(u_world) * in_normal;\n" + +" v_tex0 = in_tex0;\n" +" v_tex1 = (u_texMatrix * vec4(N, 1.0)).xy;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(V.xyz, N)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; +const char *matfx_env_frag_src = +"uniform sampler2D tex0;\n" +"uniform sampler2D tex1;\n" + +"uniform vec2 u_fxparams;\n" +"uniform vec4 u_colorClamp;\n" + +"#define shininess (u_fxparams.x)\n" +"#define disableFBA (u_fxparams.y)\n" + +"varying vec4 v_color;\n" +"varying vec2 v_tex0;\n" +"varying vec2 v_tex1;\n" +"varying float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 color;\n" + +" vec4 pass1 = v_color;\n" +" vec4 envColor = max(pass1, u_colorClamp);\n" +" pass1 *= texture2D(tex0, vec2(v_tex0.x, 1.0-v_tex0.y));\n" + +" vec4 pass2 = envColor*shininess*texture2D(tex1, vec2(v_tex1.x, 1.0-v_tex1.y));\n" + +" pass1.rgb = mix(u_fogColor.rgb, pass1.rgb, v_fog);\n" +" pass2.rgb = mix(vec3(0.0, 0.0, 0.0), pass2.rgb, v_fog);\n" + +" float fba = max(pass1.a, disableFBA);\n" +" color.rgb = pass1.rgb*pass1.a + pass2.rgb*fba;\n" +" color.a = pass1.a;\n" + +" DoAlphaTest(color.a);\n" + +" gl_FragColor = color;\n" +"}\n" +; diff --git a/src/gl/gl2_shaders/simple.frag b/src/gl/gl2_shaders/simple.frag new file mode 100644 index 0000000..becbbf7 --- /dev/null +++ b/src/gl/gl2_shaders/simple.frag @@ -0,0 +1,16 @@ +uniform sampler2D tex0; + +varying vec4 v_color; +varying vec2 v_tex0; +varying float v_fog; + +void +main(void) +{ + vec4 color; + color = v_color*texture2D(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); + gl_FragColor = color; +} + diff --git a/src/gl/gl2_shaders/simple_fs_gl2.inc b/src/gl/gl2_shaders/simple_fs_gl2.inc new file mode 100644 index 0000000..3e95645 --- /dev/null +++ b/src/gl/gl2_shaders/simple_fs_gl2.inc @@ -0,0 +1,18 @@ +const char *simple_frag_src = +"uniform sampler2D tex0;\n" + +"varying vec4 v_color;\n" +"varying vec2 v_tex0;\n" +"varying float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 color;\n" +" color = v_color*texture2D(tex0, vec2(v_tex0.x, 1.0-v_tex0.y));\n" +" color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog);\n" +" DoAlphaTest(color.a);\n" +" gl_FragColor = color;\n" +"}\n" + +; diff --git a/src/gl/gl2_shaders/skin.vert b/src/gl/gl2_shaders/skin.vert new file mode 100644 index 0000000..e79db10 --- /dev/null +++ b/src/gl/gl2_shaders/skin.vert @@ -0,0 +1,37 @@ +uniform mat4 u_boneMatrices[64]; + +attribute vec3 in_pos; +attribute vec3 in_normal; +attribute vec4 in_color; +attribute vec2 in_tex0; +attribute vec4 in_weights; +attribute vec4 in_indices; + +varying vec4 v_color; +varying vec2 v_tex0; +varying float v_fog; + +void +main(void) +{ + vec3 SkinVertex = vec3(0.0, 0.0, 0.0); + vec3 SkinNormal = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < 4; i++){ + SkinVertex += (u_boneMatrices[int(in_indices[i])] * vec4(in_pos, 1.0)).xyz * in_weights[i]; + SkinNormal += (mat3(u_boneMatrices[int(in_indices[i])]) * in_normal) * in_weights[i]; + } + + vec4 V = u_world * vec4(SkinVertex, 1.0); + gl_Position = u_proj * u_view * V; + vec3 N = mat3(u_world) * SkinNormal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(V.xyz, N)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.z); +} diff --git a/src/gl/gl2_shaders/skin_gl2.inc b/src/gl/gl2_shaders/skin_gl2.inc new file mode 100644 index 0000000..7e52c4e --- /dev/null +++ b/src/gl/gl2_shaders/skin_gl2.inc @@ -0,0 +1,39 @@ +const char *skin_vert_src = +"uniform mat4 u_boneMatrices[64];\n" + +"attribute vec3 in_pos;\n" +"attribute vec3 in_normal;\n" +"attribute vec4 in_color;\n" +"attribute vec2 in_tex0;\n" +"attribute vec4 in_weights;\n" +"attribute vec4 in_indices;\n" + +"varying vec4 v_color;\n" +"varying vec2 v_tex0;\n" +"varying float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec3 SkinVertex = vec3(0.0, 0.0, 0.0);\n" +" vec3 SkinNormal = vec3(0.0, 0.0, 0.0);\n" +" for(int i = 0; i < 4; i++){\n" +" SkinVertex += (u_boneMatrices[int(in_indices[i])] * vec4(in_pos, 1.0)).xyz * in_weights[i];\n" +" SkinNormal += (mat3(u_boneMatrices[int(in_indices[i])]) * in_normal) * in_weights[i];\n" +" }\n" + +" vec4 V = u_world * vec4(SkinVertex, 1.0);\n" +" gl_Position = u_proj * u_view * V;\n" +" vec3 N = mat3(u_world) * SkinNormal;\n" + +" v_tex0 = in_tex0;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(V.xyz, N)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.z);\n" +"}\n" +; diff --git a/src/gl/gl3.cpp b/src/gl/gl3.cpp new file mode 100644 index 0000000..1fe6995 --- /dev/null +++ b/src/gl/gl3.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwgl3.h" +#include "rwgl3shader.h" + +#include "rwgl3impl.h" + +namespace rw { +namespace gl3 { + +// TODO: make some of these things platform-independent + +static void* +driverOpen(void *o, int32, int32) +{ +#ifdef RW_OPENGL + engine->driver[PLATFORM_GL3]->defaultPipeline = makeDefaultPipeline(); +#endif + engine->driver[PLATFORM_GL3]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_GL3]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_GL3]->rasterLock = rasterLock; + engine->driver[PLATFORM_GL3]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_GL3]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_GL3]->imageFindRasterFormat = imageFindRasterFormat; + engine->driver[PLATFORM_GL3]->rasterFromImage = rasterFromImage; + + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_GL3, 0, PLATFORM_GL3, + driverOpen, driverClose); + registerNativeRaster(); +} + +} +} diff --git a/src/gl/gl3device.cpp b/src/gl/gl3device.cpp new file mode 100644 index 0000000..efcb02d --- /dev/null +++ b/src/gl/gl3device.cpp @@ -0,0 +1,1761 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#ifdef RW_OPENGL +#include "rwgl3.h" +#include "rwgl3shader.h" +#include "rwgl3impl.h" + +#define PLUGIN_ID 0 + +namespace rw { +namespace gl3 { +#ifndef LIBRW_SDL2 +struct DisplayMode +{ + GLFWvidmode mode; + int32 depth; + uint32 flags; +}; +#endif + +struct GlGlobals +{ +#ifdef LIBRW_SDL2 + SDL_Window *window; + SDL_GLContext glcontext; +#else + GLFWwindow *window; + + GLFWmonitor *monitor; + int numMonitors; + int currentMonitor; + + DisplayMode *modes; + int numModes; + int currentMode; + GLFWwindow **pWindow; +#endif + int presentWidth, presentHeight; + + // for opening the window + int winWidth, winHeight; + const char *winTitle; +} glGlobals; + +int32 alphaFunc; +float32 alphaRef; + +struct UniformState +{ + float32 alphaRefLow; + float32 alphaRefHigh; + int32 pad[2]; + + float32 fogStart; + float32 fogEnd; + float32 fogRange; + float32 fogDisable; + + RGBAf fogColor; +}; + +struct UniformScene +{ + float32 proj[16]; + float32 view[16]; +}; + +#define MAX_LIGHTS 8 + +struct UniformObject +{ + RawMatrix world; + RGBAf ambLight; + struct { + float type; + float radius; + float minusCosAngle; + float hardSpot; + } lightParams[MAX_LIGHTS]; + V4d lightPosition[MAX_LIGHTS]; + V4d lightDirection[MAX_LIGHTS]; + RGBAf lightColor[MAX_LIGHTS]; +}; + +struct GLShaderState +{ + RGBA matColor; + SurfaceProperties surfProps; +}; + +const char *shaderDecl330 = "#version 330\n"; +const char *shaderDecl100es = +"#version 100\n"\ +"precision highp float;\n"\ +"precision highp int;\n"; +const char *shaderDecl310es = +"#version 310 es\n"\ +"precision highp float;\n"\ +"precision highp int;\n"; + +#ifdef RW_GLES3 +const char *shaderDecl = shaderDecl310es; +#elif defined RW_GLES2 +const char *shaderDecl = shaderDecl100es; +#else +const char *shaderDecl = shaderDecl330; +#endif + +// this needs a define in the shaders as well! +//#define RW_GL_USE_UBOS + +#ifndef RW_GLES2 +static GLuint vao; +#endif +#ifdef RW_GL_USE_UBOS +static GLuint ubo_state, ubo_scene, ubo_object; +#endif +static GLuint whitetex; +static UniformState uniformState; +static UniformScene uniformScene; +static UniformObject uniformObject; +static GLShaderState shaderState; + +#ifndef RW_GL_USE_UBOS +// State +int32 u_alphaRef; +int32 u_fogData; +//int32 u_fogStart; +//int32 u_fogEnd; +//int32 u_fogRange; +//int32 u_fogDisable; +int32 u_fogColor; + +// Scene +int32 u_proj; +int32 u_view; + +// Object +int32 u_world; +int32 u_ambLight; +int32 u_lightParams; +int32 u_lightPosition; +int32 u_lightDirection; +int32 u_lightColor; +#endif + +int32 u_matColor; +int32 u_surfProps; + +Shader *defaultShader; + +static bool32 stateDirty = 1; +static bool32 sceneDirty = 1; +static bool32 objectDirty = 1; + +struct RwRasterStateCache { + Raster *raster; + Texture::Addressing addressingU; + Texture::Addressing addressingV; + Texture::FilterMode filter; +}; + +#define MAXNUMSTAGES 8 + +// cached RW render states +struct RwStateCache { + bool32 vertexAlpha; + uint32 alphaTestEnable; + uint32 alphaFunc; + bool32 textureAlpha; + bool32 blendEnable; + uint32 srcblend, destblend; + uint32 zwrite; + uint32 ztest; + uint32 cullmode; + uint32 fogEnable; + float32 fogStart; + float32 fogEnd; + + // emulation of PS2 GS + bool32 gsalpha; + uint32 gsalpharef; + + RwRasterStateCache texstage[MAXNUMSTAGES]; +}; +static RwStateCache rwStateCache; + +enum +{ + // actual gl states + RWGL_BLEND, + RWGL_SRCBLEND, + RWGL_DESTBLEND, + RWGL_DEPTHTEST, + RWGL_DEPTHFUNC, + RWGL_DEPTHMASK, + RWGL_CULL, + RWGL_CULLFACE, + + // uniforms + RWGL_ALPHAFUNC, + RWGL_ALPHAREF, + RWGL_FOG, + RWGL_FOGSTART, + RWGL_FOGEND, + RWGL_FOGCOLOR, + + RWGL_NUM_STATES +}; +static bool uniformStateDirty[RWGL_NUM_STATES]; + +struct GlState { + bool32 blendEnable; + uint32 srcblend, destblend; + + bool32 depthTest; + uint32 depthFunc; + + uint32 depthMask; + + bool32 cullEnable; + uint32 cullFace; +}; +static GlState curGlState, oldGlState; + +static int32 activeTexture; +static uint32 boundTexture[MAXNUMSTAGES]; + +static uint32 currentFramebuffer; + +static uint32 blendMap[] = { + GL_ZERO, // actually invalid + GL_ZERO, + GL_ONE, + GL_SRC_COLOR, + GL_ONE_MINUS_SRC_COLOR, + GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, + GL_ONE_MINUS_DST_ALPHA, + GL_DST_COLOR, + GL_ONE_MINUS_DST_COLOR, + GL_SRC_ALPHA_SATURATE, +}; + +/* + * GL state cache + */ + +void +setGlRenderState(uint32 state, uint32 value) +{ + switch(state){ + case RWGL_BLEND: curGlState.blendEnable = value; break; + case RWGL_SRCBLEND: curGlState.srcblend = value; break; + case RWGL_DESTBLEND: curGlState.destblend = value; break; + case RWGL_DEPTHTEST: curGlState.depthTest = value; break; + case RWGL_DEPTHFUNC: curGlState.depthFunc = value; break; + case RWGL_DEPTHMASK: curGlState.depthMask = value; break; + case RWGL_CULL: curGlState.cullEnable = value; break; + case RWGL_CULLFACE: curGlState.cullFace = value; break; + } +} + +void +flushGlRenderState(void) +{ + if(oldGlState.blendEnable != curGlState.blendEnable){ + oldGlState.blendEnable = curGlState.blendEnable; + (oldGlState.blendEnable ? glEnable : glDisable)(GL_BLEND); + } + + if(oldGlState.srcblend != curGlState.srcblend || + oldGlState.destblend != curGlState.destblend){ + oldGlState.srcblend = curGlState.srcblend; + oldGlState.destblend = curGlState.destblend; + glBlendFunc(oldGlState.srcblend, oldGlState.destblend); + } + + if(oldGlState.depthTest != curGlState.depthTest){ + oldGlState.depthTest = curGlState.depthTest; + (oldGlState.depthTest ? glEnable : glDisable)(GL_DEPTH_TEST); + } + if(oldGlState.depthFunc != curGlState.depthFunc){ + oldGlState.depthFunc = curGlState.depthFunc; + glDepthFunc(oldGlState.depthFunc); + } + if(oldGlState.depthMask != curGlState.depthMask){ + oldGlState.depthMask = curGlState.depthMask; + glDepthMask(oldGlState.depthMask); + } + + if(oldGlState.cullEnable != curGlState.cullEnable){ + oldGlState.cullEnable = curGlState.cullEnable; + (oldGlState.cullEnable ? glEnable : glDisable)(GL_CULL_FACE); + } + if(oldGlState.cullFace != curGlState.cullFace){ + oldGlState.cullFace = curGlState.cullFace; + glCullFace(oldGlState.cullFace); + } +} + + + +void +setAlphaBlend(bool32 enable) +{ + if(rwStateCache.blendEnable != enable){ + rwStateCache.blendEnable = enable; + setGlRenderState(RWGL_BLEND, enable); + } +} + +bool32 +getAlphaBlend(void) +{ + return rwStateCache.blendEnable; +} + +static void +setDepthTest(bool32 enable) +{ + if(rwStateCache.ztest != enable){ + rwStateCache.ztest = enable; + if(rwStateCache.zwrite && !enable){ + // If we still want to write, enable but set mode to always + setGlRenderState(RWGL_DEPTHTEST, true); + setGlRenderState(RWGL_DEPTHFUNC, GL_ALWAYS); + }else{ + setGlRenderState(RWGL_DEPTHTEST, rwStateCache.ztest); + setGlRenderState(RWGL_DEPTHFUNC, GL_LEQUAL); + } + } +} + +static void +setDepthWrite(bool32 enable) +{ + enable = enable ? GL_TRUE : GL_FALSE; + if(rwStateCache.zwrite != enable){ + rwStateCache.zwrite = enable; + if(enable && !rwStateCache.ztest){ + // Have to switch on ztest so writing can work + setGlRenderState(RWGL_DEPTHTEST, true); + setGlRenderState(RWGL_DEPTHFUNC, GL_ALWAYS); + } + setGlRenderState(RWGL_DEPTHMASK, rwStateCache.zwrite); + } +} + +static void +setAlphaTest(bool32 enable) +{ + uint32 shaderfunc; + if(rwStateCache.alphaTestEnable != enable){ + rwStateCache.alphaTestEnable = enable; + shaderfunc = rwStateCache.alphaTestEnable ? rwStateCache.alphaFunc : ALPHAALWAYS; + if(alphaFunc != shaderfunc){ + alphaFunc = shaderfunc; + uniformStateDirty[RWGL_ALPHAFUNC] = true; + stateDirty = 1; + } + } +} + +static void +setAlphaTestFunction(uint32 function) +{ + uint32 shaderfunc; + if(rwStateCache.alphaFunc != function){ + rwStateCache.alphaFunc = function; + shaderfunc = rwStateCache.alphaTestEnable ? rwStateCache.alphaFunc : ALPHAALWAYS; + if(alphaFunc != shaderfunc){ + alphaFunc = shaderfunc; + uniformStateDirty[RWGL_ALPHAFUNC] = true; + stateDirty = 1; + } + } +} + +static void +setVertexAlpha(bool32 enable) +{ + if(rwStateCache.vertexAlpha != enable){ + if(!rwStateCache.textureAlpha){ + setAlphaBlend(enable); + setAlphaTest(enable); + } + rwStateCache.vertexAlpha = enable; + } +} + +static void +setActiveTexture(int32 n) +{ + if(activeTexture != n){ + activeTexture = n; + glActiveTexture(GL_TEXTURE0+n); + } +} + +uint32 +bindTexture(uint32 texid) +{ + uint32 prev = boundTexture[activeTexture]; + boundTexture[activeTexture] = texid; + glBindTexture(GL_TEXTURE_2D, texid); + return prev; +} + +void +bindFramebuffer(uint32 fbo) +{ + //glBindFramebuffer(GL_FRAMEBUFFER, fbo); + if(currentFramebuffer != fbo){ + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + currentFramebuffer = fbo; + } +} + +// TODO: support mipmaps +static GLint filterConvMap_NoMIP[] = { + 0, GL_NEAREST, GL_LINEAR, + GL_NEAREST, GL_LINEAR, + GL_NEAREST, GL_LINEAR +}; + +static GLint addressConvMap[] = { + 0, GL_REPEAT, GL_MIRRORED_REPEAT, + GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER +}; + +static void +setFilterMode(uint32 stage, int32 filter) +{ + if(rwStateCache.texstage[stage].filter != (Texture::FilterMode)filter){ + rwStateCache.texstage[stage].filter = (Texture::FilterMode)filter; + Raster *raster = rwStateCache.texstage[stage].raster; + if(raster){ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, rwStateCache.texstage[stage].raster, nativeRasterOffset); + if(natras->filterMode != filter){ + setActiveTexture(stage); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterConvMap_NoMIP[filter]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterConvMap_NoMIP[filter]); + natras->filterMode = filter; + } + } + } +} + +static void +setAddressU(uint32 stage, int32 addressing) +{ + if(rwStateCache.texstage[stage].addressingU != (Texture::Addressing)addressing){ + rwStateCache.texstage[stage].addressingU = (Texture::Addressing)addressing; + Raster *raster = rwStateCache.texstage[stage].raster; + if(raster){ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + if(natras->addressU == addressing){ + setActiveTexture(stage); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, addressConvMap[addressing]); + natras->addressU = addressing; + } + } + } +} + +static void +setAddressV(uint32 stage, int32 addressing) +{ + if(rwStateCache.texstage[stage].addressingV != (Texture::Addressing)addressing){ + rwStateCache.texstage[stage].addressingV = (Texture::Addressing)addressing; + Raster *raster = rwStateCache.texstage[stage].raster; + if(raster){ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, rwStateCache.texstage[stage].raster, nativeRasterOffset); + if(natras->addressV == addressing){ + setActiveTexture(stage); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, addressConvMap[addressing]); + natras->addressV = addressing; + } + } + } +} + +static void +setRasterStageOnly(uint32 stage, Raster *raster) +{ + bool32 alpha; + if(raster != rwStateCache.texstage[stage].raster){ + rwStateCache.texstage[stage].raster = raster; + setActiveTexture(stage); + if(raster){ + assert(raster->platform == PLATFORM_GL3); + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + bindTexture(natras->texid); + + rwStateCache.texstage[stage].filter = (rw::Texture::FilterMode)natras->filterMode; + rwStateCache.texstage[stage].addressingU = (rw::Texture::Addressing)natras->addressU; + rwStateCache.texstage[stage].addressingV = (rw::Texture::Addressing)natras->addressV; + + alpha = natras->hasAlpha; + }else{ + bindTexture(whitetex); + alpha = 0; + } + + if(stage == 0){ + if(alpha != rwStateCache.textureAlpha){ + rwStateCache.textureAlpha = alpha; + if(!rwStateCache.vertexAlpha){ + setAlphaBlend(alpha); + setAlphaTest(alpha); + } + } + } + } +} + +static void +setRasterStage(uint32 stage, Raster *raster) +{ + bool32 alpha; + if(raster != rwStateCache.texstage[stage].raster){ + rwStateCache.texstage[stage].raster = raster; + setActiveTexture(stage); + if(raster){ + assert(raster->platform == PLATFORM_GL3); + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + bindTexture(natras->texid); + uint32 filter = rwStateCache.texstage[stage].filter; + uint32 addrU = rwStateCache.texstage[stage].addressingU; + uint32 addrV = rwStateCache.texstage[stage].addressingV; + if(natras->filterMode != filter){ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterConvMap_NoMIP[filter]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterConvMap_NoMIP[filter]); + natras->filterMode = filter; + } + if(natras->addressU != addrU){ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, addressConvMap[addrU]); + natras->addressU = addrU; + } + if(natras->addressV != addrV){ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, addressConvMap[addrV]); + natras->addressV = addrV; + } + alpha = natras->hasAlpha; + }else{ + bindTexture(whitetex); + alpha = 0; + } + + if(stage == 0){ + if(alpha != rwStateCache.textureAlpha){ + rwStateCache.textureAlpha = alpha; + if(!rwStateCache.vertexAlpha){ + setAlphaBlend(alpha); + setAlphaTest(alpha); + } + } + } + } +} + +void +setTexture(int32 stage, Texture *tex) +{ + if(tex == nil || tex->raster == nil){ + setRasterStage(stage, nil); + return; + } + setRasterStageOnly(stage, tex->raster); + setFilterMode(stage, tex->getFilter()); + setAddressU(stage, tex->getAddressU()); + setAddressV(stage, tex->getAddressV()); +} + +static void +setRenderState(int32 state, void *pvalue) +{ + uint32 value = (uint32)(uintptr)pvalue; + switch(state){ + case TEXTURERASTER: + setRasterStage(0, (Raster*)pvalue); + break; + case TEXTUREADDRESS: + setAddressU(0, value); + setAddressV(0, value); + break; + case TEXTUREADDRESSU: + setAddressU(0, value); + break; + case TEXTUREADDRESSV: + setAddressV(0, value); + break; + case TEXTUREFILTER: + setFilterMode(0, value); + break; + case VERTEXALPHA: + setVertexAlpha(value); + break; + case SRCBLEND: + if(rwStateCache.srcblend != value){ + rwStateCache.srcblend = value; + setGlRenderState(RWGL_SRCBLEND, blendMap[rwStateCache.srcblend]); + } + break; + case DESTBLEND: + if(rwStateCache.destblend != value){ + rwStateCache.destblend = value; + setGlRenderState(RWGL_DESTBLEND, blendMap[rwStateCache.destblend]); + } + break; + case ZTESTENABLE: + setDepthTest(value); + break; + case ZWRITEENABLE: + setDepthWrite(value); + break; + case FOGENABLE: + if(rwStateCache.fogEnable != value){ + rwStateCache.fogEnable = value; + uniformStateDirty[RWGL_FOG] = true; + stateDirty = 1; + } + break; + case FOGCOLOR: + // no cache check here...too lazy + RGBA c; + c.red = value; + c.green = value>>8; + c.blue = value>>16; + c.alpha = value>>24; + convColor(&uniformState.fogColor, &c); + uniformStateDirty[RWGL_FOGCOLOR] = true; + stateDirty = 1; + break; + case CULLMODE: + if(rwStateCache.cullmode != value){ + rwStateCache.cullmode = value; + if(rwStateCache.cullmode == CULLNONE) + setGlRenderState(RWGL_CULL, false); + else{ + setGlRenderState(RWGL_CULL, true); + setGlRenderState(RWGL_CULLFACE, rwStateCache.cullmode == CULLBACK ? GL_BACK : GL_FRONT); + } + } + break; + + case ALPHATESTFUNC: + setAlphaTestFunction(value); + break; + case ALPHATESTREF: + if(alphaRef != value/255.0f){ + alphaRef = value/255.0f; + uniformStateDirty[RWGL_ALPHAREF] = true; + stateDirty = 1; + } + break; + case GSALPHATEST: + rwStateCache.gsalpha = value; + break; + case GSALPHATESTREF: + rwStateCache.gsalpharef = value; + } +} + +static void* +getRenderState(int32 state) +{ + uint32 val; + RGBA rgba; + switch(state){ + case TEXTURERASTER: + return rwStateCache.texstage[0].raster; + case TEXTUREADDRESS: + if(rwStateCache.texstage[0].addressingU == rwStateCache.texstage[0].addressingV) + val = rwStateCache.texstage[0].addressingU; + else + val = 0; // invalid + break; + case TEXTUREADDRESSU: + val = rwStateCache.texstage[0].addressingU; + break; + case TEXTUREADDRESSV: + val = rwStateCache.texstage[0].addressingV; + break; + case TEXTUREFILTER: + val = rwStateCache.texstage[0].filter; + break; + + case VERTEXALPHA: + val = rwStateCache.vertexAlpha; + break; + case SRCBLEND: + val = rwStateCache.srcblend; + break; + case DESTBLEND: + val = rwStateCache.destblend; + break; + case ZTESTENABLE: + val = rwStateCache.ztest; + break; + case ZWRITEENABLE: + val = rwStateCache.zwrite; + break; + case FOGENABLE: + val = rwStateCache.fogEnable; + break; + case FOGCOLOR: + convColor(&rgba, &uniformState.fogColor); + val = RWRGBAINT(rgba.red, rgba.green, rgba.blue, rgba.alpha); + break; + case CULLMODE: + val = rwStateCache.cullmode; + break; + + case ALPHATESTFUNC: + val = rwStateCache.alphaFunc; + break; + case ALPHATESTREF: + val = (uint32)(alphaRef*255.0f); + break; + case GSALPHATEST: + val = rwStateCache.gsalpha; + break; + case GSALPHATESTREF: + val = rwStateCache.gsalpharef; + break; + default: + val = 0; + } + return (void*)(uintptr)val; +} + +static void +resetRenderState(void) +{ + rwStateCache.alphaFunc = ALPHAGREATEREQUAL; + alphaFunc = 0; + alphaRef = 10.0f/255.0f; + uniformState.fogDisable = 1.0f; + uniformState.fogStart = 0.0f; + uniformState.fogEnd = 0.0f; + uniformState.fogRange = 0.0f; + uniformState.fogColor = { 1.0f, 1.0f, 1.0f, 1.0f }; + rwStateCache.gsalpha = 0; + rwStateCache.gsalpharef = 128; + stateDirty = 1; + + rwStateCache.vertexAlpha = 0; + rwStateCache.textureAlpha = 0; + rwStateCache.alphaTestEnable = 0; + + memset(&oldGlState, 0xFF, sizeof(oldGlState)); + + rwStateCache.blendEnable = 0; + setGlRenderState(RWGL_BLEND, false); + rwStateCache.srcblend = BLENDSRCALPHA; + rwStateCache.destblend = BLENDINVSRCALPHA; + setGlRenderState(RWGL_SRCBLEND, blendMap[rwStateCache.srcblend]); + setGlRenderState(RWGL_DESTBLEND, blendMap[rwStateCache.destblend]); + + rwStateCache.zwrite = GL_TRUE; + setGlRenderState(RWGL_DEPTHMASK, rwStateCache.zwrite); + + rwStateCache.ztest = 1; + setGlRenderState(RWGL_DEPTHTEST, true); + setGlRenderState(RWGL_DEPTHFUNC, GL_LEQUAL); + + rwStateCache.cullmode = CULLNONE; + setGlRenderState(RWGL_CULL, false); + setGlRenderState(RWGL_CULLFACE, GL_BACK); + + activeTexture = -1; + for(int i = 0; i < MAXNUMSTAGES; i++){ + setActiveTexture(i); + bindTexture(whitetex); + } + setActiveTexture(0); +} + +void +setWorldMatrix(Matrix *mat) +{ + convMatrix(&uniformObject.world, mat); + objectDirty = 1; +} + +int32 +setLights(WorldLights *lightData) +{ + int i, n; + Light *l; + int32 bits; + + uniformObject.ambLight = lightData->ambient; + + bits = 0; + + if(lightData->numAmbients) + bits |= VSLIGHT_AMBIENT; + + n = 0; + for(i = 0; i < lightData->numDirectionals && i < 8; i++){ + l = lightData->directionals[i]; + uniformObject.lightParams[n].type = 1.0f; + uniformObject.lightColor[n] = l->color; + memcpy(&uniformObject.lightDirection[n], &l->getFrame()->getLTM()->at, sizeof(V3d)); + bits |= VSLIGHT_POINT; + n++; + if(n >= MAX_LIGHTS) + goto out; + } + + for(i = 0; i < lightData->numLocals; i++){ + Light *l = lightData->locals[i]; + + switch(l->getType()){ + case Light::POINT: + uniformObject.lightParams[n].type = 2.0f; + uniformObject.lightParams[n].radius = l->radius; + uniformObject.lightColor[n] = l->color; + memcpy(&uniformObject.lightPosition[n], &l->getFrame()->getLTM()->pos, sizeof(V3d)); + bits |= VSLIGHT_POINT; + n++; + if(n >= MAX_LIGHTS) + goto out; + break; + case Light::SPOT: + case Light::SOFTSPOT: + uniformObject.lightParams[n].type = 3.0f; + uniformObject.lightParams[n].minusCosAngle = l->minusCosAngle; + uniformObject.lightParams[n].radius = l->radius; + uniformObject.lightColor[n] = l->color; + memcpy(&uniformObject.lightPosition[n], &l->getFrame()->getLTM()->pos, sizeof(V3d)); + memcpy(&uniformObject.lightDirection[n], &l->getFrame()->getLTM()->at, sizeof(V3d)); + // lower bound of falloff + if(l->getType() == Light::SOFTSPOT) + uniformObject.lightParams[n].hardSpot = 0.0f; + else + uniformObject.lightParams[n].hardSpot = 1.0f; + bits |= VSLIGHT_SPOT; + n++; + if(n >= MAX_LIGHTS) + goto out; + break; + } + } + + uniformObject.lightParams[n].type = 0.0f; +out: + objectDirty = 1; + return bits; +} + +void +setProjectionMatrix(float32 *mat) +{ + memcpy(&uniformScene.proj, mat, 64); + sceneDirty = 1; +} + +void +setViewMatrix(float32 *mat) +{ + memcpy(&uniformScene.view, mat, 64); + sceneDirty = 1; +} + +Shader *lastShaderUploaded; + +#define U(i) currentShader->uniformLocations[i] + +void +setMaterial(const RGBA &color, const SurfaceProperties &surfaceprops) +{ + bool force = lastShaderUploaded != currentShader; + if(force || !equal(shaderState.matColor, color)){ + rw::RGBAf col; + convColor(&col, &color); + glUniform4fv(U(u_matColor), 1, (GLfloat*)&col); + shaderState.matColor = color; + } + + if(force || + shaderState.surfProps.ambient != surfaceprops.ambient || + shaderState.surfProps.specular != surfaceprops.specular || + shaderState.surfProps.diffuse != surfaceprops.diffuse){ + float surfProps[4]; + surfProps[0] = surfaceprops.ambient; + surfProps[1] = surfaceprops.specular; + surfProps[2] = surfaceprops.diffuse; + surfProps[3] = 0.0f; + glUniform4fv(U(u_surfProps), 1, surfProps); + shaderState.surfProps = surfaceprops; + } +} + +void +flushCache(void) +{ + flushGlRenderState(); + +#ifndef RW_GL_USE_UBOS + + // TODO: this is probably a stupid way to do it without UBOs + if(lastShaderUploaded != currentShader){ + lastShaderUploaded = currentShader; + objectDirty = 1; + sceneDirty = 1; + stateDirty = 1; + + int i; + for(i = 0; i < RWGL_NUM_STATES; i++) + uniformStateDirty[i] = true; + } + + if(sceneDirty){ + glUniformMatrix4fv(U(u_proj), 1, 0, uniformScene.proj); + glUniformMatrix4fv(U(u_view), 1, 0, uniformScene.view); + sceneDirty = 0; + } + + if(objectDirty){ + glUniformMatrix4fv(U(u_world), 1, 0, (float*)&uniformObject.world); + glUniform4fv(U(u_ambLight), 1, (float*)&uniformObject.ambLight); + glUniform4fv(U(u_lightParams), MAX_LIGHTS, (float*)uniformObject.lightParams); + glUniform4fv(U(u_lightPosition), MAX_LIGHTS, (float*)uniformObject.lightPosition); + glUniform4fv(U(u_lightDirection), MAX_LIGHTS, (float*)uniformObject.lightDirection); + glUniform4fv(U(u_lightColor), MAX_LIGHTS, (float*)uniformObject.lightColor); + objectDirty = 0; + } + +// if(stateDirty){ + + uniformState.fogDisable = rwStateCache.fogEnable ? 0.0f : 1.0f; + uniformState.fogStart = rwStateCache.fogStart; + uniformState.fogEnd = rwStateCache.fogEnd; + uniformState.fogRange = 1.0f/(rwStateCache.fogStart - rwStateCache.fogEnd); + + if(uniformStateDirty[RWGL_ALPHAFUNC] || uniformStateDirty[RWGL_ALPHAREF]){ + switch(alphaFunc){ + case ALPHAALWAYS: + default: + glUniform2f(U(u_alphaRef), -1000.0f, 1000.0f); + break; + case ALPHAGREATEREQUAL: + glUniform2f(U(u_alphaRef), alphaRef, 1000.0f); + break; + case ALPHALESS: + glUniform2f(U(u_alphaRef), -1000.0f, alphaRef); + break; + } + uniformStateDirty[RWGL_ALPHAFUNC] = false; + uniformStateDirty[RWGL_ALPHAREF] = false; + } + + if(uniformStateDirty[RWGL_FOG] || + uniformStateDirty[RWGL_FOGSTART] || + uniformStateDirty[RWGL_FOGEND]){ + float fogData[4] = { + uniformState.fogStart, + uniformState.fogEnd, + uniformState.fogRange, + uniformState.fogDisable + }; + glUniform4fv(U(u_fogData), 1, fogData); + uniformStateDirty[RWGL_FOG] = false; + uniformStateDirty[RWGL_FOGSTART] = false; + uniformStateDirty[RWGL_FOGEND] = false; + } + + if(uniformStateDirty[RWGL_FOGCOLOR]){ + glUniform4fv(U(u_fogColor), 1, (float*)&uniformState.fogColor); + uniformStateDirty[RWGL_FOGCOLOR] = false; + } + +// stateDirty = 0; +// } +#else + if(objectDirty){ + glBindBuffer(GL_UNIFORM_BUFFER, ubo_object); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformObject), nil, GL_STREAM_DRAW); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformObject), &uniformObject, GL_STREAM_DRAW); + objectDirty = 0; + } + if(sceneDirty){ + glBindBuffer(GL_UNIFORM_BUFFER, ubo_scene); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformScene), nil, GL_STREAM_DRAW); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformScene), &uniformScene, GL_STREAM_DRAW); + sceneDirty = 0; + } + if(stateDirty){ + switch(alphaFunc){ + case ALPHAALWAYS: + default: + uniformState.alphaRefLow = -1000.0f; + uniformState.alphaRefHigh = 1000.0f; + break; + case ALPHAGREATEREQUAL: + uniformState.alphaRefLow = alphaRef; + uniformState.alphaRefHigh = 1000.0f; + break; + case ALPHALESS: + uniformState.alphaRefLow = -1000.0f; + uniformState.alphaRefHigh = alphaRef; + break; + } + uniformState.fogDisable = rwStateCache.fogEnable ? 0.0f : 1.0f; + uniformState.fogStart = rwStateCache.fogStart; + uniformState.fogEnd = rwStateCache.fogEnd; + uniformState.fogRange = 1.0f/(rwStateCache.fogStart - rwStateCache.fogEnd); + glBindBuffer(GL_UNIFORM_BUFFER, ubo_state); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformState), nil, GL_STREAM_DRAW); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformState), &uniformState, GL_STREAM_DRAW); + stateDirty = 0; + } +#endif +} + +static void +setFrameBuffer(Camera *cam) +{ + Raster *fbuf = cam->frameBuffer; + Raster *zbuf = cam->zBuffer; + assert(fbuf); + + Gl3Raster *natfb = PLUGINOFFSET(Gl3Raster, fbuf, nativeRasterOffset); + Gl3Raster *natzb = PLUGINOFFSET(Gl3Raster, zbuf, nativeRasterOffset); + assert(fbuf->type == Raster::CAMERA || fbuf->type == Raster::CAMERATEXTURE); + + // Have to make sure depth buffer is attached to FB's fbo + bindFramebuffer(natfb->fbo); + if(zbuf){ + if(natfb->fboMate == zbuf){ + // all good + assert(natzb->fboMate == fbuf); + }else{ + if(natzb->fboMate){ + // have to detatch from fbo first! + Gl3Raster *oldfb = PLUGINOFFSET(Gl3Raster, natzb->fboMate, nativeRasterOffset); + if(oldfb->fbo){ + bindFramebuffer(oldfb->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + bindFramebuffer(natfb->fbo); + } + oldfb->fboMate = nil; + } + natfb->fboMate = zbuf; + natzb->fboMate = fbuf; + if(natfb->fbo) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, natzb->texid, 0); + } + }else{ + // remove z-buffer + if(natfb->fboMate && natfb->fbo) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + natfb->fboMate = nil; + } +} + +static void +beginUpdate(Camera *cam) +{ + float view[16], proj[16]; + // View Matrix + Matrix inv; + Matrix::invert(&inv, cam->getFrame()->getLTM()); + // Since we're looking into positive Z, + // flip X to ge a left handed view space. + view[0] = -inv.right.x; + view[1] = inv.right.y; + view[2] = inv.right.z; + view[3] = 0.0f; + view[4] = -inv.up.x; + view[5] = inv.up.y; + view[6] = inv.up.z; + view[7] = 0.0f; + view[8] = -inv.at.x; + view[9] = inv.at.y; + view[10] = inv.at.z; + view[11] = 0.0f; + view[12] = -inv.pos.x; + view[13] = inv.pos.y; + view[14] = inv.pos.z; + view[15] = 1.0f; + memcpy(&cam->devView, &view, sizeof(RawMatrix)); + setViewMatrix(view); + + // Projection Matrix + float32 invwx = 1.0f/cam->viewWindow.x; + float32 invwy = 1.0f/cam->viewWindow.y; + float32 invz = 1.0f/(cam->farPlane-cam->nearPlane); + + proj[0] = invwx; + proj[1] = 0.0f; + proj[2] = 0.0f; + proj[3] = 0.0f; + + proj[4] = 0.0f; + proj[5] = invwy; + proj[6] = 0.0f; + proj[7] = 0.0f; + + proj[8] = cam->viewOffset.x*invwx; + proj[9] = cam->viewOffset.y*invwy; + proj[12] = -proj[8]; + proj[13] = -proj[9]; + if(cam->projection == Camera::PERSPECTIVE){ + proj[10] = (cam->farPlane+cam->nearPlane)*invz; + proj[11] = 1.0f; + + proj[14] = -2.0f*cam->nearPlane*cam->farPlane*invz; + proj[15] = 0.0f; + }else{ + proj[10] = -(cam->farPlane+cam->nearPlane)*invz; + proj[11] = 0.0f; + + proj[14] = 2.0f*invz; + proj[15] = 1.0f; + } + memcpy(&cam->devProj, &proj, sizeof(RawMatrix)); + setProjectionMatrix(proj); + + if(rwStateCache.fogStart != cam->fogPlane){ + rwStateCache.fogStart = cam->fogPlane; + uniformStateDirty[RWGL_FOGSTART] = true; + stateDirty = 1; + } + if(rwStateCache.fogEnd != cam->farPlane){ + rwStateCache.fogEnd = cam->farPlane; + uniformStateDirty[RWGL_FOGEND] = true; + stateDirty = 1; + } + + setFrameBuffer(cam); + + int w, h; + if(cam->frameBuffer->type == Raster::CAMERA){ +#ifdef LIBRW_SDL2 + SDL_GetWindowSize(glGlobals.window, &w, &h); +#else + glfwGetWindowSize(glGlobals.window, &w, &h); +#endif + }else{ + w = cam->frameBuffer->width; + h = cam->frameBuffer->height; + } + + if(w != glGlobals.presentWidth || h != glGlobals.presentHeight){ + glViewport(0, 0, w, h); + glGlobals.presentWidth = w; + glGlobals.presentHeight = h; + } +} + +static void +clearCamera(Camera *cam, RGBA *col, uint32 mode) +{ + RGBAf colf; + GLbitfield mask; + + setFrameBuffer(cam); + + convColor(&colf, col); + glClearColor(colf.red, colf.green, colf.blue, colf.alpha); + mask = 0; + if(mode & Camera::CLEARIMAGE) + mask |= GL_COLOR_BUFFER_BIT; + if(mode & Camera::CLEARZ) + mask |= GL_DEPTH_BUFFER_BIT; + glDepthMask(GL_TRUE); + glClear(mask); + glDepthMask(rwStateCache.zwrite); +} + +static void +showRaster(Raster *raster, uint32 flags) +{ + // TODO: do this properly! +#ifdef LIBRW_SDL2 + SDL_GL_SwapWindow(glGlobals.window); +#else + if(flags & Raster::FLIPWAITVSYNCH) + glfwSwapInterval(1); + else + glfwSwapInterval(0); + glfwSwapBuffers(glGlobals.window); +#endif +} + +static bool32 +rasterRenderFast(Raster *raster, int32 x, int32 y) +{ + Raster *src = raster; + Raster *dst = Raster::getCurrentContext(); + Gl3Raster *natdst = PLUGINOFFSET(Gl3Raster, dst, nativeRasterOffset); + Gl3Raster *natsrc = PLUGINOFFSET(Gl3Raster, src, nativeRasterOffset); + + switch(dst->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + case Raster::CAMERATEXTURE: + switch(src->type){ + case Raster::CAMERA: + setActiveTexture(0); + glBindTexture(GL_TEXTURE_2D, natdst->texid); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x, (dst->height-src->height)-y, + 0, 0, src->width, src->height); + glBindTexture(GL_TEXTURE_2D, boundTexture[0]); + return 1; + } + break; + } + return 0; +} + +#ifdef LIBRW_SDL2 +static int +openSDL2(EngineOpenParams *openparams) +{ + if (!openparams){ + RWERROR((ERR_GENERAL, "openparams invalid")); + return 0; + } + + GLenum status; + SDL_Window *win; + SDL_GLContext ctx; + + /* Init SDL */ + if(SDL_InitSubSystem(SDL_INIT_VIDEO)){ + RWERROR((ERR_ENGINEOPEN, SDL_GetError())); + return 0; + } + SDL_ClearHints(); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + int flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL; + if (openparams->fullscreen) + flags |= SDL_WINDOW_FULLSCREEN; + win = SDL_CreateWindow(openparams->windowtitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, openparams->width, openparams->height, flags); + if(win == nil){ + RWERROR((ERR_ENGINEOPEN, SDL_GetError())); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return 0; + } + ctx = SDL_GL_CreateContext(win); + +#ifndef LIBRW_GLAD + /* Init GLEW */ + glewExperimental = GL_TRUE; + status = glewInit(); + if(status != GLEW_OK){ + RWERROR((ERR_ENGINEOPEN, glewGetErrorString(status))); + SDL_GL_DeleteContext(ctx); + SDL_DestroyWindow(win); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return 0; + } + if(!GLEW_VERSION_3_3){ + RWERROR((ERR_VERSION, "OpenGL 3.3 needed")); + SDL_GL_DeleteContext(ctx); + SDL_DestroyWindow(win); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return 0; + } +#else + if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) { + RWERROR((ERR_ENGINEOPEN, "gladLoadGLLoader failed")); + SDL_GL_DeleteContext(ctx); + SDL_DestroyWindow(win); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return 0; + } + if(!GLAD_GL_VERSION_3_3) { + RWERROR((ERR_VERSION, "OpenGL 3.3 needed")); + SDL_GL_DeleteContext(ctx); + SDL_DestroyWindow(win); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return 0; + } +#endif + glGlobals.window = win; + glGlobals.glcontext = ctx; + *openparams->window = win; + return 1; +} + +static int +closeSDL2(void) +{ + SDL_GL_DeleteContext(glGlobals.glcontext); + SDL_DestroyWindow(glGlobals.window); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return 1; +} +#else + +static void +addVideoMode(const GLFWvidmode *mode) +{ + int i; + + for(i = 1; i < glGlobals.numModes; i++){ + if(glGlobals.modes[i].mode.width == mode->width && + glGlobals.modes[i].mode.height == mode->height && + glGlobals.modes[i].mode.redBits == mode->redBits && + glGlobals.modes[i].mode.greenBits == mode->greenBits && + glGlobals.modes[i].mode.blueBits == mode->blueBits){ + // had this mode already, remember highest refresh rate + if(mode->refreshRate > glGlobals.modes[i].mode.refreshRate) + glGlobals.modes[i].mode.refreshRate = mode->refreshRate; + return; + } + } + + // none found, add + glGlobals.modes[glGlobals.numModes].mode = *mode; + glGlobals.modes[glGlobals.numModes].flags = VIDEOMODEEXCLUSIVE; + glGlobals.numModes++; +} + +static void +makeVideoModeList(void) +#ifndef __SWITCH__ +{ + int i, num; + const GLFWvidmode *modes; + + modes = glfwGetVideoModes(glGlobals.monitor, &num); + rwFree(glGlobals.modes); + glGlobals.modes = rwNewT(DisplayMode, num+1, ID_DRIVER | MEMDUR_EVENT); + + glGlobals.modes[0].mode = *glfwGetVideoMode(glGlobals.monitor); + glGlobals.modes[0].flags = 0; + glGlobals.numModes = 1; + + for(i = 0; i < num; i++) + addVideoMode(&modes[i]); + + for(i = 0; i < glGlobals.numModes; i++){ + num = glGlobals.modes[i].mode.redBits + + glGlobals.modes[i].mode.greenBits + + glGlobals.modes[i].mode.blueBits; + // set depth to power of two + for(glGlobals.modes[i].depth = 1; glGlobals.modes[i].depth < num; glGlobals.modes[i].depth <<= 1); + } +} +#else +// stub video modes manually: 1080p and 720p +{ + glGlobals.modes = rwNewT(DisplayMode, 2, ID_DRIVER | MEMDUR_EVENT); + + glGlobals.modes[0].mode.redBits = 8; + glGlobals.modes[0].mode.greenBits = 8; + glGlobals.modes[0].mode.blueBits = 8; + glGlobals.modes[0].mode.width = 1280; + glGlobals.modes[0].mode.height = 720; + glGlobals.modes[0].mode.refreshRate = 60; + glGlobals.modes[0].depth = 32; + glGlobals.modes[0].flags = VIDEOMODEEXCLUSIVE; + + glGlobals.modes[1].mode.redBits = 8; + glGlobals.modes[1].mode.greenBits = 8; + glGlobals.modes[1].mode.blueBits = 8; + glGlobals.modes[1].mode.width = 1920; + glGlobals.modes[1].mode.height = 1080; + glGlobals.modes[1].mode.refreshRate = 60; + glGlobals.modes[1].depth = 32; + glGlobals.modes[1].flags = VIDEOMODEEXCLUSIVE; + + glGlobals.numModes = 2; +} +#endif + +static int +openGLFW(EngineOpenParams *openparams) +{ + glGlobals.winWidth = openparams->width; + glGlobals.winHeight = openparams->height; + glGlobals.winTitle = openparams->windowtitle; + glGlobals.pWindow = openparams->window; + + /* Init GLFW */ + if(!glfwInit()){ + RWERROR((ERR_GENERAL, "glfwInit() failed")); + return 0; + } + glfwWindowHint(GLFW_SAMPLES, 0); +#ifdef RW_GLES3 + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); +#elif defined RW_GLES2 + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); +#else + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +#endif + + #ifndef __SWITCH__ + glGlobals.monitor = glfwGetMonitors(&glGlobals.numMonitors)[0]; + #else + glGlobals.monitor = glfwGetPrimaryMonitor(); + glGlobals.numMonitors = 1; + #endif + + makeVideoModeList(); + + return 1; +} + +static int +closeGLFW(void) +{ + glfwTerminate(); + return 1; +} + +static void +glfwerr(int error, const char *desc) +{ + fprintf(stderr, "GLFW Error: %s\n", desc); +} + +static int +startGLFW(void) +{ + GLenum status; + GLFWwindow *win; + DisplayMode *mode; + + mode = &glGlobals.modes[glGlobals.currentMode]; + + glfwSetErrorCallback(glfwerr); + glfwWindowHint(GLFW_RED_BITS, mode->mode.redBits); + glfwWindowHint(GLFW_GREEN_BITS, mode->mode.greenBits); + glfwWindowHint(GLFW_BLUE_BITS, mode->mode.blueBits); + glfwWindowHint(GLFW_REFRESH_RATE, mode->mode.refreshRate); + + if(mode->flags & VIDEOMODEEXCLUSIVE) + win = glfwCreateWindow(mode->mode.width, mode->mode.height, glGlobals.winTitle, glGlobals.monitor, nil); + else + win = glfwCreateWindow(glGlobals.winWidth, glGlobals.winHeight, glGlobals.winTitle, nil, nil); + if(win == nil){ + RWERROR((ERR_GENERAL, "glfwCreateWindow() failed")); + return 0; + } + glfwMakeContextCurrent(win); + +#ifndef LIBRW_GLAD + /* Init GLEW */ + glewExperimental = GL_TRUE; + status = glewInit(); + if(status != GLEW_OK){ + RWERROR((ERR_GENERAL, glewGetErrorString(status))); + glfwDestroyWindow(win); + return 0; + } + if(!GLEW_VERSION_3_3){ + RWERROR((ERR_GENERAL, "OpenGL 3.3 needed")); + glfwDestroyWindow(win); + return 0; + } +#else + if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) { + RWERROR((ERR_GENERAL, "gladLoadGLLoader failed")); + glfwDestroyWindow(win); + return 0; + } + + /*if(!GLAD_GL_VERSION_3_3) { + RWERROR((ERR_GENERAL, "OpenGL 3.3 needed")); + glfwDestroyWindow(win); + return 0; + }*/ +#endif + printf("version %s\n", glGetString(GL_VERSION)); + + printf("GL_VENDOR: %s\n", glGetString(GL_VENDOR)); + printf("GL_RENDERER: %s\n", glGetString(GL_RENDERER)); + printf("GL_VERSION: %s\n", glGetString(GL_VERSION)); + printf("GL_EXTENSIONS: %s\n", glGetString(GL_EXTENSIONS)); + + glGlobals.window = win; + *glGlobals.pWindow = win; + glGlobals.presentWidth = 0; + glGlobals.presentHeight = 0; + return 1; +} + +static int +stopGLFW(void) +{ + glfwDestroyWindow(glGlobals.window); + return 1; +} +#endif + +static int +initOpenGL(void) +{ +#ifndef RW_GL_USE_UBOS + u_alphaRef = registerUniform("u_alphaRef"); + u_fogData = registerUniform("u_fogData"); +// u_fogStart = registerUniform("u_fogStart"); +// u_fogEnd = registerUniform("u_fogEnd"); +// u_fogRange = registerUniform("u_fogRange"); +// u_fogDisable = registerUniform("u_fogDisable"); + u_fogColor = registerUniform("u_fogColor"); + u_proj = registerUniform("u_proj"); + u_view = registerUniform("u_view"); + u_world = registerUniform("u_world"); + u_ambLight = registerUniform("u_ambLight"); + u_lightParams = registerUniform("u_lightParams"); + u_lightPosition = registerUniform("u_lightPosition"); + u_lightDirection = registerUniform("u_lightDirection"); + u_lightColor = registerUniform("u_lightColor"); + lastShaderUploaded = nil; +#else + registerBlock("Scene"); + registerBlock("Object"); + registerBlock("State"); +#endif + u_matColor = registerUniform("u_matColor"); + u_surfProps = registerUniform("u_surfProps"); + + glClearColor(0.25, 0.25, 0.25, 1.0); + + byte whitepixel[4] = {0xFF, 0xFF, 0xFF, 0xFF}; + glGenTextures(1, &whitetex); + glBindTexture(GL_TEXTURE_2D, whitetex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, + 0, GL_RGBA, GL_UNSIGNED_BYTE, &whitepixel); + + resetRenderState(); + +#ifndef RW_GLES2 + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); +#endif + +#ifdef RW_GL_USE_UBOS + glGenBuffers(1, &ubo_state); + glBindBuffer(GL_UNIFORM_BUFFER, ubo_state); + glBindBufferBase(GL_UNIFORM_BUFFER, gl3::findBlock("State"), ubo_state); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformState), &uniformState, + GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glGenBuffers(1, &ubo_scene); + glBindBuffer(GL_UNIFORM_BUFFER, ubo_scene); + glBindBufferBase(GL_UNIFORM_BUFFER, gl3::findBlock("Scene"), ubo_scene); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformScene), &uniformScene, + GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glGenBuffers(1, &ubo_object); + glBindBuffer(GL_UNIFORM_BUFFER, ubo_object); + glBindBufferBase(GL_UNIFORM_BUFFER, gl3::findBlock("Object"), ubo_object); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformObject), &uniformObject, + GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); +#endif + +#ifdef RW_GLES2 +#include "gl2_shaders/default_vs_gl2.inc" +#include "gl2_shaders/simple_fs_gl2.inc" +#else +#include "shaders/default_vs_gl3.inc" +#include "shaders/simple_fs_gl3.inc" +#endif + const char *vs[] = { shaderDecl, header_vert_src, default_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + defaultShader = Shader::create(vs, fs); + assert(defaultShader); + + openIm2D(); + openIm3D(); + + return 1; +} + +static int +termOpenGL(void) +{ + closeIm3D(); + closeIm2D(); + return 1; +} + +static int +finalizeOpenGL(void) +{ + return 1; +} + +#ifdef LIBRW_SDL2 +static int +deviceSystemSDL2(DeviceReq req, void *arg, int32 n) +{ + switch(req){ + case DEVICEOPEN: + return openSDL2((EngineOpenParams*)arg); + case DEVICECLOSE: + return closeSDL2(); + + case DEVICEINIT: + return initOpenGL(); + case DEVICETERM: + return termOpenGL(); + + // TODO: implement subsystems and video modes + + default: + assert(0 && "not implemented"); + return 0; + } + return 1; +} + +#else + +static int +deviceSystemGLFW(DeviceReq req, void *arg, int32 n) +{ + GLFWmonitor **monitors; + VideoMode *rwmode; + + switch(req){ + case DEVICEOPEN: + return openGLFW((EngineOpenParams*)arg); + case DEVICECLOSE: + return closeGLFW(); + + case DEVICEINIT: + return startGLFW() && initOpenGL(); + case DEVICETERM: + return termOpenGL() && stopGLFW(); + + case DEVICEFINALIZE: + return finalizeOpenGL(); + + + case DEVICEGETNUMSUBSYSTEMS: + return glGlobals.numMonitors; + + case DEVICEGETCURRENTSUBSYSTEM: + return glGlobals.currentMonitor; + + case DEVICESETSUBSYSTEM: + #ifndef __SWITCH__ + monitors = glfwGetMonitors(&glGlobals.numMonitors); + #endif + if(n >= glGlobals.numMonitors) + return 0; + #ifndef __SWITCH__ + glGlobals.currentMonitor = n; + glGlobals.monitor = monitors[glGlobals.currentMonitor]; + #else + glGlobals.currentMonitor = 0; + glGlobals.monitor = glfwGetPrimaryMonitor(); + #endif + return 1; + + case DEVICEGETSUBSSYSTEMINFO: + #ifndef __SWITCH__ + monitors = glfwGetMonitors(&glGlobals.numMonitors); + #endif + if(n >= glGlobals.numMonitors) + return 0; + #ifndef __SWITCH__ + strncpy(((SubSystemInfo*)arg)->name, glfwGetMonitorName(monitors[n]), sizeof(SubSystemInfo::name)); + #else + strncpy(((SubSystemInfo*)arg)->name, "Nintendo Switch Screen Stub", sizeof(SubSystemInfo::name)); + #endif + return 1; + + + case DEVICEGETNUMVIDEOMODES: + return glGlobals.numModes; + + case DEVICEGETCURRENTVIDEOMODE: + return glGlobals.currentMode; + + case DEVICESETVIDEOMODE: + if(n >= glGlobals.numModes) + return 0; + glGlobals.currentMode = n; + return 1; + + case DEVICEGETVIDEOMODEINFO: + rwmode = (VideoMode*)arg; + rwmode->width = glGlobals.modes[n].mode.width; + rwmode->height = glGlobals.modes[n].mode.height; + rwmode->depth = glGlobals.modes[n].depth; + rwmode->flags = glGlobals.modes[n].flags; + return 1; + + default: + assert(0 && "not implemented"); + return 0; + } + return 1; +} + +#endif + +Device renderdevice = { + -1.0f, 1.0f, + gl3::beginUpdate, + null::endUpdate, + gl3::clearCamera, + gl3::showRaster, + gl3::rasterRenderFast, + gl3::setRenderState, + gl3::getRenderState, + gl3::im2DRenderLine, + gl3::im2DRenderTriangle, + gl3::im2DRenderPrimitive, + gl3::im2DRenderIndexedPrimitive, + gl3::im3DTransform, + gl3::im3DRenderPrimitive, + gl3::im3DRenderIndexedPrimitive, + gl3::im3DEnd, +#ifdef LIBRW_SDL2 + gl3::deviceSystemSDL2 +#else + gl3::deviceSystemGLFW +#endif +}; + +} +} + +#endif + diff --git a/src/gl/gl3immed.cpp b/src/gl/gl3immed.cpp new file mode 100644 index 0000000..738e5e2 --- /dev/null +++ b/src/gl/gl3immed.cpp @@ -0,0 +1,313 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#ifdef RW_OPENGL +#include "rwgl3.h" +#include "rwgl3impl.h" +#include "rwgl3shader.h" + +namespace rw { +namespace gl3 { + +uint32 im2DVbo, im2DIbo; +#ifdef RW_GL_USE_VAOS +uint32 im2DVao; +#endif + +Shader *im2dOverrideShader; + +static int32 u_xform; + +#define STARTINDICES 10000 +#define STARTVERTICES 10000 + +static Shader *im2dShader; +static AttribDesc im2dattribDesc[3] = { + { ATTRIB_POS, GL_FLOAT, GL_FALSE, 4, + sizeof(Im2DVertex), 0 }, + { ATTRIB_COLOR, GL_UNSIGNED_BYTE, GL_TRUE, 4, + sizeof(Im2DVertex), offsetof(Im2DVertex, r) }, + { ATTRIB_TEXCOORDS0, GL_FLOAT, GL_FALSE, 2, + sizeof(Im2DVertex), offsetof(Im2DVertex, u) }, +}; + +static int primTypeMap[] = { + GL_POINTS, // invalid + GL_LINES, + GL_LINE_STRIP, + GL_TRIANGLES, + GL_TRIANGLE_STRIP, + GL_TRIANGLE_FAN, + GL_POINTS +}; + +void +openIm2D(void) +{ + u_xform = registerUniform("u_xform"); + +#ifdef RW_GLES2 +#include "gl2_shaders/im2d_gl2.inc" +#include "gl2_shaders/simple_fs_gl2.inc" +#else +#include "shaders/im2d_gl3.inc" +#include "shaders/simple_fs_gl3.inc" +#endif + const char *vs[] = { shaderDecl, header_vert_src, im2d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + im2dShader = Shader::create(vs, fs); + assert(im2dShader); + + glGenBuffers(1, &im2DIbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im2DIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, STARTINDICES*2, nil, GL_STREAM_DRAW); + + glGenBuffers(1, &im2DVbo); + glBindBuffer(GL_ARRAY_BUFFER, im2DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im2DVertex), nil, GL_STREAM_DRAW); + +#ifdef RW_GL_USE_VAOS + glGenVertexArrays(1, &im2DVao); + glBindVertexArray(im2DVao); + setAttribPointers(im2dattribDesc, 3); +#endif +} + +void +closeIm2D(void) +{ + glDeleteBuffers(1, &im2DIbo); + glDeleteBuffers(1, &im2DVbo); + im2dShader->destroy(); + im2dShader = nil; +} + +static Im2DVertex tmpprimbuf[3]; + +void +im2DRenderLine(void *vertices, int32 numVertices, int32 vert1, int32 vert2) +{ + Im2DVertex *verts = (Im2DVertex*)vertices; + tmpprimbuf[0] = verts[vert1]; + tmpprimbuf[1] = verts[vert2]; + im2DRenderPrimitive(PRIMTYPELINELIST, tmpprimbuf, 2); +} + +void +im2DRenderTriangle(void *vertices, int32 numVertices, int32 vert1, int32 vert2, int32 vert3) +{ + Im2DVertex *verts = (Im2DVertex*)vertices; + tmpprimbuf[0] = verts[vert1]; + tmpprimbuf[1] = verts[vert2]; + tmpprimbuf[2] = verts[vert3]; + im2DRenderPrimitive(PRIMTYPETRILIST, tmpprimbuf, 3); +} + +void +im2DRenderPrimitive(PrimitiveType primType, void *vertices, int32 numVertices) +{ + GLfloat xform[4]; + Camera *cam; + cam = (Camera*)engine->currentCamera; + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(im2DVao); +#endif + + glBindBuffer(GL_ARRAY_BUFFER, im2DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im2DVertex), nil, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Im2DVertex), vertices); + + xform[0] = 2.0f/cam->frameBuffer->width; + xform[1] = -2.0f/cam->frameBuffer->height; + xform[2] = -1.0f; + xform[3] = 1.0f; + + if(im2dOverrideShader) + im2dOverrideShader->use(); + else + im2dShader->use(); +#ifndef RW_GL_USE_VAOS + setAttribPointers(im2dattribDesc, 3); +#endif + + glUniform4fv(currentShader->uniformLocations[u_xform], 1, xform); + + flushCache(); + glDrawArrays(primTypeMap[primType], 0, numVertices); +#ifndef RW_GL_USE_VAOS + disableAttribPointers(im2dattribDesc, 3); +#endif +} + +void +im2DRenderIndexedPrimitive(PrimitiveType primType, + void *vertices, int32 numVertices, + void *indices, int32 numIndices) +{ + GLfloat xform[4]; + Camera *cam; + cam = (Camera*)engine->currentCamera; + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(im2DVao); +#endif + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im2DIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, STARTINDICES*2, nil, GL_STREAM_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numIndices*2, indices); + + glBindBuffer(GL_ARRAY_BUFFER, im2DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im2DVertex), nil, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Im2DVertex), vertices); + + xform[0] = 2.0f/cam->frameBuffer->width; + xform[1] = -2.0f/cam->frameBuffer->height; + xform[2] = -1.0f; + xform[3] = 1.0f; + + if(im2dOverrideShader) + im2dOverrideShader->use(); + else + im2dShader->use(); +#ifndef RW_GL_USE_VAOS + setAttribPointers(im2dattribDesc, 3); +#endif + + glUniform4fv(currentShader->uniformLocations[u_xform], 1, xform); + + flushCache(); + glDrawElements(primTypeMap[primType], numIndices, + GL_UNSIGNED_SHORT, nil); +#ifndef RW_GL_USE_VAOS + disableAttribPointers(im2dattribDesc, 3); +#endif +} + + +// Im3D + + +static Shader *im3dShader; +static AttribDesc im3dattribDesc[3] = { + { ATTRIB_POS, GL_FLOAT, GL_FALSE, 3, + sizeof(Im3DVertex), 0 }, + { ATTRIB_COLOR, GL_UNSIGNED_BYTE, GL_TRUE, 4, + sizeof(Im3DVertex), offsetof(Im3DVertex, r) }, + { ATTRIB_TEXCOORDS0, GL_FLOAT, GL_FALSE, 2, + sizeof(Im3DVertex), offsetof(Im3DVertex, u) }, +}; +static uint32 im3DVbo, im3DIbo; +#ifdef RW_GL_USE_VAOS +static uint32 im3DVao; +#endif +static int32 num3DVertices; // not actually needed here + +void +openIm3D(void) +{ +#ifdef RW_GLES2 +#include "gl2_shaders/im3d_gl2.inc" +#include "gl2_shaders/simple_fs_gl2.inc" +#else +#include "shaders/im3d_gl3.inc" +#include "shaders/simple_fs_gl3.inc" +#endif + const char *vs[] = { shaderDecl, header_vert_src, im3d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + im3dShader = Shader::create(vs, fs); + assert(im3dShader); + + glGenBuffers(1, &im3DIbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im3DIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, STARTINDICES*2, nil, GL_STREAM_DRAW); + + glGenBuffers(1, &im3DVbo); + glBindBuffer(GL_ARRAY_BUFFER, im3DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im3DVertex), nil, GL_STREAM_DRAW); + +#ifdef RW_GL_USE_VAOS + glGenVertexArrays(1, &im3DVao); + glBindVertexArray(im3DVao); + setAttribPointers(im3dattribDesc, 3); +#endif +} + +void +closeIm3D(void) +{ + glDeleteBuffers(1, &im3DIbo); + glDeleteBuffers(1, &im3DVbo); + im3dShader->destroy(); + im3dShader = nil; +} + +void +im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags) +{ + if(world == nil){ + Matrix ident; + ident.setIdentity(); + world = &ident; + } + setWorldMatrix(world); + im3dShader->use(); + + if((flags & im3d::VERTEXUV) == 0) + SetRenderStatePtr(TEXTURERASTER, nil); + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(im2DVao); +#endif + + glBindBuffer(GL_ARRAY_BUFFER, im3DVbo); + glBufferData(GL_ARRAY_BUFFER, STARTVERTICES*sizeof(Im3DVertex), nil, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices*sizeof(Im3DVertex), vertices); +#ifndef RW_GL_USE_VAOS + setAttribPointers(im3dattribDesc, 3); +#endif + num3DVertices = numVertices; +} + +void +im3DRenderPrimitive(PrimitiveType primType) +{ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im3DIbo); + + flushCache(); + glDrawArrays(primTypeMap[primType], 0, num3DVertices); +} + +void +im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices) +{ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, im3DIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, STARTINDICES*2, nil, GL_STREAM_DRAW); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numIndices*2, indices); + + flushCache(); + glDrawElements(primTypeMap[primType], numIndices, + GL_UNSIGNED_SHORT, nil); +} + +void +im3DEnd(void) +{ +#ifndef RW_GL_USE_VAOS + disableAttribPointers(im3dattribDesc, 3); +#endif +} + +} +} + +#endif diff --git a/src/gl/gl3matfx.cpp b/src/gl/gl3matfx.cpp new file mode 100644 index 0000000..ad4dc17 --- /dev/null +++ b/src/gl/gl3matfx.cpp @@ -0,0 +1,220 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwplugins.h" +#include "rwgl3.h" +#include "rwgl3shader.h" +#include "rwgl3plg.h" + +#include "rwgl3impl.h" + +namespace rw { +namespace gl3 { + +#ifdef RW_OPENGL + +#define U(i) currentShader->uniformLocations[i] + +static Shader *envShader; +static int32 u_texMatrix; +static int32 u_fxparams; +static int32 u_colorClamp; + +void +matfxDefaultRender(InstanceDataHeader *header, InstanceData *inst) +{ + Material *m; + m = inst->material; + + defaultShader->use(); + + setMaterial(m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); +} + +static Frame *lastEnvFrame; + +static RawMatrix normal2texcoord = { + { 0.5f, 0.0f, 0.0f }, 0.0f, + { 0.0f, -0.5f, 0.0f }, 0.0f, + { 0.0f, 0.0f, 1.0f }, 0.0f, + { 0.5f, 0.5f, 0.0f }, 1.0f +}; + +void +uploadEnvMatrix(Frame *frame) +{ + Matrix invMat; + if(frame == nil) + frame = engine->currentCamera->getFrame(); + + // cache the matrix across multiple meshes + static RawMatrix envMtx; + if(frame != lastEnvFrame){ + lastEnvFrame = frame; + + RawMatrix invMtx; + Matrix::invert(&invMat, frame->getLTM()); + convMatrix(&invMtx, &invMat); + invMtx.pos.set(0.0f, 0.0f, 0.0f); + RawMatrix::mult(&envMtx, &invMtx, &normal2texcoord); + } + glUniformMatrix4fv(U(u_texMatrix), 1, GL_FALSE, (float*)&envMtx); +} + +void +matfxEnvRender(InstanceDataHeader *header, InstanceData *inst, MatFX::Env *env) +{ + Material *m; + m = inst->material; + + if(env->tex == nil || env->coefficient == 0.0f){ + matfxDefaultRender(header, inst); + return; + } + + envShader->use(); + + setTexture(0, m->texture); + setTexture(1, env->tex); + uploadEnvMatrix(env->frame); + + setMaterial(m->color, m->surfaceProps); + + float fxparams[2]; + fxparams[0] = env->coefficient; + fxparams[1] = env->fbAlpha ? 0.0f : 1.0f; + + glUniform2fv(U(u_fxparams), 1, fxparams); + static float zero[4]; + static float one[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + // This clamps the vertex color below. With it we can achieve both PC and PS2 style matfx + if(MatFX::modulateEnvMap) + glUniform4fv(U(u_colorClamp), 1, zero); + else + glUniform4fv(U(u_colorClamp), 1, one); + + rw::SetRenderState(VERTEXALPHA, 1); + rw::SetRenderState(SRCBLEND, BLENDONE); + + drawInst(header, inst); + + rw::SetRenderState(SRCBLEND, BLENDSRCALPHA); +} + +void +matfxRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(header->vao); +#else + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); + glBindBuffer(GL_ARRAY_BUFFER, header->vbo); + setAttribPointers(header->attribDesc, header->numAttribs); +#endif + + lastEnvFrame = nil; + + InstanceData *inst = header->inst; + int32 n = header->numMeshes; + + while(n--){ + MatFX *matfx = MatFX::get(inst->material); + + if(matfx == nil) + matfxDefaultRender(header, inst); + else switch(matfx->type){ + case MatFX::ENVMAP: + matfxEnvRender(header, inst, &matfx->fx[0].env); + break; + default: + matfxDefaultRender(header, inst); + break; + } + inst++; + } +#ifndef RW_GL_USE_VAOS + disableAttribPointers(header->attribDesc, header->numAttribs); +#endif +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = matfxRenderCB; + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_GL3] = makeMatFXPipeline(); + +#ifdef RW_GLES2 +#include "gl2_shaders/matfx_gl2.inc" +#else +#include "shaders/matfx_gl3.inc" +#endif + const char *vs[] = { shaderDecl, header_vert_src, matfx_env_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, matfx_env_frag_src, nil }; + envShader = Shader::create(vs, fs); + assert(envShader); + + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_GL3])->destroy(); + matFXGlobals.pipelines[PLATFORM_GL3] = nil; + + envShader->destroy(); + envShader = nil; + + return o; +} + +void +initMatFX(void) +{ + u_texMatrix = registerUniform("u_texMatrix"); + u_fxparams = registerUniform("u_fxparams"); + u_colorClamp = registerUniform("u_colorClamp"); + + Driver::registerPlugin(PLATFORM_GL3, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +#else + +void initMatFX(void) { } + +#endif + +} +} + diff --git a/src/gl/gl3pipe.cpp b/src/gl/gl3pipe.cpp new file mode 100644 index 0000000..5e9d748 --- /dev/null +++ b/src/gl/gl3pipe.cpp @@ -0,0 +1,331 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwgl3.h" +#include "rwgl3shader.h" + +namespace rw { +namespace gl3 { + +// TODO: make some of these things platform-independent + +#ifdef RW_OPENGL + +void +freeInstanceData(Geometry *geometry) +{ + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_GL3) + return; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + glDeleteBuffers(1, &header->ibo); + glDeleteBuffers(1, &header->vbo); +#ifdef RW_GL_USE_VAOS + glDeleteBuffers(1, &header->vao); +#endif + rwFree(header->indexBuffer); + rwFree(header->vertexBuffer); + rwFree(header->attribDesc); + rwFree(header); +} + +void* +destroyNativeData(void *object, int32, int32) +{ + freeInstanceData((Geometry*)object); + return object; +} + +static InstanceDataHeader* +instanceMesh(rw::ObjPipeline *rwpipe, Geometry *geo) +{ + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + MeshHeader *meshh = geo->meshHeader; + geo->instData = header; + header->platform = PLATFORM_GL3; + + header->serialNumber = meshh->serialNum; + header->numMeshes = meshh->numMeshes; + header->primType = meshh->flags == 1 ? GL_TRIANGLE_STRIP : GL_TRIANGLES; + header->totalNumVertex = geo->numVertices; + header->totalNumIndex = meshh->totalIndices; + header->inst = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + + header->indexBuffer = rwNewT(uint16, header->totalNumIndex, MEMDUR_EVENT | ID_GEOMETRY); + InstanceData *inst = header->inst; + Mesh *mesh = meshh->getMeshes(); + uint32 offset = 0; + for(uint32 i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &inst->minVert, &inst->numVertices); + assert(inst->minVert != 0xFFFFFFFF); + inst->numIndex = mesh->numIndices; + inst->material = mesh->material; + inst->vertexAlpha = 0; + inst->program = 0; + inst->offset = offset; + memcpy((uint8*)header->indexBuffer + inst->offset, + mesh->indices, inst->numIndex*2); + offset += inst->numIndex*2; + mesh++; + inst++; + } + + header->vertexBuffer = nil; + header->numAttribs = 0; + header->attribDesc = nil; + header->ibo = 0; + header->vbo = 0; + +#ifdef RW_GL_USE_VAOS + glGenVertexArrays(1, &header->vao); + glBindVertexArray(header->vao); +#endif + glGenBuffers(1, &header->ibo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, header->totalNumIndex*2, + header->indexBuffer, GL_STATIC_DRAW); + + return header; +} + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // don't try to (re)instance native data + if(geo->flags & Geometry::NATIVE) + return; + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + if(geo->instData){ + // Already have instanced data, so check if we have to reinstance + assert(header->platform == PLATFORM_GL3); + if(header->serialNumber != geo->meshHeader->serialNum){ + // Mesh changed, so reinstance everything + freeInstanceData(geo); + } + } + + // no instance or complete reinstance + if(geo->instData == nil){ + geo->instData = instanceMesh(rwpipe, geo); + pipe->instanceCB(geo, (InstanceDataHeader*)geo->instData, 0); + }else if(geo->lockedSinceInst) + pipe->instanceCB(geo, (InstanceDataHeader*)geo->instData, 1); + + geo->lockedSinceInst = 0; +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + assert(0 && "can't uninstance"); +} + +static void +render(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + pipe->instance(atomic); + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_GL3); + if(pipe->renderCB) + pipe->renderCB(atomic, (InstanceDataHeader*)geo->instData); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_GL3); + this->impl.instance = gl3::instance; + this->impl.uninstance = gl3::uninstance; + this->impl.render = gl3::render; + this->instanceCB = nil; + this->uninstanceCB = nil; + this->renderCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +defaultInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) +{ + AttribDesc *attribs, *a; + + bool isPrelit = !!(geo->flags & Geometry::PRELIT); + bool hasNormals = !!(geo->flags & Geometry::NORMALS); + + if(!reinstance){ + AttribDesc tmpAttribs[12]; + uint32 stride; + + // + // Create attribute descriptions + // + a = tmpAttribs; + stride = 0; + + // Positions + a->index = ATTRIB_POS; + a->size = 3; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 12; + a++; + + // Normals + // TODO: compress + if(hasNormals){ + a->index = ATTRIB_NORMAL; + a->size = 3; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 12; + a++; + } + + // Prelighting + if(isPrelit){ + a->index = ATTRIB_COLOR; + a->size = 4; + a->type = GL_UNSIGNED_BYTE; + a->normalized = GL_TRUE; + a->offset = stride; + stride += 4; + a++; + } + + // Texture coordinates + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + a->index = ATTRIB_TEXCOORDS0+n; + a->size = 2; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 8; + a++; + } + + header->numAttribs = a - tmpAttribs; + for(a = tmpAttribs; a != &tmpAttribs[header->numAttribs]; a++) + a->stride = stride; + header->attribDesc = rwNewT(AttribDesc, header->numAttribs, MEMDUR_EVENT | ID_GEOMETRY); + memcpy(header->attribDesc, tmpAttribs, + header->numAttribs*sizeof(AttribDesc)); + + // + // Allocate vertex buffer + // + header->vertexBuffer = rwNewT(uint8, header->totalNumVertex*stride, MEMDUR_EVENT | ID_GEOMETRY); + assert(header->vbo == 0); + glGenBuffers(1, &header->vbo); + } + + attribs = header->attribDesc; + + // + // Fill vertex buffer + // + + uint8 *verts = header->vertexBuffer; + + // Positions + if(!reinstance || geo->lockedSinceInst&Geometry::LOCKVERTICES){ + for(a = attribs; a->index != ATTRIB_POS; a++) + ; + instV3d(VERT_FLOAT3, verts + a->offset, + geo->morphTargets[0].vertices, + header->totalNumVertex, a->stride); + } + + // Normals + if(hasNormals && (!reinstance || geo->lockedSinceInst&Geometry::LOCKNORMALS)){ + for(a = attribs; a->index != ATTRIB_NORMAL; a++) + ; + instV3d(VERT_FLOAT3, verts + a->offset, + geo->morphTargets[0].normals, + header->totalNumVertex, a->stride); + } + + // Prelighting + if(isPrelit && (!reinstance || geo->lockedSinceInst&Geometry::LOCKPRELIGHT)){ + for(a = attribs; a->index != ATTRIB_COLOR; a++) + ; + int n = header->numMeshes; + InstanceData *inst = header->inst; + while(n--){ + assert(inst->minVert != 0xFFFFFFFF); + inst->vertexAlpha = instColor(VERT_RGBA, + verts + a->offset + a->stride*inst->minVert, + geo->colors + inst->minVert, + inst->numVertices, a->stride); + inst++; + } + } + + // Texture coordinates + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + if(!reinstance || geo->lockedSinceInst&(Geometry::LOCKTEXCOORDS<index != ATTRIB_TEXCOORDS0+n; a++) + ; + instTexCoords(VERT_FLOAT2, verts + a->offset, + geo->texCoords[n], + header->totalNumVertex, a->stride); + } + } + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(header->vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); +#endif + glBindBuffer(GL_ARRAY_BUFFER, header->vbo); + glBufferData(GL_ARRAY_BUFFER, header->totalNumVertex*attribs[0].stride, + header->vertexBuffer, GL_STATIC_DRAW); +#ifdef RW_GL_USE_VAOS + setAttribPointers(header->attribDesc, header->numAttribs); + glBindVertexArray(0); +#endif +} + +void +defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + assert(0 && "can't uninstance"); +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = defaultInstanceCB; + pipe->uninstanceCB = defaultUninstanceCB; + pipe->renderCB = defaultRenderCB; + return pipe; +} + +#else +void *destroyNativeData(void *object, int32, int32) { return object; } +#endif + +} +} diff --git a/src/gl/gl3raster.cpp b/src/gl/gl3raster.cpp new file mode 100644 index 0000000..4e1aeab --- /dev/null +++ b/src/gl/gl3raster.cpp @@ -0,0 +1,663 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwgl3.h" +#include "rwgl3shader.h" + +#define PLUGIN_ID ID_DRIVER + +namespace rw { +namespace gl3 { + +int32 nativeRasterOffset; + +#ifdef RW_OPENGL +static Raster* +rasterCreateTexture(Raster *raster) +{ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + switch(raster->format & 0xF00){ + case Raster::C8888: + natras->internalFormat = GL_RGBA8; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 1; + natras->bpp = 4; + raster->depth = 32; + break; + case Raster::C888: + natras->internalFormat = GL_RGB8; + natras->format = GL_RGB; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 0; + natras->bpp = 3; + raster->depth = 24; + break; + case Raster::C1555: + natras->internalFormat = GL_RGB5_A1; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_SHORT_5_5_5_1; + natras->hasAlpha = 1; + natras->bpp = 2; + raster->depth = 16; + break; + default: + RWERROR((ERR_INVRASTER)); + return nil; + } + +#ifdef RW_GLES + // glReadPixels only supports GL_RGBA + natras->internalFormat = GL_RGBA8; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_BYTE; + natras->bpp = 4; +#endif + + raster->stride = raster->width*natras->bpp; + + glGenTextures(1, &natras->texid); + uint32 prev = bindTexture(natras->texid); + glTexImage2D(GL_TEXTURE_2D, 0, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, nil); + natras->filterMode = 0; + natras->addressU = 0; + natras->addressV = 0; + + bindTexture(prev); + return raster; +} + +static Raster* +rasterCreateCameraTexture(Raster *raster) +{ + if(raster->format & (Raster::PAL4 | Raster::PAL8)){ + RWERROR((ERR_NOTEXTURE)); + return nil; + } + + // TODO: figure out what the backbuffer is and use that as a default + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + switch(raster->format & 0xF00){ + case Raster::C8888: + natras->internalFormat = GL_RGBA8; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 1; + natras->bpp = 4; + break; + case Raster::C888: + default: + natras->internalFormat = GL_RGB8; + natras->format = GL_RGB; + natras->type = GL_UNSIGNED_BYTE; + natras->hasAlpha = 0; + natras->bpp = 3; + break; + case Raster::C1555: + natras->internalFormat = GL_RGB5_A1; + natras->format = GL_RGBA; + natras->type = GL_UNSIGNED_SHORT_5_5_5_1; + natras->hasAlpha = 1; + natras->bpp = 2; + break; + } + +#ifdef RW_GLES + // glReadPixels only supports GL_RGBA +// natras->internalFormat = GL_RGBA8; +// natras->format = GL_RGBA; +// natras->type = GL_UNSIGNED_BYTE; +// natras->bpp = 4; +#endif + + raster->stride = raster->width*natras->bpp; + + glGenTextures(1, &natras->texid); + uint32 prev = bindTexture(natras->texid); + glTexImage2D(GL_TEXTURE_2D, 0, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, nil); + natras->filterMode = 0; + natras->addressU = 0; + natras->addressV = 0; + + bindTexture(prev); + + + glGenFramebuffers(1, &natras->fbo); + bindFramebuffer(natras->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, natras->texid, 0); + bindFramebuffer(0); + natras->fboMate = nil; + + return raster; +} + +static Raster* +rasterCreateCamera(Raster *raster) +{ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + + // TODO: set/check width, height, depth, format? + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->stride = 0; + raster->pixels = nil; + + natras->texid = 0; + natras->fbo = 0; + natras->fboMate = nil; + + return raster; +} + +static Raster* +rasterCreateZbuffer(Raster *raster) +{ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + + // TODO: set/check width, height, depth, format? + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->stride = 0; + raster->pixels = nil; + + natras->internalFormat = GL_DEPTH_COMPONENT; + natras->format = GL_DEPTH_COMPONENT; + natras->type = GL_UNSIGNED_BYTE; + + glGenTextures(1, &natras->texid); + uint32 prev = bindTexture(natras->texid); + glTexImage2D(GL_TEXTURE_2D, 0, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, nil); + natras->filterMode = 0; + natras->addressU = 0; + natras->addressV = 0; + + bindTexture(prev); + + natras->fbo = 0; + natras->fboMate = nil; + + return raster; +} + +#endif + +/* +{ 0, 0, 0 }, +{ 16, 4, GL_RGBA }, // 1555 +{ 16, 3, GL_RGB }, // 565 +{ 16, 4, GL_RGBA }, // 4444 +{ 0, 0, 0 }, // LUM8 +{ 32, 4, GL_RGBA }, // 8888 +{ 24, 3, GL_RGB }, // 888 +{ 16, 3, GL_RGB }, // D16 +{ 24, 3, GL_RGB }, // D24 +{ 32, 4, GL_RGBA }, // D32 +{ 16, 3, GL_RGB }, // 555 + +0, +GL_RGB5_A1, +GL_RGB5, +GL_RGBA4, +0, +GL_RGBA8, +GL_RGB8, +GL_RGB5, +GL_RGB8, +GL_RGBA8, +GL_RGB5 +*/ + +Raster* +rasterCreate(Raster *raster) +{ + if(raster->width == 0 || raster->height == 0){ + raster->flags |= Raster::DONTALLOCATE; + raster->stride = 0; + return raster; + } + if(raster->flags & Raster::DONTALLOCATE) + return raster; + + switch(raster->type){ +#ifdef RW_OPENGL + case Raster::NORMAL: + case Raster::TEXTURE: + return rasterCreateTexture(raster); + case Raster::CAMERATEXTURE: + return rasterCreateCameraTexture(raster); + case Raster::ZBUFFER: + return rasterCreateZbuffer(raster); + case Raster::CAMERA: + return rasterCreateCamera(raster); +#endif + + default: + RWERROR((ERR_INVRASTER)); + return nil; + } +} + +uint8* +rasterLock(Raster *raster, int32 level, int32 lockMode) +{ +#ifdef RW_OPENGL + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + uint8 *px; + + assert(raster->privateFlags == 0); + + switch(raster->type & 0xF00){ + case Raster::NORMAL: + case Raster::TEXTURE: + case Raster::CAMERATEXTURE: + px = (uint8*)rwMalloc(raster->stride*raster->height, MEMDUR_EVENT | ID_DRIVER); +memset(px, 0, raster->stride*raster->height); + assert(raster->pixels == nil); + raster->pixels = px; + + if(lockMode & Raster::LOCKREAD || !(lockMode & Raster::LOCKNOFETCH)){ +#ifdef RW_GLES + GLuint fbo; +GLenum e; + glGenFramebuffers(1, &fbo); + bindFramebuffer(fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, natras->texid, 0); + e = glCheckFramebufferStatus(GL_FRAMEBUFFER); +assert(natras->format == GL_RGBA); + glReadPixels(0, 0, raster->width, raster->height, natras->format, natras->type, px); +//e = glGetError(); printf("GL err4 %x (%x)\n", e, natras->format); + bindFramebuffer(0); + glDeleteFramebuffers(1, &fbo); +#else + uint32 prev = bindTexture(natras->texid); + glGetTexImage(GL_TEXTURE_2D, level, natras->format, natras->type, px); + bindTexture(prev); +#endif + } + + raster->privateFlags = lockMode; + break; + + default: + assert(0 && "cannot lock this type of raster yet"); + return nil; + } + + return px; +#else + return nil; +#endif +} + +void +rasterUnlock(Raster *raster, int32 level) +{ +#ifdef RW_OPENGL + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + + assert(raster->pixels); + + if(raster->privateFlags & Raster::LOCKWRITE){ + uint32 prev = bindTexture(natras->texid); + glTexImage2D(GL_TEXTURE_2D, level, natras->internalFormat, + raster->width, raster->height, + 0, natras->format, natras->type, raster->pixels); + bindTexture(prev); + } + + rwFree(raster->pixels); + raster->pixels = nil; + raster->privateFlags = 0; +#endif +} + +int32 +rasterNumLevels(Raster*) +{ + // TODO + return 1; +} + +// Almost the same as d3d9 and ps2 function +bool32 +imageFindRasterFormat(Image *img, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat) +{ + int32 width, height, depth, format; + + assert((type&0xF) == Raster::TEXTURE); + +// for(width = 1; width < img->width; width <<= 1); +// for(height = 1; height < img->height; height <<= 1); + // Perhaps non-power-of-2 textures are acceptable? + width = img->width; + height = img->height; + + depth = img->depth; + + if(depth <= 8) + depth = 32; + + switch(depth){ + case 32: + if(img->hasAlpha()) + format = Raster::C8888; + else{ + format = Raster::C888; + depth = 24; + } + break; + case 24: + format = Raster::C888; + break; + case 16: + format = Raster::C1555; + break; + + case 8: + case 4: + default: + RWERROR((ERR_INVRASTER)); + return 0; + } + + format |= type; + + *pWidth = width; + *pHeight = height; + *pDepth = depth; + *pFormat = format; + + return 1; +} + +bool32 +rasterFromImage(Raster *raster, Image *image) +{ + if((raster->type&0xF) != Raster::TEXTURE) + return 0; + + void (*conv)(uint8 *out, uint8 *in) = nil; + + // Unpalettize image if necessary but don't change original + Image *truecolimg = nil; + if(image->depth <= 8){ + truecolimg = Image::create(image->width, image->height, image->depth); + truecolimg->pixels = image->pixels; + truecolimg->stride = image->stride; + truecolimg->palette = image->palette; + truecolimg->unpalettize(); + image = truecolimg; + } + + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + switch(image->depth){ + case 32: +#ifdef RW_GLES + conv = conv_RGBA8888_from_RGBA8888; +#else + if(raster->format == Raster::C8888) + conv = conv_RGBA8888_from_RGBA8888; + else if(raster->format == Raster::C888) + conv = conv_RGB888_from_RGB888; + else + goto err; +#endif + break; + case 24: +#ifdef RW_GLES + conv = conv_RGBA8888_from_RGB888; +#else + if(raster->format == Raster::C8888) + conv = conv_RGBA8888_from_RGB888; + else if(raster->format == Raster::C888) + conv = conv_RGB888_from_RGB888; + else + goto err; +#endif + break; + case 16: +#ifdef RW_GLES + conv = conv_RGBA8888_from_ARGB1555; +#else + if(raster->format == Raster::C1555) + conv = conv_RGBA5551_from_ARGB1555; + else + goto err; +#endif + break; + + case 8: + case 4: + default: + err: + RWERROR((ERR_INVRASTER)); + return 0; + } + + natras->hasAlpha = image->hasAlpha(); + + uint8 *pixels = raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + assert(pixels); + uint8 *imgpixels = image->pixels + (image->height-1)*image->stride; + + int x, y; + assert(image->width == raster->width); + assert(image->height == raster->height); + for(y = 0; y < image->height; y++){ + uint8 *imgrow = imgpixels; + uint8 *rasrow = pixels; + for(x = 0; x < image->width; x++){ + conv(rasrow, imgrow); + imgrow += image->bpp; + rasrow += natras->bpp; + } + imgpixels -= image->stride; + pixels += raster->stride; + } + raster->unlock(0); + + if(truecolimg) + truecolimg->destroy(); + + return 1; +} + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + Gl3Raster *ras = PLUGINOFFSET(Gl3Raster, object, offset); + ras->texid = 0; + ras->fbo = 0; + ras->fboMate = nil; + return object; +} + +static void* +destroyNativeRaster(void *object, int32 offset, int32) +{ + Raster *raster = (Raster*)object; + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, object, offset); +#ifdef RW_OPENGL + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + glDeleteTextures(1, &natras->texid); + break; + + case Raster::CAMERATEXTURE: + if(natras->fboMate){ + // Break apart from currently associated zbuffer + Gl3Raster *zras = PLUGINOFFSET(Gl3Raster, natras->fboMate, offset); + zras->fboMate = nil; + natras->fboMate = nil; + } + glDeleteFramebuffers(1, &natras->fbo); + glDeleteTextures(1, &natras->texid); + break; + + case Raster::ZBUFFER: + if(natras->fboMate){ + // Detatch from FBO we may be attached to + Gl3Raster *oldfb = PLUGINOFFSET(Gl3Raster, natras->fboMate, nativeRasterOffset); + if(oldfb->fbo){ + bindFramebuffer(oldfb->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + oldfb->fboMate = nil; + } + glDeleteTextures(1, &natras->texid); + break; + + case Raster::CAMERA: + break; + } + natras->texid = 0; + natras->fbo = 0; + +#endif + return object; +} + +static void* +copyNativeRaster(void *dst, void *, int32 offset, int32) +{ + Gl3Raster *d = PLUGINOFFSET(Gl3Raster, dst, offset); + d->texid = 0; + d->fbo = 0; + d->fboMate = nil; + return dst; +} + +static uint32 +getLevelSize(Raster *raster, int32 level) +{ + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + uint32 size = raster->stride*raster->height; + while(level--) + size /= 4; + return size; +} + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_GL3){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + // Texture + tex->filterAddressing = stream->readU32(); + stream->read8(tex->name, 32); + stream->read8(tex->mask, 32); + + // Raster + uint32 format = stream->readU32(); + int32 width = stream->readI32(); + int32 height = stream->readI32(); + int32 depth = stream->readI32(); + int32 numLevels = stream->readI32(); + + Raster *raster; + Gl3Raster *natras; + raster = Raster::create(width, height, depth, format | Raster::TEXTURE, PLATFORM_GL3); + assert(raster); + natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + tex->raster = raster; + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = stream->readU32(); + if(i < raster->getNumLevels()){ + data = raster->lock(i, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + stream->read8(data, size); + raster->unlock(i); + }else + stream->seek(size); + } + return tex; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + Raster *raster = tex->raster; + Gl3Raster *natras = PLUGINOFFSET(Gl3Raster, raster, nativeRasterOffset); + + int32 chunksize = getSizeNativeTexture(tex); + writeChunkHeader(stream, ID_STRUCT, chunksize-12); + stream->writeU32(PLATFORM_GL3); + + // Texture + stream->writeU32(tex->filterAddressing); + stream->write8(tex->name, 32); + stream->write8(tex->mask, 32); + + // Raster + int32 numLevels = raster->getNumLevels(); + stream->writeI32(raster->format); + stream->writeI32(raster->width); + stream->writeI32(raster->height); + stream->writeI32(raster->depth); + stream->writeI32(numLevels); + // TODO: compression? auto mipmaps? + + uint32 size; + uint8 *data; + for(int32 i = 0; i < numLevels; i++){ + size = getLevelSize(raster, i); + stream->writeU32(size); + data = raster->lock(i, Raster::LOCKREAD); + stream->write8(data, size); + raster->unlock(i); + } +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 72 + 20; + int32 levels = tex->raster->getNumLevels(); + for(int32 i = 0; i < levels; i++) + size += 4 + getLevelSize(tex->raster, i); + return size; +} + + + +void registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(Gl3Raster), + ID_RASTERGL3, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); +} + +} +} diff --git a/src/gl/gl3render.cpp b/src/gl/gl3render.cpp new file mode 100644 index 0000000..6055f44 --- /dev/null +++ b/src/gl/gl3render.cpp @@ -0,0 +1,163 @@ +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#ifdef RW_OPENGL +#include "rwgl3.h" +#include "rwgl3shader.h" + +#include "rwgl3impl.h" + +namespace rw { +namespace gl3 { + +#define MAX_LIGHTS + +void +drawInst_simple(InstanceDataHeader *header, InstanceData *inst) +{ + flushCache(); + glDrawElements(header->primType, inst->numIndex, + GL_UNSIGNED_SHORT, (void*)(uintptr)inst->offset); +} + +// Emulate PS2 GS alpha test FB_ONLY case: failed alpha writes to frame- but not to depth buffer +void +drawInst_GSemu(InstanceDataHeader *header, InstanceData *inst) +{ + uint32 hasAlpha; + int alphafunc, alpharef, gsalpharef; + int zwrite; + hasAlpha = getAlphaBlend(); + if(hasAlpha){ + zwrite = rw::GetRenderState(rw::ZWRITEENABLE); + alphafunc = rw::GetRenderState(rw::ALPHATESTFUNC); + if(zwrite){ + alpharef = rw::GetRenderState(rw::ALPHATESTREF); + gsalpharef = rw::GetRenderState(rw::GSALPHATESTREF); + + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAGREATEREQUAL); + SetRenderState(rw::ALPHATESTREF, gsalpharef); + drawInst_simple(header, inst); + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHALESS); + SetRenderState(rw::ZWRITEENABLE, 0); + drawInst_simple(header, inst); + SetRenderState(rw::ZWRITEENABLE, 1); + SetRenderState(rw::ALPHATESTFUNC, alphafunc); + SetRenderState(rw::ALPHATESTREF, alpharef); + }else{ + SetRenderState(rw::ALPHATESTFUNC, rw::ALPHAALWAYS); + drawInst_simple(header, inst); + SetRenderState(rw::ALPHATESTFUNC, alphafunc); + } + }else + drawInst_simple(header, inst); +} + +void +drawInst(InstanceDataHeader *header, InstanceData *inst) +{ + if(rw::GetRenderState(rw::GSALPHATEST)) + drawInst_GSemu(header, inst); + else + drawInst_simple(header, inst); +} + + +void +setAttribPointers(AttribDesc *attribDescs, int32 numAttribs) +{ + AttribDesc *a; + for(a = attribDescs; a != &attribDescs[numAttribs]; a++){ + glEnableVertexAttribArray(a->index); + glVertexAttribPointer(a->index, a->size, a->type, a->normalized, + a->stride, (void*)(uint64)a->offset); + } +} + +void +disableAttribPointers(AttribDesc *attribDescs, int32 numAttribs) +{ + AttribDesc *a; + for(a = attribDescs; a != &attribDescs[numAttribs]; a++) + glDisableVertexAttribArray(a->index); +} + +int32 +lightingCB(Atomic *atomic) +{ + WorldLights lightData; + Light *directionals[8]; + Light *locals[8]; + lightData.directionals = directionals; + lightData.numDirectionals = 8; + lightData.locals = locals; + lightData.numLocals = 8; + + if(atomic->geometry->flags & rw::Geometry::LIGHT){ + ((World*)engine->currentWorld)->enumerateLights(atomic, &lightData); + if((atomic->geometry->flags & rw::Geometry::NORMALS) == 0){ + // Get rid of lights that need normals when we don't have any + lightData.numDirectionals = 0; + lightData.numLocals = 0; + } + return setLights(&lightData); + }else{ + memset(&lightData, 0, sizeof(lightData)); + return setLights(&lightData); + } +} + +#define U(i) currentShader->uniformLocations[i] + +void +defaultRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + Material *m; + + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(header->vao); +#else + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); + glBindBuffer(GL_ARRAY_BUFFER, header->vbo); + setAttribPointers(header->attribDesc, header->numAttribs); +#endif + + InstanceData *inst = header->inst; + int32 n = header->numMeshes; + + defaultShader->use(); + + while(n--){ + m = inst->material; + + setMaterial(m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); + inst++; + } +#ifndef RW_GL_USE_VAOS + disableAttribPointers(header->attribDesc, header->numAttribs); +#endif +} + + +} +} + +#endif + diff --git a/src/gl/gl3shader.cpp b/src/gl/gl3shader.cpp new file mode 100644 index 0000000..b51623e --- /dev/null +++ b/src/gl/gl3shader.cpp @@ -0,0 +1,273 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#ifdef RW_OPENGL +#include "rwgl3.h" +#include "rwgl3shader.h" + +namespace rw { +namespace gl3 { + +#ifdef RW_GLES2 +#include "gl2_shaders/header_vs.inc" +#include "gl2_shaders/header_fs.inc" +#else +#include "shaders/header_vs.inc" +#include "shaders/header_fs.inc" +#endif + +UniformRegistry uniformRegistry; + +int32 +registerUniform(const char *name) +{ + int i; + i = findUniform(name); + if(i >= 0) return i; + // TODO: print error + if(uniformRegistry.numUniforms+1 >= MAX_UNIFORMS){ + assert(0 && "no space for uniform"); + return -1; + } + uniformRegistry.uniformNames[uniformRegistry.numUniforms] = strdup(name); + return uniformRegistry.numUniforms++; +} + +int32 +findUniform(const char *name) +{ + int i; + for(i = 0; i < uniformRegistry.numUniforms; i++) + if(strcmp(name, uniformRegistry.uniformNames[i]) == 0) + return i; + return -1; +} + +int32 +registerBlock(const char *name) +{ + int i; + i = findBlock(name); + if(i >= 0) return i; + // TODO: print error + if(uniformRegistry.numBlocks+1 >= MAX_BLOCKS) + return -1; + uniformRegistry.blockNames[uniformRegistry.numBlocks] = strdup(name); + return uniformRegistry.numBlocks++; +} + +int32 +findBlock(const char *name) +{ + int i; + for(i = 0; i < uniformRegistry.numBlocks; i++) + if(strcmp(name, uniformRegistry.blockNames[i]) == 0) + return i; + return -1; +} + +Shader *currentShader; + +static void +printShaderSource(const char **src) +{ + int f; + const char *file; + bool printline; + int line = 1; + for(f = 0; src[f]; f++){ + int fileline = 1; + char c; + file = src[f]; + printline = true; + while(c = *file++, c != '\0'){ + if(printline) + printf("%.4d/%d:%.4d: ", line++, f, fileline++); + putchar(c); + printline = c == '\n'; + } + putchar('\n'); + } +} + +static int +compileshader(GLenum type, const char **src, GLuint *shader) +{ + GLint n; + GLint shdr, success; + GLint len; + char *log; + + for(n = 0; src[n]; n++); + + shdr = glCreateShader(type); + glShaderSource(shdr, n, src, nil); + glCompileShader(shdr); + glGetShaderiv(shdr, GL_COMPILE_STATUS, &success); + if(!success){ + printShaderSource(src); + fprintf(stderr, "Error in %s shader\n", + type == GL_VERTEX_SHADER ? "vertex" : "fragment"); + glGetShaderiv(shdr, GL_INFO_LOG_LENGTH, &len); + log = (char*)rwMalloc(len, MEMDUR_FUNCTION); + glGetShaderInfoLog(shdr, len, nil, log); + fprintf(stderr, "%s\n", log); + rwFree(log); + return 1; + } + *shader = shdr; + return 0; +} + +static int +linkprogram(GLint vs, GLint fs, GLuint *program) +{ + GLint prog, success; + GLint len; + char *log; + + prog = glCreateProgram(); + +#ifdef RW_GLES2 + // TODO: perhaps just do this always and get rid of the layout stuff? + glBindAttribLocation(prog, ATTRIB_POS, "in_pos"); + glBindAttribLocation(prog, ATTRIB_NORMAL, "in_normal"); + glBindAttribLocation(prog, ATTRIB_COLOR, "in_color"); + glBindAttribLocation(prog, ATTRIB_TEXCOORDS0, "in_tex0"); + glBindAttribLocation(prog, ATTRIB_WEIGHTS, "in_weights"); + glBindAttribLocation(prog, ATTRIB_INDICES, "in_indices"); +#endif + + glAttachShader(prog, vs); + glAttachShader(prog, fs); + glLinkProgram(prog); + glGetProgramiv(prog, GL_LINK_STATUS, &success); + if(!success){ + fprintf(stderr, "Error in program\n"); + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len); + log = (char*)rwMalloc(len, MEMDUR_FUNCTION); + glGetProgramInfoLog(prog, len, nil, log); + fprintf(stderr, "%s\n", log); + rwFree(log); + return 1; + } + *program = prog; + return 0; +} + +Shader* +Shader::create(const char **vsrc, const char **fsrc) +{ + GLuint vs, fs, program; + int i; + int fail; + + fail = compileshader(GL_VERTEX_SHADER, vsrc, &vs); + if(fail) + return nil; + + fail = compileshader(GL_FRAGMENT_SHADER, fsrc, &fs); + if(fail){ + glDeleteShader(vs); + return nil; + } + + fail = linkprogram(vs, fs, &program); + if(fail){ + glDeleteShader(fs); + glDeleteShader(vs); + return nil; + } + glDeleteProgram(vs); + glDeleteProgram(fs); + + Shader *sh = rwNewT(Shader, 1, MEMDUR_EVENT | ID_DRIVER); // or global? + +#ifdef xxxRW_GLES2 + int numUniforms; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numUniforms); + for(i = 0; i < numUniforms; i++){ + GLint size; + GLenum type; + char name[100]; + glGetActiveUniform(program, i, 100, nil, &size, &type, name); + printf("%d %d %s\n", size, type, name); + } + printf("\n"); +#endif + +#ifdef xxxRW_GLES2 + int numAttribs; + glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &numAttribs); + for(i = 0; i < numAttribs; i++){ + GLint size; + GLenum type; + char name[100]; + glGetActiveAttrib(program, i, 100, nil, &size, &type, name); + GLint bind = glGetAttribLocation(program, name); + printf("%d %d %s. %d\n", size, type, name, bind); + } + printf("\n"); +#endif + + // set uniform block binding + for(i = 0; i < uniformRegistry.numBlocks; i++){ + int idx = glGetUniformBlockIndex(program, + uniformRegistry.blockNames[i]); + if(idx >= 0) + glUniformBlockBinding(program, idx, i); + } + + // query uniform locations + sh->program = program; + sh->uniformLocations = rwNewT(GLint, uniformRegistry.numUniforms, MEMDUR_EVENT | ID_DRIVER); + for(i = 0; i < uniformRegistry.numUniforms; i++) + sh->uniformLocations[i] = glGetUniformLocation(program, + uniformRegistry.uniformNames[i]); + + // set samplers + glUseProgram(program); + char name[64]; + GLint loc; + for(i = 0; i < 4; i++){ + sprintf(name, "tex%d", i); + loc = glGetUniformLocation(program, name); + glUniform1i(loc, i); + } + + // reset program + if(currentShader) + glUseProgram(currentShader->program); + + return sh; +} + +void +Shader::use(void) +{ + if(currentShader != this){ + glUseProgram(this->program); + currentShader = this; + } +} + +void +Shader::destroy(void) +{ + glDeleteProgram(this->program); + rwFree(this->uniformLocations); + rwFree(this); +} + +} +} + +#endif diff --git a/src/gl/gl3skin.cpp b/src/gl/gl3skin.cpp new file mode 100644 index 0000000..cc55301 --- /dev/null +++ b/src/gl/gl3skin.cpp @@ -0,0 +1,353 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwrender.h" +#include "../rwengine.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwplugins.h" +#include "rwgl3.h" +#include "rwgl3shader.h" +#include "rwgl3plg.h" + +#include "rwgl3impl.h" + +namespace rw { +namespace gl3 { + +#ifdef RW_OPENGL + +#define U(i) currentShader->uniformLocations[i] + +static Shader *skinShader; +static int32 u_boneMatrices; + +void +skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance) +{ + AttribDesc *attribs, *a; + + bool isPrelit = !!(geo->flags & Geometry::PRELIT); + bool hasNormals = !!(geo->flags & Geometry::NORMALS); + + if(!reinstance){ + AttribDesc tmpAttribs[14]; + uint32 stride; + + // + // Create attribute descriptions + // + a = tmpAttribs; + stride = 0; + + // Positions + a->index = ATTRIB_POS; + a->size = 3; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 12; + a++; + + // Normals + // TODO: compress + if(hasNormals){ + a->index = ATTRIB_NORMAL; + a->size = 3; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 12; + a++; + } + + // Prelighting + if(isPrelit){ + a->index = ATTRIB_COLOR; + a->size = 4; + a->type = GL_UNSIGNED_BYTE; + a->normalized = GL_TRUE; + a->offset = stride; + stride += 4; + a++; + } + + // Texture coordinates + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + a->index = ATTRIB_TEXCOORDS0+n; + a->size = 2; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 8; + a++; + } + + // Weights + a->index = ATTRIB_WEIGHTS; + a->size = 4; + a->type = GL_FLOAT; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 16; + a++; + + // Indices + a->index = ATTRIB_INDICES; + a->size = 4; + a->type = GL_UNSIGNED_BYTE; + a->normalized = GL_FALSE; + a->offset = stride; + stride += 4; + a++; + + header->numAttribs = a - tmpAttribs; + for(a = tmpAttribs; a != &tmpAttribs[header->numAttribs]; a++) + a->stride = stride; + header->attribDesc = rwNewT(AttribDesc, header->numAttribs, MEMDUR_EVENT | ID_GEOMETRY); + memcpy(header->attribDesc, tmpAttribs, + header->numAttribs*sizeof(AttribDesc)); + + // + // Allocate vertex buffer + // + header->vertexBuffer = rwNewT(uint8, header->totalNumVertex*stride, MEMDUR_EVENT | ID_GEOMETRY); + assert(header->vbo == 0); + glGenBuffers(1, &header->vbo); + } + + Skin *skin = Skin::get(geo); + attribs = header->attribDesc; + + // + // Fill vertex buffer + // + + uint8 *verts = header->vertexBuffer; + + // Positions + if(!reinstance || geo->lockedSinceInst&Geometry::LOCKVERTICES){ + for(a = attribs; a->index != ATTRIB_POS; a++) + ; + instV3d(VERT_FLOAT3, verts + a->offset, + geo->morphTargets[0].vertices, + header->totalNumVertex, a->stride); + } + + // Normals + if(hasNormals && (!reinstance || geo->lockedSinceInst&Geometry::LOCKNORMALS)){ + for(a = attribs; a->index != ATTRIB_NORMAL; a++) + ; + instV3d(VERT_FLOAT3, verts + a->offset, + geo->morphTargets[0].normals, + header->totalNumVertex, a->stride); + } + + // Prelighting + if(isPrelit && (!reinstance || geo->lockedSinceInst&Geometry::LOCKPRELIGHT)){ + for(a = attribs; a->index != ATTRIB_COLOR; a++) + ; + instColor(VERT_RGBA, verts + a->offset, + geo->colors, + header->totalNumVertex, a->stride); + } + + // Texture coordinates + for(int32 n = 0; n < geo->numTexCoordSets; n++){ + if(!reinstance || geo->lockedSinceInst&(Geometry::LOCKTEXCOORDS<index != ATTRIB_TEXCOORDS0+n; a++) + ; + instTexCoords(VERT_FLOAT2, verts + a->offset, + geo->texCoords[n], + header->totalNumVertex, a->stride); + } + } + + // Weights + if(!reinstance){ + for(a = attribs; a->index != ATTRIB_WEIGHTS; a++) + ; + float *w = skin->weights; + instV4d(VERT_FLOAT4, verts + a->offset, + (V4d*)w, + header->totalNumVertex, a->stride); + } + + // Indices + if(!reinstance){ + for(a = attribs; a->index != ATTRIB_INDICES; a++) + ; + // not really colors of course but what the heck + instColor(VERT_RGBA, verts + a->offset, + (RGBA*)skin->indices, + header->totalNumVertex, a->stride); + } + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(header->vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); +#endif + glBindBuffer(GL_ARRAY_BUFFER, header->vbo); + glBufferData(GL_ARRAY_BUFFER, header->totalNumVertex*attribs[0].stride, + header->vertexBuffer, GL_STATIC_DRAW); +#ifdef RW_GL_USE_VAOS + setAttribPointers(header->attribDesc, header->numAttribs); + glBindVertexArray(0); +#endif +} + +void +skinUninstanceCB(Geometry *geo, InstanceDataHeader *header) +{ + assert(0 && "can't uninstance"); +} + +static float skinMatrices[64*16]; + +void +uploadSkinMatrices(Atomic *a) +{ + int i; + Skin *skin = Skin::get(a->geometry); + Matrix *m = (Matrix*)skinMatrices; + HAnimHierarchy *hier = Skin::getHierarchy(a); + + if(hier){ + Matrix *invMats = (Matrix*)skin->inverseMatrices; + Matrix tmp; + + assert(skin->numBones == hier->numNodes); + if(hier->flags & HAnimHierarchy::LOCALSPACEMATRICES){ + for(i = 0; i < hier->numNodes; i++){ + invMats[i].flags = 0; + Matrix::mult(m, &invMats[i], &hier->matrices[i]); + m++; + } + }else{ + Matrix invAtmMat; + Matrix::invert(&invAtmMat, a->getFrame()->getLTM()); + for(i = 0; i < hier->numNodes; i++){ + invMats[i].flags = 0; + Matrix::mult(&tmp, &hier->matrices[i], &invAtmMat); + Matrix::mult(m, &invMats[i], &tmp); + m++; + } + } + }else{ + for(i = 0; i < skin->numBones; i++){ + m->setIdentity(); + m++; + } + } + glUniformMatrix4fv(U(u_boneMatrices), 64, GL_FALSE, + (GLfloat*)skinMatrices); +} + +void +skinRenderCB(Atomic *atomic, InstanceDataHeader *header) +{ + Material *m; + + setWorldMatrix(atomic->getFrame()->getLTM()); + lightingCB(atomic); + +#ifdef RW_GL_USE_VAOS + glBindVertexArray(header->vao); +#else + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, header->ibo); + glBindBuffer(GL_ARRAY_BUFFER, header->vbo); + setAttribPointers(header->attribDesc, header->numAttribs); +#endif + + InstanceData *inst = header->inst; + int32 n = header->numMeshes; + + skinShader->use(); + + uploadSkinMatrices(atomic); + + while(n--){ + m = inst->material; + + setMaterial(m->color, m->surfaceProps); + + setTexture(0, m->texture); + + rw::SetRenderState(VERTEXALPHA, inst->vertexAlpha || m->color.alpha != 0xFF); + + drawInst(header, inst); + inst++; + } +#ifndef RW_GL_USE_VAOS + disableAttribPointers(header->attribDesc, header->numAttribs); +#endif +} + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_GL3] = makeSkinPipeline(); + +#ifdef RW_GLES2 +#include "gl2_shaders/simple_fs_gl2.inc" +#include "gl2_shaders/skin_gl2.inc" +#else +#include "shaders/simple_fs_gl3.inc" +#include "shaders/skin_gl3.inc" +#endif + const char *vs[] = { shaderDecl, header_vert_src, skin_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, simple_frag_src, nil }; + skinShader = Shader::create(vs, fs); + assert(skinShader); + + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_GL3])->destroy(); + skinGlobals.pipelines[PLATFORM_GL3] = nil; + + skinShader->destroy(); + skinShader = nil; + + return o; +} + +void +initSkin(void) +{ + u_boneMatrices = registerUniform("u_boneMatrices"); + + Driver::registerPlugin(PLATFORM_GL3, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = skinUninstanceCB; + pipe->renderCB = skinRenderCB; + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + return pipe; +} + +#else + +void initSkin(void) { } + +#endif + +} +} + diff --git a/src/gl/rwgl3.h b/src/gl/rwgl3.h new file mode 100644 index 0000000..f003c43 --- /dev/null +++ b/src/gl/rwgl3.h @@ -0,0 +1,265 @@ +#ifdef RW_GL3 + +#ifdef __SWITCH__ // switch must be included early for some defines that are needed later on for opengl stuff +#include +#endif + +#ifndef LIBRW_GLAD +#include +#else +#include +#endif + +#ifdef LIBRW_SDL2 +#include +#else +#include +#endif +#endif + +namespace rw { + +#ifdef RW_GL3 +struct EngineOpenParams +{ +#ifdef LIBRW_SDL2 + SDL_Window **window; + bool32 fullscreen; +#else + GLFWwindow **window; +#endif + int width, height; + const char *windowtitle; +}; +#endif + +namespace gl3 { + +void registerPlatformPlugins(void); + +extern Device renderdevice; + +// arguments to glVertexAttribPointer basically +struct AttribDesc +{ + uint32 index; + int32 type; + bool32 normalized; + int32 size; + uint32 stride; + uint32 offset; +}; + +enum AttribIndices +{ + ATTRIB_POS = 0, + ATTRIB_NORMAL, + ATTRIB_COLOR, + ATTRIB_TEXCOORDS0, + ATTRIB_TEXCOORDS1, +#ifndef RW_GLES2 + ATTRIB_TEXCOORDS2, + ATTRIB_TEXCOORDS3, + ATTRIB_TEXCOORDS4, + ATTRIB_TEXCOORDS5, + ATTRIB_TEXCOORDS6, + ATTRIB_TEXCOORDS7, +#endif + ATTRIB_WEIGHTS, + ATTRIB_INDICES +}; + +// default uniform indices +extern int32 u_matColor; +extern int32 u_surfProps; + +struct InstanceData +{ + uint32 numIndex; + uint32 minVert; // not used for rendering + int32 numVertices; // + Material *material; + bool32 vertexAlpha; + uint32 program; + uint32 offset; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + uint32 serialNumber; + uint32 numMeshes; + uint16 *indexBuffer; + uint32 primType; + uint8 *vertexBuffer; + int32 numAttribs; + AttribDesc *attribDesc; + uint32 totalNumIndex; + uint32 totalNumVertex; + + uint32 ibo; + uint32 vbo; // or 2? +#ifdef RW_GL_USE_VAOS + uint32 vao; +#endif + + InstanceData *inst; +}; + +#ifdef RW_GL3 + +struct Shader; + +extern Shader *defaultShader; + +struct Im3DVertex +{ + V3d position; + uint8 r, g, b, a; + float32 u, v; + + void setX(float32 x) { this->position.x = x; } + void setY(float32 y) { this->position.y = y; } + void setZ(float32 z) { this->position.z = z; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { + this->r = r; this->g = g; this->b = b; this->a = a; } + void setU(float32 u) { this->u = u; } + void setV(float32 v) { this->v = v; } + + float getX(void) { return this->position.x; } + float getY(void) { return this->position.y; } + float getZ(void) { return this->position.z; } + RGBA getColor(void) { return makeRGBA(this->r, this->g, this->b, this->a); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + +struct Im2DVertex +{ + float32 x, y, z, w; + uint8 r, g, b, a; + float32 u, v; + + void setScreenX(float32 x) { this->x = x; } + void setScreenY(float32 y) { this->y = y; } + void setScreenZ(float32 z) { this->z = z; } + void setCameraZ(float32 z) { this->w = z; } + void setRecipCameraZ(float32 recipz) { } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { + this->r = r; this->g = g; this->b = b; this->a = a; } + void setU(float32 u, float recipz) { this->u = u; } + void setV(float32 v, float recipz) { this->v = v; } + + float getScreenX(void) { return this->x; } + float getScreenY(void) { return this->y; } + float getScreenZ(void) { return this->z; } + float getCameraZ(void) { return this->w; } + float getRecipCameraZ(void) { return 1.0f/this->w; } + RGBA getColor(void) { return makeRGBA(this->r, this->g, this->b, this->a); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + +void setAttribPointers(AttribDesc *attribDescs, int32 numAttribs); +void disableAttribPointers(AttribDesc *attribDescs, int32 numAttribs); + +// Render state + +// Vertex shader bits +enum +{ + // These should be low so they could be used as indices + VSLIGHT_DIRECT = 1, + VSLIGHT_POINT = 2, + VSLIGHT_SPOT = 4, + VSLIGHT_MASK = 7, // all the above + // less critical + VSLIGHT_AMBIENT = 8, +}; + +extern const char *shaderDecl; // #version stuff +extern const char *header_vert_src; +extern const char *header_frag_src; + +extern Shader *im2dOverrideShader; + +// per Scene +void setProjectionMatrix(float32*); +void setViewMatrix(float32*); + +// per Object +void setWorldMatrix(Matrix*); +int32 setLights(WorldLights *lightData); + +// per Mesh +void setTexture(int32 n, Texture *tex); +void setMaterial(const RGBA &color, const SurfaceProperties &surfaceprops); + +void setAlphaBlend(bool32 enable); +bool32 getAlphaBlend(void); + +void bindFramebuffer(uint32 fbo); +uint32 bindTexture(uint32 texid); + +void flushCache(void); + +#endif + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + void (*instanceCB)(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); + void (*uninstanceCB)(Geometry *geo, InstanceDataHeader *header); + void (*renderCB)(Atomic *atomic, InstanceDataHeader *header); +}; + +void defaultInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); +void defaultUninstanceCB(Geometry *geo, InstanceDataHeader *header); +void defaultRenderCB(Atomic *atomic, InstanceDataHeader *header); +int32 lightingCB(Atomic *atomic); + +void drawInst_simple(InstanceDataHeader *header, InstanceData *inst); +// Emulate PS2 GS alpha test FB_ONLY case: failed alpha writes to frame- but not to depth buffer +void drawInst_GSemu(InstanceDataHeader *header, InstanceData *inst); +// This one switches between the above two depending on render state; +void drawInst(InstanceDataHeader *header, InstanceData *inst); + + +void *destroyNativeData(void *object, int32, int32); + +ObjPipeline *makeDefaultPipeline(void); + +// Native Texture and Raster + +extern int32 nativeRasterOffset; + +struct Gl3Raster +{ + // arguments to glTexImage2D + int32 internalFormat; + int32 type; + int32 format; + int32 bpp; // bytes per pixel + // texture object + uint32 texid; + + bool32 hasAlpha; + // cached filtermode and addressing + uint8 filterMode; + uint8 addressU; + uint8 addressV; + + uint32 fbo; // used for camera texture only! + Raster *fboMate; // color or zbuffer raster mate of this one +}; + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +void registerNativeRaster(void); + +} +} diff --git a/src/gl/rwgl3impl.h b/src/gl/rwgl3impl.h new file mode 100644 index 0000000..8c5e428 --- /dev/null +++ b/src/gl/rwgl3impl.h @@ -0,0 +1,35 @@ +namespace rw { +namespace gl3 { + +#ifdef RW_OPENGL + +extern uint32 im2DVbo, im2DIbo; +void openIm2D(void); +void closeIm2D(void); +void im2DRenderLine(void *vertices, int32 numVertices, + int32 vert1, int32 vert2); +void im2DRenderTriangle(void *vertices, int32 numVertices, + int32 vert1, int32 vert2, int32 vert3); +void im2DRenderPrimitive(PrimitiveType primType, + void *vertices, int32 numVertices); +void im2DRenderIndexedPrimitive(PrimitiveType primType, + void *vertices, int32 numVertices, void *indices, int32 numIndices); + +void openIm3D(void); +void closeIm3D(void); +void im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags); +void im3DRenderPrimitive(PrimitiveType primType); +void im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices); +void im3DEnd(void); +#endif + +Raster *rasterCreate(Raster *raster); +uint8 *rasterLock(Raster*, int32 level, int32 lockMode); +void rasterUnlock(Raster*, int32); +int32 rasterNumLevels(Raster*); +bool32 imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); +bool32 rasterFromImage(Raster *raster, Image *image); + +} +} diff --git a/src/gl/rwgl3plg.h b/src/gl/rwgl3plg.h new file mode 100644 index 0000000..5374b3e --- /dev/null +++ b/src/gl/rwgl3plg.h @@ -0,0 +1,16 @@ +namespace rw { +namespace gl3 { + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); +void matfxRenderCB(Atomic *atomic, InstanceDataHeader *header); + +void initSkin(void); +ObjPipeline *makeSkinPipeline(void); +void skinInstanceCB(Geometry *geo, InstanceDataHeader *header, bool32 reinstance); +void skinRenderCB(Atomic *atomic, InstanceDataHeader *header); +void uploadSkinMatrices(Atomic *atomic); + + +} +} diff --git a/src/gl/rwgl3shader.h b/src/gl/rwgl3shader.h new file mode 100644 index 0000000..f9bb632 --- /dev/null +++ b/src/gl/rwgl3shader.h @@ -0,0 +1,46 @@ +#ifdef RW_OPENGL + +namespace rw { +namespace gl3 { + +// TODO: make this dynamic +enum { + MAX_UNIFORMS = 40, + MAX_BLOCKS = 20 +}; + +struct UniformRegistry +{ + int32 numUniforms; + char *uniformNames[MAX_UNIFORMS]; + + int32 numBlocks; + char *blockNames[MAX_BLOCKS]; +}; + +int32 registerUniform(const char *name); +int32 findUniform(const char *name); +int32 registerBlock(const char *name); +int32 findBlock(const char *name); + +extern UniformRegistry uniformRegistry; + +struct Shader +{ + GLuint program; + // same number of elements as UniformRegistry::numUniforms + GLint *uniformLocations; + + static Shader *create(const char **vsrc, const char **fsrc); +// static Shader *fromFiles(const char *vs, const char *fs); +// static Shader *fromStrings(const char *vsrc, const char *fsrc); + void use(void); + void destroy(void); +}; + +extern Shader *currentShader; + +} +} + +#endif diff --git a/src/gl/rwwdgl.h b/src/gl/rwwdgl.h new file mode 100644 index 0000000..b336ba3 --- /dev/null +++ b/src/gl/rwwdgl.h @@ -0,0 +1,88 @@ + +namespace rw { +namespace wdgl { + +// NOTE: This is not really RW OpenGL! It's specific to WarDrum's GTA ports + +void registerPlatformPlugins(void); + +struct AttribDesc +{ + // arguments to glVertexAttribPointer (should use OpenGL types here) + // Vertex = 0, TexCoord, Normal, Color, Weight, Bone Index, Extra Color + uint32 index; + // float = 0, byte, ubyte, short, ushort + int32 type; + bool32 normalized; + int32 size; + uint32 stride; + uint32 offset; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + int32 numAttribs; + AttribDesc *attribs; + uint32 dataSize; + uint8 *data; + + // needed for rendering + uint32 vbo; + uint32 ibo; +}; + +// only RW_OPENGL +void uploadGeo(Geometry *geo); +void setAttribPointers(InstanceDataHeader *inst); + +void packattrib(uint8 *dst, float32 *src, AttribDesc *a, float32 scale); +void unpackattrib(float *dst, uint8 *src, AttribDesc *a, float32 scale); + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +void printPipeinfo(Atomic *a); + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + uint32 numCustomAttribs; + uint32 (*instanceCB)(Geometry *g, int32 i, uint32 offset); + void (*uninstanceCB)(Geometry *g); +}; + +ObjPipeline *makeDefaultPipeline(void); + +// Skin plugin + +void initSkin(void); +Stream *readNativeSkin(Stream *stream, int32, void *object, int32 offset); +Stream *writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset); +int32 getSizeNativeSkin(void *object, int32 offset); + +ObjPipeline *makeSkinPipeline(void); + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Raster + +struct Texture : rw::Texture +{ + void upload(void); + void bind(int n); +}; + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); + +} +} diff --git a/src/gl/shaders/Makefile b/src/gl/shaders/Makefile new file mode 100644 index 0000000..2301a95 --- /dev/null +++ b/src/gl/shaders/Makefile @@ -0,0 +1,45 @@ +all: header_vs.inc header_fs.inc im2d_gl3.inc im3d_gl3.inc default_vs_gl3.inc simple_fs_gl3.inc matfx_gl3.inc skin_gl3.inc + +header_vs.inc: header.vert + (echo 'const char *header_vert_src =';\ + sed 's/..*/"&\\n"/' header.vert;\ + echo ';') >header_vs.inc + +header_fs.inc: header.frag + (echo 'const char *header_frag_src =';\ + sed 's/..*/"&\\n"/' header.frag;\ + echo ';') >header_fs.inc + +im2d_gl3.inc: im2d.vert + (echo 'const char *im2d_vert_src =';\ + sed 's/..*/"&\\n"/' im2d.vert;\ + echo ';') >im2d_gl3.inc + +im3d_gl3.inc: im3d.vert + (echo 'const char *im3d_vert_src =';\ + sed 's/..*/"&\\n"/' im3d.vert;\ + echo ';') >im3d_gl3.inc + +default_vs_gl3.inc: default.vert + (echo 'const char *default_vert_src =';\ + sed 's/..*/"&\\n"/' default.vert;\ + echo ';') >default_vs_gl3.inc + +simple_fs_gl3.inc: simple.frag + (echo 'const char *simple_frag_src =';\ + sed 's/..*/"&\\n"/' simple.frag;\ + echo ';') >simple_fs_gl3.inc + +matfx_gl3.inc: matfx_env.frag matfx_env.vert + (echo 'const char *matfx_env_vert_src =';\ + sed 's/..*/"&\\n"/' matfx_env.vert;\ + echo ';';\ + echo 'const char *matfx_env_frag_src =';\ + sed 's/..*/"&\\n"/' matfx_env.frag;\ + echo ';') >matfx_gl3.inc + +skin_gl3.inc: skin.vert + (echo 'const char *skin_vert_src =';\ + sed 's/..*/"&\\n"/' skin.vert;\ + echo ';') >skin_gl3.inc + diff --git a/src/gl/shaders/default.vert b/src/gl/shaders/default.vert new file mode 100644 index 0000000..edd3ac6 --- /dev/null +++ b/src/gl/shaders/default.vert @@ -0,0 +1,26 @@ +layout(location = 0) in vec3 in_pos; +layout(location = 1) in vec3 in_normal; +layout(location = 2) in vec4 in_color; +layout(location = 3) in vec2 in_tex0; + +out vec4 v_color; +out vec2 v_tex0; +out float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/gl/shaders/default_vs_gl3.inc b/src/gl/shaders/default_vs_gl3.inc new file mode 100644 index 0000000..098ec0a --- /dev/null +++ b/src/gl/shaders/default_vs_gl3.inc @@ -0,0 +1,28 @@ +const char *default_vert_src = +"layout(location = 0) in vec3 in_pos;\n" +"layout(location = 1) in vec3 in_normal;\n" +"layout(location = 2) in vec4 in_color;\n" +"layout(location = 3) in vec2 in_tex0;\n" + +"out vec4 v_color;\n" +"out vec2 v_tex0;\n" +"out float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 Vertex = u_world * vec4(in_pos, 1.0);\n" +" gl_Position = u_proj * u_view * Vertex;\n" +" vec3 Normal = mat3(u_world) * in_normal;\n" + +" v_tex0 = in_tex0;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; diff --git a/src/gl/shaders/header.frag b/src/gl/shaders/header.frag new file mode 100644 index 0000000..98b424b --- /dev/null +++ b/src/gl/shaders/header.frag @@ -0,0 +1,24 @@ +#ifdef USE_UBOS +layout(std140) uniform State +{ + vec2 u_alphaRef; + vec4 u_fogData; + vec4 u_fogColor; +}; +#else +uniform vec2 u_alphaRef; + +uniform vec4 u_fogData; +uniform vec4 u_fogColor; +#endif + +#define u_fogStart (u_fogData.x) +#define u_fogEnd (u_fogData.y) +#define u_fogRange (u_fogData.z) +#define u_fogDisable (u_fogData.w) + +void DoAlphaTest(float a) +{ + if(a < u_alphaRef.x || a >= u_alphaRef.y) + discard; +} diff --git a/src/gl/shaders/header.vert b/src/gl/shaders/header.vert new file mode 100644 index 0000000..61b711a --- /dev/null +++ b/src/gl/shaders/header.vert @@ -0,0 +1,111 @@ + +#define DIRECTIONALS +//#define POINTLIGHTS +//#define SPOTLIGHTS + +#ifdef USE_UBOS +layout(std140) uniform State +{ + vec2 u_alphaRef; + vec4 u_fogData; + vec4 u_fogColor; +}; +#else +uniform vec2 u_alphaRef; +uniform vec4 u_fogData; +uniform vec4 u_fogColor; +#endif + +#define u_fogStart (u_fogData.x) +#define u_fogEnd (u_fogData.y) +#define u_fogRange (u_fogData.z) +#define u_fogDisable (u_fogData.w) + +#ifdef USE_UBOS +layout(std140) uniform Scene +{ + mat4 u_proj; + mat4 u_view; +}; +#else +uniform mat4 u_proj; +uniform mat4 u_view; +#endif + +#define MAX_LIGHTS 8 + +#ifdef USE_UBOS +layout(std140) uniform Object +{ + mat4 u_world; + vec4 u_ambLight; + vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot + vec4 u_lightPosition[MAX_LIGHTS]; + vec4 u_lightDirection[MAX_LIGHTS]; + vec4 u_lightColor[MAX_LIGHTS]; +}; +#else +uniform mat4 u_world; +uniform vec4 u_ambLight; +uniform vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot +uniform vec4 u_lightPosition[MAX_LIGHTS]; +uniform vec4 u_lightDirection[MAX_LIGHTS]; +uniform vec4 u_lightColor[MAX_LIGHTS]; +#endif + +uniform vec4 u_matColor; +uniform vec4 u_surfProps; // amb, spec, diff, extra + +#define surfAmbient (u_surfProps.x) +#define surfSpecular (u_surfProps.y) +#define surfDiffuse (u_surfProps.z) + +vec3 DoDynamicLight(vec3 V, vec3 N) +{ + vec3 color = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < MAX_LIGHTS; i++){ + if(u_lightParams[i].x == 0.0) + break; +#ifdef DIRECTIONALS + if(u_lightParams[i].x == 1.0){ + // direct + float l = max(0.0, dot(N, -u_lightDirection[i].xyz)); + color += l*u_lightColor[i].rgb; + }else +#endif +#ifdef POINTLIGHTS + if(u_lightParams[i].x == 2.0){ + // point + vec3 dir = V - u_lightPosition[i].xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/u_lightParams[i].y)); + float l = max(0.0, dot(N, -normalize(dir))); + color += l*u_lightColor[i].rgb*atten; + }else +#endif +#ifdef SPOTLIGHTS + if(u_lightParams[i].x == 3.0){ + // spot + vec3 dir = V - u_lightPosition[i].xyz; + float dist = length(dir); + float atten = max(0.0, (1.0 - dist/u_lightParams[i].y)); + dir /= dist; + float l = max(0.0, dot(N, -dir)); + float pcos = dot(dir, u_lightDirection[i].xyz); // cos to point + float ccos = -u_lightParams[i].z; + float falloff = (pcos-ccos)/(1.0-ccos); + if(falloff < 0.0) // outside of cone + l = 0.0; + l *= max(falloff, u_lightParams[i].w); + return l*u_lightColor[i].rgb*atten; + }else +#endif + ; + } + return color; +} + +float DoFog(float w) +{ + return clamp((w - u_fogEnd)*u_fogRange, u_fogDisable, 1.0); +} diff --git a/src/gl/shaders/header_fs.inc b/src/gl/shaders/header_fs.inc new file mode 100644 index 0000000..fc106a7 --- /dev/null +++ b/src/gl/shaders/header_fs.inc @@ -0,0 +1,26 @@ +const char *header_frag_src = +"#ifdef USE_UBOS\n" +"layout(std140) uniform State\n" +"{\n" +" vec2 u_alphaRef;\n" +" vec4 u_fogData;\n" +" vec4 u_fogColor;\n" +"};\n" +"#else\n" +"uniform vec2 u_alphaRef;\n" + +"uniform vec4 u_fogData;\n" +"uniform vec4 u_fogColor;\n" +"#endif\n" + +"#define u_fogStart (u_fogData.x)\n" +"#define u_fogEnd (u_fogData.y)\n" +"#define u_fogRange (u_fogData.z)\n" +"#define u_fogDisable (u_fogData.w)\n" + +"void DoAlphaTest(float a)\n" +"{\n" +" if(a < u_alphaRef.x || a >= u_alphaRef.y)\n" +" discard;\n" +"}\n" +; diff --git a/src/gl/shaders/header_vs.inc b/src/gl/shaders/header_vs.inc new file mode 100644 index 0000000..a0ca752 --- /dev/null +++ b/src/gl/shaders/header_vs.inc @@ -0,0 +1,113 @@ +const char *header_vert_src = + +"#define DIRECTIONALS\n" +"//#define POINTLIGHTS\n" +"//#define SPOTLIGHTS\n" + +"#ifdef USE_UBOS\n" +"layout(std140) uniform State\n" +"{\n" +" vec2 u_alphaRef;\n" +" vec4 u_fogData;\n" +" vec4 u_fogColor;\n" +"};\n" +"#else\n" +"uniform vec2 u_alphaRef;\n" +"uniform vec4 u_fogData;\n" +"uniform vec4 u_fogColor;\n" +"#endif\n" + +"#define u_fogStart (u_fogData.x)\n" +"#define u_fogEnd (u_fogData.y)\n" +"#define u_fogRange (u_fogData.z)\n" +"#define u_fogDisable (u_fogData.w)\n" + +"#ifdef USE_UBOS\n" +"layout(std140) uniform Scene\n" +"{\n" +" mat4 u_proj;\n" +" mat4 u_view;\n" +"};\n" +"#else\n" +"uniform mat4 u_proj;\n" +"uniform mat4 u_view;\n" +"#endif\n" + +"#define MAX_LIGHTS 8\n" + +"#ifdef USE_UBOS\n" +"layout(std140) uniform Object\n" +"{\n" +" mat4 u_world;\n" +" vec4 u_ambLight;\n" +" vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot\n" +" vec4 u_lightPosition[MAX_LIGHTS];\n" +" vec4 u_lightDirection[MAX_LIGHTS];\n" +" vec4 u_lightColor[MAX_LIGHTS];\n" +"};\n" +"#else\n" +"uniform mat4 u_world;\n" +"uniform vec4 u_ambLight;\n" +"uniform vec4 u_lightParams[MAX_LIGHTS]; // type, radius, minusCosAngle, hardSpot\n" +"uniform vec4 u_lightPosition[MAX_LIGHTS];\n" +"uniform vec4 u_lightDirection[MAX_LIGHTS];\n" +"uniform vec4 u_lightColor[MAX_LIGHTS];\n" +"#endif\n" + +"uniform vec4 u_matColor;\n" +"uniform vec4 u_surfProps; // amb, spec, diff, extra\n" + +"#define surfAmbient (u_surfProps.x)\n" +"#define surfSpecular (u_surfProps.y)\n" +"#define surfDiffuse (u_surfProps.z)\n" + +"vec3 DoDynamicLight(vec3 V, vec3 N)\n" +"{\n" +" vec3 color = vec3(0.0, 0.0, 0.0);\n" +" for(int i = 0; i < MAX_LIGHTS; i++){\n" +" if(u_lightParams[i].x == 0.0)\n" +" break;\n" +"#ifdef DIRECTIONALS\n" +" if(u_lightParams[i].x == 1.0){\n" +" // direct\n" +" float l = max(0.0, dot(N, -u_lightDirection[i].xyz));\n" +" color += l*u_lightColor[i].rgb;\n" +" }else\n" +"#endif\n" +"#ifdef POINTLIGHTS\n" +" if(u_lightParams[i].x == 2.0){\n" +" // point\n" +" vec3 dir = V - u_lightPosition[i].xyz;\n" +" float dist = length(dir);\n" +" float atten = max(0.0, (1.0 - dist/u_lightParams[i].y));\n" +" float l = max(0.0, dot(N, -normalize(dir)));\n" +" color += l*u_lightColor[i].rgb*atten;\n" +" }else\n" +"#endif\n" +"#ifdef SPOTLIGHTS\n" +" if(u_lightParams[i].x == 3.0){\n" +" // spot\n" +" vec3 dir = V - u_lightPosition[i].xyz;\n" +" float dist = length(dir);\n" +" float atten = max(0.0, (1.0 - dist/u_lightParams[i].y));\n" +" dir /= dist;\n" +" float l = max(0.0, dot(N, -dir));\n" +" float pcos = dot(dir, u_lightDirection[i].xyz); // cos to point\n" +" float ccos = -u_lightParams[i].z;\n" +" float falloff = (pcos-ccos)/(1.0-ccos);\n" +" if(falloff < 0.0) // outside of cone\n" +" l = 0.0;\n" +" l *= max(falloff, u_lightParams[i].w);\n" +" return l*u_lightColor[i].rgb*atten;\n" +" }else\n" +"#endif\n" +" ;\n" +" }\n" +" return color;\n" +"}\n" + +"float DoFog(float w)\n" +"{\n" +" return clamp((w - u_fogEnd)*u_fogRange, u_fogDisable, 1.0);\n" +"}\n" +; diff --git a/src/gl/shaders/im2d.vert b/src/gl/shaders/im2d.vert new file mode 100644 index 0000000..241593b --- /dev/null +++ b/src/gl/shaders/im2d.vert @@ -0,0 +1,21 @@ +uniform vec4 u_xform; + +layout(location = 0) in vec4 in_pos; +layout(location = 2) in vec4 in_color; +layout(location = 3) in vec2 in_tex0; + +out vec4 v_color; +out vec2 v_tex0; +out float v_fog; + +void +main(void) +{ + gl_Position = in_pos; + gl_Position.w = 1.0; + gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw; + v_fog = DoFog(gl_Position.z); + gl_Position.xyz *= gl_Position.w; + v_color = in_color; + v_tex0 = in_tex0; +} diff --git a/src/gl/shaders/im2d_gl3.inc b/src/gl/shaders/im2d_gl3.inc new file mode 100644 index 0000000..68341b3 --- /dev/null +++ b/src/gl/shaders/im2d_gl3.inc @@ -0,0 +1,23 @@ +const char *im2d_vert_src = +"uniform vec4 u_xform;\n" + +"layout(location = 0) in vec4 in_pos;\n" +"layout(location = 2) in vec4 in_color;\n" +"layout(location = 3) in vec2 in_tex0;\n" + +"out vec4 v_color;\n" +"out vec2 v_tex0;\n" +"out float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" gl_Position = in_pos;\n" +" gl_Position.w = 1.0;\n" +" gl_Position.xy = gl_Position.xy * u_xform.xy + u_xform.zw;\n" +" v_fog = DoFog(gl_Position.z);\n" +" gl_Position.xyz *= gl_Position.w;\n" +" v_color = in_color;\n" +" v_tex0 = in_tex0;\n" +"}\n" +; diff --git a/src/gl/shaders/im3d.vert b/src/gl/shaders/im3d.vert new file mode 100644 index 0000000..162d4f9 --- /dev/null +++ b/src/gl/shaders/im3d.vert @@ -0,0 +1,18 @@ +layout(location = 0) in vec3 in_pos; +layout(location = 2) in vec4 in_color; +layout(location = 3) in vec2 in_tex0; + +out vec4 v_color; +out vec2 v_tex0; +out float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + vec4 CamVertex = u_view * Vertex; + gl_Position = u_proj * CamVertex; + v_color = in_color; + v_tex0 = in_tex0; + v_fog = DoFog(gl_Position.w); +} diff --git a/src/gl/shaders/im3d_gl3.inc b/src/gl/shaders/im3d_gl3.inc new file mode 100644 index 0000000..789cb2f --- /dev/null +++ b/src/gl/shaders/im3d_gl3.inc @@ -0,0 +1,20 @@ +const char *im3d_vert_src = +"layout(location = 0) in vec3 in_pos;\n" +"layout(location = 2) in vec4 in_color;\n" +"layout(location = 3) in vec2 in_tex0;\n" + +"out vec4 v_color;\n" +"out vec2 v_tex0;\n" +"out float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 Vertex = u_world * vec4(in_pos, 1.0);\n" +" vec4 CamVertex = u_view * Vertex;\n" +" gl_Position = u_proj * CamVertex;\n" +" v_color = in_color;\n" +" v_tex0 = in_tex0;\n" +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; diff --git a/src/gl/shaders/matfx_env.frag b/src/gl/shaders/matfx_env.frag new file mode 100644 index 0000000..0d11871 --- /dev/null +++ b/src/gl/shaders/matfx_env.frag @@ -0,0 +1,34 @@ +uniform sampler2D tex0; +uniform sampler2D tex1; + +uniform vec2 u_fxparams; +uniform vec4 u_colorClamp; + +#define shininess (u_fxparams.x) +#define disableFBA (u_fxparams.y) + +in vec4 v_color; +in vec2 v_tex0; +in vec2 v_tex1; +in float v_fog; + +out vec4 color; + +void +main(void) +{ + vec4 pass1 = v_color; + vec4 envColor = max(pass1, u_colorClamp); + pass1 *= texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + + vec4 pass2 = envColor*shininess*texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y)); + + pass1.rgb = mix(u_fogColor.rgb, pass1.rgb, v_fog); + pass2.rgb = mix(vec3(0.0, 0.0, 0.0), pass2.rgb, v_fog); + + float fba = max(pass1.a, disableFBA); + color.rgb = pass1.rgb*pass1.a + pass2.rgb*fba; + color.a = pass1.a; + + DoAlphaTest(color.a); +} diff --git a/src/gl/shaders/matfx_env.vert b/src/gl/shaders/matfx_env.vert new file mode 100644 index 0000000..b19ae0f --- /dev/null +++ b/src/gl/shaders/matfx_env.vert @@ -0,0 +1,30 @@ +uniform mat4 u_texMatrix; + +layout(location = 0) in vec3 in_pos; +layout(location = 1) in vec3 in_normal; +layout(location = 2) in vec4 in_color; +layout(location = 3) in vec2 in_tex0; + +out vec4 v_color; +out vec2 v_tex0; +out vec2 v_tex1; +out float v_fog; + +void +main(void) +{ + vec4 Vertex = u_world * vec4(in_pos, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * in_normal; + + v_tex0 = in_tex0; + v_tex1 = (u_texMatrix * vec4(Normal, 1.0)).xy; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.w); +} diff --git a/src/gl/shaders/matfx_gl3.inc b/src/gl/shaders/matfx_gl3.inc new file mode 100644 index 0000000..664db3a --- /dev/null +++ b/src/gl/shaders/matfx_gl3.inc @@ -0,0 +1,68 @@ +const char *matfx_env_vert_src = +"uniform mat4 u_texMatrix;\n" + +"layout(location = 0) in vec3 in_pos;\n" +"layout(location = 1) in vec3 in_normal;\n" +"layout(location = 2) in vec4 in_color;\n" +"layout(location = 3) in vec2 in_tex0;\n" + +"out vec4 v_color;\n" +"out vec2 v_tex0;\n" +"out vec2 v_tex1;\n" +"out float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 Vertex = u_world * vec4(in_pos, 1.0);\n" +" gl_Position = u_proj * u_view * Vertex;\n" +" vec3 Normal = mat3(u_world) * in_normal;\n" + +" v_tex0 = in_tex0;\n" +" v_tex1 = (u_texMatrix * vec4(Normal, 1.0)).xy;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.w);\n" +"}\n" +; +const char *matfx_env_frag_src = +"uniform sampler2D tex0;\n" +"uniform sampler2D tex1;\n" + +"uniform vec2 u_fxparams;\n" +"uniform vec4 u_colorClamp;\n" + +"#define shininess (u_fxparams.x)\n" +"#define disableFBA (u_fxparams.y)\n" + +"in vec4 v_color;\n" +"in vec2 v_tex0;\n" +"in vec2 v_tex1;\n" +"in float v_fog;\n" + +"out vec4 color;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec4 pass1 = v_color;\n" +" vec4 envColor = max(pass1, u_colorClamp);\n" +" pass1 *= texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y));\n" + +" vec4 pass2 = envColor*shininess*texture(tex1, vec2(v_tex1.x, 1.0-v_tex1.y));\n" + +" pass1.rgb = mix(u_fogColor.rgb, pass1.rgb, v_fog);\n" +" pass2.rgb = mix(vec3(0.0, 0.0, 0.0), pass2.rgb, v_fog);\n" + +" float fba = max(pass1.a, disableFBA);\n" +" color.rgb = pass1.rgb*pass1.a + pass2.rgb*fba;\n" +" color.a = pass1.a;\n" + +" DoAlphaTest(color.a);\n" +"}\n" +; diff --git a/src/gl/shaders/simple.frag b/src/gl/shaders/simple.frag new file mode 100644 index 0000000..87157be --- /dev/null +++ b/src/gl/shaders/simple.frag @@ -0,0 +1,16 @@ +uniform sampler2D tex0; + +in vec4 v_color; +in vec2 v_tex0; +in float v_fog; + +out vec4 color; + +void +main(void) +{ + color = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y)); + color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog); + DoAlphaTest(color.a); +} + diff --git a/src/gl/shaders/simple_fs_gl3.inc b/src/gl/shaders/simple_fs_gl3.inc new file mode 100644 index 0000000..47d8997 --- /dev/null +++ b/src/gl/shaders/simple_fs_gl3.inc @@ -0,0 +1,18 @@ +const char *simple_frag_src = +"uniform sampler2D tex0;\n" + +"in vec4 v_color;\n" +"in vec2 v_tex0;\n" +"in float v_fog;\n" + +"out vec4 color;\n" + +"void\n" +"main(void)\n" +"{\n" +" color = v_color*texture(tex0, vec2(v_tex0.x, 1.0-v_tex0.y));\n" +" color.rgb = mix(u_fogColor.rgb, color.rgb, v_fog);\n" +" DoAlphaTest(color.a);\n" +"}\n" + +; diff --git a/src/gl/shaders/skin.vert b/src/gl/shaders/skin.vert new file mode 100644 index 0000000..ff198b1 --- /dev/null +++ b/src/gl/shaders/skin.vert @@ -0,0 +1,37 @@ +uniform mat4 u_boneMatrices[64]; + +layout(location = 0) in vec3 in_pos; +layout(location = 1) in vec3 in_normal; +layout(location = 2) in vec4 in_color; +layout(location = 3) in vec2 in_tex0; +layout(location = 11) in vec4 in_weights; +layout(location = 12) in vec4 in_indices; + +out vec4 v_color; +out vec2 v_tex0; +out float v_fog; + +void +main(void) +{ + vec3 SkinVertex = vec3(0.0, 0.0, 0.0); + vec3 SkinNormal = vec3(0.0, 0.0, 0.0); + for(int i = 0; i < 4; i++){ + SkinVertex += (u_boneMatrices[int(in_indices[i])] * vec4(in_pos, 1.0)).xyz * in_weights[i]; + SkinNormal += (mat3(u_boneMatrices[int(in_indices[i])]) * in_normal) * in_weights[i]; + } + + vec4 Vertex = u_world * vec4(SkinVertex, 1.0); + gl_Position = u_proj * u_view * Vertex; + vec3 Normal = mat3(u_world) * SkinNormal; + + v_tex0 = in_tex0; + + v_color = in_color; + v_color.rgb += u_ambLight.rgb*surfAmbient; + v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse; + v_color = clamp(v_color, 0.0, 1.0); + v_color *= u_matColor; + + v_fog = DoFog(gl_Position.z); +} diff --git a/src/gl/shaders/skin_gl3.inc b/src/gl/shaders/skin_gl3.inc new file mode 100644 index 0000000..6a6fd44 --- /dev/null +++ b/src/gl/shaders/skin_gl3.inc @@ -0,0 +1,39 @@ +const char *skin_vert_src = +"uniform mat4 u_boneMatrices[64];\n" + +"layout(location = 0) in vec3 in_pos;\n" +"layout(location = 1) in vec3 in_normal;\n" +"layout(location = 2) in vec4 in_color;\n" +"layout(location = 3) in vec2 in_tex0;\n" +"layout(location = 11) in vec4 in_weights;\n" +"layout(location = 12) in vec4 in_indices;\n" + +"out vec4 v_color;\n" +"out vec2 v_tex0;\n" +"out float v_fog;\n" + +"void\n" +"main(void)\n" +"{\n" +" vec3 SkinVertex = vec3(0.0, 0.0, 0.0);\n" +" vec3 SkinNormal = vec3(0.0, 0.0, 0.0);\n" +" for(int i = 0; i < 4; i++){\n" +" SkinVertex += (u_boneMatrices[int(in_indices[i])] * vec4(in_pos, 1.0)).xyz * in_weights[i];\n" +" SkinNormal += (mat3(u_boneMatrices[int(in_indices[i])]) * in_normal) * in_weights[i];\n" +" }\n" + +" vec4 Vertex = u_world * vec4(SkinVertex, 1.0);\n" +" gl_Position = u_proj * u_view * Vertex;\n" +" vec3 Normal = mat3(u_world) * SkinNormal;\n" + +" v_tex0 = in_tex0;\n" + +" v_color = in_color;\n" +" v_color.rgb += u_ambLight.rgb*surfAmbient;\n" +" v_color.rgb += DoDynamicLight(Vertex.xyz, Normal)*surfDiffuse;\n" +" v_color = clamp(v_color, 0.0, 1.0);\n" +" v_color *= u_matColor;\n" + +" v_fog = DoFog(gl_Position.z);\n" +"}\n" +; diff --git a/src/gl/wdgl.cpp b/src/gl/wdgl.cpp new file mode 100644 index 0000000..f396c27 --- /dev/null +++ b/src/gl/wdgl.cpp @@ -0,0 +1,879 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwwdgl.h" + +#ifdef RW_OPENGL +#ifndef LIBRW_GLAD +#include +#else +#include +#endif +#endif + +#define PLUGIN_ID 2 + +namespace rw { +namespace wdgl { + +static void* +driverOpen(void *o, int32, int32) +{ + engine->driver[PLATFORM_WDGL]->defaultPipeline = makeDefaultPipeline(); + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_WDGL, 0, PLATFORM_WDGL, + driverOpen, driverClose); +} + + +// VC +// 8733 0 0 0 3 +// 45 1 0 0 2 +// 8657 1 3 0 2 +// 4610 2 1 1 3 +// 4185 3 2 1 4 +// 256 4 2 1 4 +// 201 4 4 1 4 +// 457 5 2 0 4 + +// SA +// 20303 0 0 0 3 vertices: 3 floats +// 53 1 0 0 2 texCoords: 2 floats +// 20043 1 3 0 2 texCoords: 2 shorts +// 6954 2 1 1 3 normal: 3 bytes normalized +// 13527 3 2 1 4 color: 4 ubytes normalized +// 196 4 2 1 4 weight: 4 ubytes normalized +// 225 4 4 1 4 weight: 4 ushorts normalized +// 421 5 2 0 4 indices: 4 ubytes +// 12887 6 2 1 4 extracolor:4 ubytes normalized + +/* +static void +printAttribInfo(AttribDesc *attribs, int n) +{ + for(int i = 0; i < n; i++) + printf("%x %x %x %x\n", + attribs[i].index, + attribs[i].type, + attribs[i].normalized, + attribs[i].size); +} +*/ + +#ifdef RW_OPENGL +void +uploadGeo(Geometry *geo) +{ + InstanceDataHeader *inst = (InstanceDataHeader*)geo->instData; + MeshHeader *meshHeader = geo->meshHeader; + + glGenBuffers(1, &inst->vbo); + glBindBuffer(GL_ARRAY_BUFFER, inst->vbo); + glBufferData(GL_ARRAY_BUFFER, inst->dataSize, + inst->data, GL_STATIC_DRAW); + + glGenBuffers(1, &inst->ibo); + glBindBuffer(GL_ARRAY_BUFFER, inst->ibo); + glBufferData(GL_ARRAY_BUFFER, meshHeader->totalIndices*2, + 0, GL_STATIC_DRAW); + GLintptr offset = 0; + for(uint32 i = 0; i < meshHeader->numMeshes; i++){ + Mesh *mesh = &meshHeader->getMeshes()[i]; + glBufferSubData(GL_ARRAY_BUFFER, offset, mesh->numIndices*2, + mesh->indices); + offset += mesh->numIndices*2; + } + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void +setAttribPointers(InstanceDataHeader *inst) +{ + static GLenum attribType[] = { + GL_FLOAT, + GL_BYTE, GL_UNSIGNED_BYTE, + GL_SHORT, GL_UNSIGNED_SHORT + }; + for(int32 i = 0; i < inst->numAttribs; i++){ + AttribDesc *a = &inst->attribs[i]; + glEnableVertexAttribArray(a->index); + glVertexAttribPointer(a->index, a->size, attribType[a->type], + a->normalized, a->stride, + (void*)(uint64)a->offset); + } +} +#endif + +void +packattrib(uint8 *dst, float32 *src, AttribDesc *a, float32 scale=1.0f) +{ + int8 *i8dst; + uint16 *u16dst; + int16 *i16dst; + + switch(a->type){ + case 0: // float + memcpy(dst, src, a->size*4); + break; + + // TODO: maybe have loop inside if? + case 1: // byte + i8dst = (int8*)dst; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + i8dst[i] = src[i]*scale; + else + i8dst[i] = src[i]*127.0f; + } + break; + + case 2: // ubyte + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = src[i]*scale; + else + dst[i] = src[i]*255.0f; + } + break; + + case 3: // short + i16dst = (int16*)dst; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + i16dst[i] = src[i]*scale; + else + i16dst[i] = src[i]*32767.0f; + } + break; + + case 4: // ushort + u16dst = (uint16*)dst; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + u16dst[i] = src[i]*scale; + else + u16dst[i] = src[i]*65535.0f; + } + break; + } +} + +void +unpackattrib(float *dst, uint8 *src, AttribDesc *a, float32 scale=1.0f) +{ + int8 *i8src; + uint16 *u16src; + int16 *i16src; + + switch(a->type){ + case 0: // float + memcpy(dst, src, a->size*4); + break; + + // TODO: maybe have loop inside if? + case 1: // byte + i8src = (int8*)src; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = i8src[i]/scale; + else + dst[i] = i8src[i]/127.0f; + } + break; + + case 2: // ubyte + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = src[i]/scale; + else + dst[i] = src[i]/255.0f; + } + break; + + case 3: // short + i16src = (int16*)src; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = i16src[i]/scale; + else + dst[i] = i16src[i]/32767.0f; + } + break; + + case 4: // ushort + u16src = (uint16*)src; + for(int i = 0; i < a->size; i++){ + if(!a->normalized) + dst[i] = u16src[i]/scale; + else + dst[i] = u16src[i]/65435.0f; + } + break; + } +} + +void* +destroyNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_WDGL) + return object; + InstanceDataHeader *header = + (InstanceDataHeader*)geometry->instData; + geometry->instData = nil; + // TODO: delete ibo and vbo + rwFree(header->attribs); + rwFree(header->data); + rwFree(header); + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_WDGL; + header->vbo = 0; + header->ibo = 0; + header->numAttribs = stream->readU32(); + header->attribs = rwNewT(AttribDesc, header->numAttribs, MEMDUR_EVENT | ID_GEOMETRY); + stream->read32(header->attribs, + header->numAttribs*sizeof(AttribDesc)); + header->dataSize = header->attribs[0].stride*geometry->numVertices; + header->data = rwNewT(uint8, header->dataSize, MEMDUR_EVENT | ID_GEOMETRY); + ASSERTLITTLE; + stream->read8(header->data, header->dataSize); + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32, void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_WDGL) + return stream; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + stream->writeU32(header->numAttribs); + stream->write32(header->attribs, header->numAttribs*sizeof(AttribDesc)); + ASSERTLITTLE; + stream->write8(header->data, header->dataSize); + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_WDGL) + return 0; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + return 4 + header->numAttribs*sizeof(AttribDesc) + header->dataSize; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +void +printPipeinfo(Atomic *a) +{ + Geometry *g = a->geometry; + if(g->instData == nil || g->instData->platform != PLATFORM_WDGL) + return; + int32 plgid = 0; + if(a->pipeline) + plgid = a->pipeline->pluginID; + printf("%s %x: ", debugFile, plgid); + InstanceDataHeader *h = (InstanceDataHeader*)g->instData; + for(int i = 0; i < h->numAttribs; i++) + printf("%x(%x) ", h->attribs[i].index, h->attribs[i].type); + printf("\n"); +} + +static void +instance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE (or not, wdgl can't render) + if(geo->instData) + return; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geo->instData = header; + header->platform = PLATFORM_WDGL; + header->vbo = 0; + header->ibo = 0; + header->numAttribs = + pipe->numCustomAttribs + 1 + (geo->numTexCoordSets > 0); + if(geo->flags & Geometry::PRELIT) + header->numAttribs++; + if(geo->flags & Geometry::NORMALS) + header->numAttribs++; + int32 offset = 0; + header->attribs = rwNewT(AttribDesc, header->numAttribs, MEMDUR_EVENT | ID_GEOMETRY); + + AttribDesc *a = header->attribs; + // Vertices + a->index = 0; + a->type = 0; + a->normalized = 0; + a->size = 3; + a->offset = offset; + offset += 12; + a++; + int32 firstCustom = 1; + + // texCoords, only one set here + if(geo->numTexCoordSets){ + a->index = 1; + a->type = 3; + a->normalized = 0; + a->size = 2; + a->offset = offset; + offset += 4; + a++; + firstCustom++; + } + + if(geo->flags & Geometry::NORMALS){ + a->index = 2; + a->type = 1; + a->normalized = 1; + a->size = 3; + a->offset = offset; + offset += 4; + a++; + firstCustom++; + } + + if(geo->flags & Geometry::PRELIT){ + a->index = 3; + a->type = 2; + a->normalized = 1; + a->size = 4; + a->offset = offset; + offset += 4; + a++; + firstCustom++; + } + + if(pipe->instanceCB) + offset += pipe->instanceCB(geo, firstCustom, offset); + else{ + header->dataSize = offset*geo->numVertices; + header->data = rwNewT(uint8, header->dataSize, MEMDUR_EVENT | ID_GEOMETRY); + } + + a = header->attribs; + for(int32 i = 0; i < header->numAttribs; i++) + a[i].stride = offset; + + uint8 *p = header->data + a->offset; + V3d *vert = geo->morphTargets->vertices; + for(int32 i = 0; i < geo->numVertices; i++){ + packattrib(p, (float32*)vert, a); + vert++; + p += a->stride; + } + a++; + + if(geo->numTexCoordSets){ + p = header->data + a->offset; + TexCoords *texcoord = geo->texCoords[0]; + for(int32 i = 0; i < geo->numVertices; i++){ + packattrib(p, (float32*)texcoord, a, 512.0f); + texcoord++; + p += a->stride; + } + a++; + } + + if(geo->flags & Geometry::NORMALS){ + p = header->data + a->offset; + V3d *norm = geo->morphTargets->normals; + for(int32 i = 0; i < geo->numVertices; i++){ + packattrib(p, (float32*)norm, a); + norm++; + p += a->stride; + } + a++; + } + + if(geo->flags & Geometry::PRELIT){ + // TODO: this seems too complicated + p = header->data + a->offset; + RGBA *color = geo->colors; + float32 f[4]; + for(int32 i = 0; i < geo->numVertices; i++){ + f[0] = color->red/255.0f; + f[1] = color->green/255.0f; + f[2] = color->blue/255.0f; + f[3] = color->alpha/255.0f; + packattrib(p, (float32*)f, a); + color++; + p += a->stride; + } + a++; + } +} + +static void +uninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_WDGL); + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + + uint8 *p; + TexCoords *texcoord = geo->texCoords[0]; + RGBA *color = geo->colors; + V3d *vert = geo->morphTargets->vertices; + V3d *norm = geo->morphTargets->normals; + float32 f[4]; + + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + for(int i = 0; i < header->numAttribs; i++){ + AttribDesc *a = &header->attribs[i]; + p = header->data + a->offset; + + switch(a->index){ + case 0: // Vertices + for(int32 i = 0; i < geo->numVertices; i++){ + unpackattrib((float32*)vert, p, a); + vert++; + p += a->stride; + } + break; + + case 1: // texCoords + for(int32 i = 0; i < geo->numVertices; i++){ + unpackattrib((float32*)texcoord, p, a, 512.0f); + texcoord++; + p += a->stride; + } + break; + + case 2: // normals + for(int32 i = 0; i < geo->numVertices; i++){ + unpackattrib((float32*)norm, p, a); + norm++; + p += a->stride; + } + break; + + case 3: // colors + for(int32 i = 0; i < geo->numVertices; i++){ + // TODO: this seems too complicated + unpackattrib(f, p, a); + color->red = f[0]*255.0f; + color->green = f[1]*255.0f; + color->blue = f[2]*255.0f; + color->alpha = f[3]*255.0f; + color++; + p += a->stride; + } + break; + } + } + + if(pipe->uninstanceCB) + pipe->uninstanceCB(geo); + + geo->generateTriangles(); + + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_GL3); + this->numCustomAttribs = 0; + this->impl.instance = wdgl::instance; + this->impl.uninstance = wdgl::uninstance; + this->instanceCB = nil; + this->uninstanceCB = nil; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +ObjPipeline* +makeDefaultPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + return pipe; +} + +// Skin + +Stream* +readNativeSkin(Stream *stream, int32, void *object, int32 offset) +{ + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_GL){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, offset) = skin; + + int32 numBones = stream->readI32(); + skin->init(numBones, 0, 0); + stream->read32(skin->inverseMatrices, skin->numBones*64); + return stream; +} + +Stream* +writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset) +{ + writeChunkHeader(stream, ID_STRUCT, len-12); + stream->writeU32(PLATFORM_GL); + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + stream->writeI32(skin->numBones); + stream->write32(skin->inverseMatrices, skin->numBones*64); + return stream; +} + +int32 +getSizeNativeSkin(void *object, int32 offset) +{ + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin == nil) + return -1; + int32 size = 12 + 4 + 4 + skin->numBones*64; + return size; +} + +uint32 +skinInstanceCB(Geometry *g, int32 i, uint32 offset) +{ + InstanceDataHeader *header = (InstanceDataHeader*)g->instData; + AttribDesc *a = &header->attribs[i]; + // weights + a->index = 4; + a->type = 2; /* but also short o_O */ + a->normalized = 1; + a->size = 4; + a->offset = offset; + offset += 4; + a++; + + // indices + a->index = 5; + a->type = 2; + a->normalized = 0; + a->size = 4; + a->offset = offset; + offset += 4; + + header->dataSize = offset*g->numVertices; + header->data = rwNewT(uint8, header->dataSize, MEMDUR_EVENT | ID_GEOMETRY); + + Skin *skin = Skin::get(g); + if(skin == nil) + return 8; + + a = &header->attribs[i]; + uint8 *wgt = header->data + a[0].offset; + uint8 *idx = header->data + a[1].offset; + uint8 *indices = skin->indices; + float32 *weights = skin->weights; + for(int32 i = 0; i < g->numVertices; i++){ + packattrib(wgt, weights, a); + weights += 4; + wgt += offset; + idx[0] = *indices++; + idx[1] = *indices++; + idx[2] = *indices++; + idx[3] = *indices++; + idx += offset; + } + + return 8; +} + +void +skinUninstanceCB(Geometry *geo) +{ + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + + Skin *skin = Skin::get(geo); + if(skin == nil) + return; + + uint8 *data = skin->data; + float *invMats = skin->inverseMatrices; + skin->init(skin->numBones, skin->numBones, geo->numVertices); + memcpy(skin->inverseMatrices, invMats, skin->numBones*64); + rwFree(data); + + uint8 *p; + float *weights = skin->weights; + uint8 *indices = skin->indices; + for(int i = 0; i < header->numAttribs; i++){ + AttribDesc *a = &header->attribs[i]; + p = header->data + a->offset; + + switch(a->index){ + case 4: // weights + for(int32 i = 0; i < geo->numVertices; i++){ + unpackattrib(weights, p, a); +float sum = weights[0] + weights[1] + weights[2] + weights[3]; +if(sum){ + weights[0] /= sum; + weights[1] /= sum; + weights[2] /= sum; + weights[3] /= sum; +} + weights += 4; + p += a->stride; + } + break; + + case 5: // indices + for(int32 i = 0; i < geo->numVertices; i++){ + *indices++ = p[0]; + *indices++ = p[1]; + *indices++ = p[2]; + *indices++ = p[3]; + p += a->stride; + } + break; + } + } + + skin->findNumWeights(geo->numVertices); + skin->findUsedBones(geo->numVertices); +} + +// Skin + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_WDGL] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_WDGL])->destroy(); + skinGlobals.pipelines[PLATFORM_WDGL] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_WDGL, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + pipe->numCustomAttribs = 2; + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = skinUninstanceCB; + return pipe; +} + +// MatFX + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_WDGL] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_WDGL])->destroy(); + matFXGlobals.pipelines[PLATFORM_WDGL] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_WDGL, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + ObjPipeline *pipe = ObjPipeline::create(); + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + return pipe; +} + +// Raster + +int32 nativeRasterOffset; + +#ifdef RW_OPENGL +struct GlRaster { + GLuint id; +}; + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + GlRaster *raster = PLUGINOFFSET(GlRaster, object, offset); + raster->id = 0; + return object; +} + +static void* +destroyNativeRaster(void *object, int32 offset, int32) +{ + // TODO: + return object; +} + +static void* +copyNativeRaster(void *dst, void *, int32 offset, int32) +{ + GlRaster *raster = PLUGINOFFSET(GlRaster, dst, offset); + raster->id = 0; + return dst; +} + +void +registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(GlRaster), + ID_RASTERWDGL, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); +} + +void +Texture::upload(void) +{ + GLuint id; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + Raster *r = this->raster; + if(r->palette){ + printf("can't upload paletted raster\n"); + return; + } + + static GLenum filter[] = { + 0, GL_NEAREST, GL_LINEAR, + GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, + GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR + }; + static GLenum filternomip[] = { + 0, GL_NEAREST, GL_LINEAR, + GL_NEAREST, GL_LINEAR, + GL_NEAREST, GL_LINEAR + }; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + filternomip[this->filterAddressing & 0xFF]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + filternomip[this->filterAddressing & 0xFF]); + + static GLenum wrap[] = { + 0, GL_REPEAT, GL_MIRRORED_REPEAT, + GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER + }; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, + wrap[(this->filterAddressing >> 8) & 0xF]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, + wrap[(this->filterAddressing >> 12) & 0xF]); + + switch(r->format & 0xF00){ + case Raster::C8888: + glTexImage2D(GL_TEXTURE_2D, 0, 4, r->width, r->height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, r->pixels); + break; + default: + printf("unsupported raster format: %x\n", r->format); + break; + } + glBindTexture(GL_TEXTURE_2D, 0); + GlRaster *glr = PLUGINOFFSET(GlRaster, r, nativeRasterOffset); + glr->id = id; +} + +void +Texture::bind(int n) +{ + Raster *r = this->raster; + GlRaster *glr = PLUGINOFFSET(GlRaster, r, nativeRasterOffset); + glActiveTexture(GL_TEXTURE0+n); + if(r){ + if(glr->id == 0) + this->upload(); + glBindTexture(GL_TEXTURE_2D, glr->id); + }else + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + +} +#endif + +} +} diff --git a/src/hanim.cpp b/src/hanim.cpp new file mode 100644 index 0000000..3f1ecb6 --- /dev/null +++ b/src/hanim.cpp @@ -0,0 +1,413 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" +#include "ps2/rwps2.h" +#include "ps2/rwps2plg.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwwdgl.h" +#include "gl/rwgl3.h" + +#define PLUGIN_ID ID_HANIM + +namespace rw { + +int32 hAnimOffset; +bool32 hAnimDoStream = 1; + +HAnimHierarchy* +HAnimHierarchy::create(int32 numNodes, int32 *nodeFlags, int32 *nodeIDs, + int32 flags, int32 maxKeySize) +{ + HAnimHierarchy *hier = (HAnimHierarchy*)rwMalloc(sizeof(*hier), MEMDUR_EVENT | ID_HANIM); + if(hier == nil){ + RWERROR((ERR_ALLOC, sizeof(*hier))); + return nil; + } + hier->interpolator = AnimInterpolator::create(numNodes, maxKeySize); + + hier->numNodes = numNodes; + hier->flags = flags; + hier->parentFrame = nil; + hier->parentHierarchy = hier; + if(hier->flags & NOMATRICES){ + hier->matrices = nil; + hier->matricesUnaligned = nil; + }else{ + hier->matricesUnaligned = rwNew(hier->numNodes*64 + 0xF, MEMDUR_EVENT | ID_HANIM); + hier->matrices = + (Matrix*)(((uintptr)hier->matricesUnaligned + 0xF) & ~0xF); + } + hier->nodeInfo = rwNewT(HAnimNodeInfo, hier->numNodes, MEMDUR_EVENT | ID_HANIM); + for(int32 i = 0; i < hier->numNodes; i++){ + if(nodeIDs) + hier->nodeInfo[i].id = nodeIDs[i]; + else + hier->nodeInfo[i].id = 0; + hier->nodeInfo[i].index = i; + if(nodeFlags) + hier->nodeInfo[i].flags = nodeFlags[i]; + else + hier->nodeInfo[i].flags = 0; + hier->nodeInfo[i].frame = nil; + } + return hier; +} + +void +HAnimHierarchy::destroy(void) +{ + this->interpolator->destroy(); + rwFree(this->matricesUnaligned); + rwFree(this->nodeInfo); + rwFree(this); +} + +static Frame* +findById(Frame *f, int32 id) +{ + if(f == nil) return nil; + HAnimData *hanim = HAnimData::get(f); + if(hanim->id >= 0 && hanim->id == id) return f; + Frame *ff = findById(f->next, id); + if(ff) return ff; + return findById(f->child, id); +} + +static Frame* +findUnattachedById(HAnimHierarchy *hier, Frame *f, int32 id) +{ + if(f == nil) return nil; + HAnimData *hanim = HAnimData::get(f); + if(hanim->id >= 0 && hanim->id == id && hier->getIndex(f) == -1) return f; + Frame *ff = findUnattachedById(hier, f->next, id); + if(ff) return ff; + return findUnattachedById(hier, f->child, id); +} + +void +HAnimHierarchy::attachByIndex(int32 idx) +{ + int32 id = this->nodeInfo[idx].id; +// Frame *f = findById(this->parentFrame, id); + Frame *f = findUnattachedById(this, this->parentFrame, id); + if(f) + this->nodeInfo[idx].frame = f; +} + +void +HAnimHierarchy::attach(void) +{ + for(int32 i = 0; i < this->numNodes; i++) + this->attachByIndex(i); +} + +int32 +HAnimHierarchy::getIndex(int32 id) +{ + for(int32 i = 0; i < this->numNodes; i++) + if(this->nodeInfo[i].id == id) + return i; + return -1; +} + +int32 +HAnimHierarchy::getIndex(Frame *f) +{ + for(int32 i = 0; i < this->numNodes; i++) + if(this->nodeInfo[i].frame == f) + return i; + return -1; +} + +HAnimHierarchy* +HAnimHierarchy::get(Frame *f) +{ + return HAnimData::get(f)->hierarchy; +} + +HAnimHierarchy* +HAnimHierarchy::find(Frame *f) +{ + if(f == nil) return nil; + HAnimHierarchy *hier = HAnimHierarchy::get(f); + if(hier) return hier; + hier = HAnimHierarchy::find(f->next); + if(hier) return hier; + return HAnimHierarchy::find(f->child); +} + +void +HAnimHierarchy::updateMatrices(void) +{ + // TODO: handle more (all!) cases + + Matrix rootMat, animMat; + Matrix *curMat, *parentMat; + Matrix **sp, *stack[64]; + Frame *frm, *parfrm; + int32 i; + AnimInterpolator *anim = this->interpolator; + + sp = stack; + curMat = this->matrices; + + frm = this->parentFrame; + if(frm && (parfrm = frm->getParent()) && !(this->flags&LOCALSPACEMATRICES)) + rootMat = *parfrm->getLTM(); + else + rootMat.setIdentity(); + parentMat = &rootMat; + *sp++ = parentMat; + HAnimNodeInfo *node = this->nodeInfo; + for(i = 0; i < this->numNodes; i++){ + anim->applyCB(&animMat, anim->getInterpFrame(i)); + + // TODO: here we could update local matrices + + Matrix::mult(curMat, &animMat, parentMat); + + // TODO: here we could update LTM + + if(node->flags & PUSH) + *sp++ = parentMat; + parentMat = curMat; + if(node->flags & POP) + parentMat = *--sp; + assert(sp >= stack); + assert(sp <= &stack[64]); + + node++; + curMat++; + } +} + +HAnimData* +HAnimData::get(Frame *f) +{ + return PLUGINOFFSET(HAnimData, f, hAnimOffset); +} + +static void* +createHAnim(void *object, int32 offset, int32) +{ + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + hanim->id = -1; + hanim->hierarchy = nil; + return object; +} + +static void* +destroyHAnim(void *object, int32 offset, int32) +{ + int i; + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + if(hanim->hierarchy){ + for(i = 0; i < hanim->hierarchy->numNodes; i++) + hanim->hierarchy->nodeInfo[i].frame = nil; + if(object == hanim->hierarchy->parentFrame) + hanim->hierarchy->destroy(); + } + hanim->id = -1; + hanim->hierarchy = nil; + return object; +} + +static void* +copyHAnim(void *dst, void *src, int32 offset, int32) +{ + int i; + HAnimData *dsthanim = PLUGINOFFSET(HAnimData, dst, offset); + HAnimData *srchanim = PLUGINOFFSET(HAnimData, src, offset); + HAnimHierarchy *srchier, *dsthier; + dsthanim->id = srchanim->id; + dsthanim->hierarchy = nil; + srchier = srchanim->hierarchy; + if(srchier && !(srchier->flags & HAnimHierarchy::SUBHIERARCHY)){ + dsthier = HAnimHierarchy::create(srchier->numNodes, nil, nil, srchier->flags, srchier->interpolator->maxInterpKeyFrameSize); + for(i = 0; i < dsthier->numNodes; i++){ + dsthier->nodeInfo[i].frame = nil; + dsthier->nodeInfo[i].flags = srchier->nodeInfo[i].flags; + dsthier->nodeInfo[i].index = srchier->nodeInfo[i].index; + dsthier->nodeInfo[i].id = srchier->nodeInfo[i].id; + } + dsthanim->hierarchy = dsthier; + dsthier->parentFrame = (Frame*)dst; + } + return dst; +} + +static Stream* +readHAnim(Stream *stream, int32, void *object, int32 offset, int32) +{ + int32 ver, numNodes; + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + ver = stream->readI32(); + assert(ver == 0x100); + hanim->id = stream->readI32(); + numNodes = stream->readI32(); + if(numNodes != 0){ + int32 flags = stream->readI32(); + int32 maxKeySize = stream->readI32(); + // Sizes are fucked for 64 bit pointers but + // AnimInterpolator::create() will take care of that + int32 *nodeFlags = rwNewT(int32, numNodes, + MEMDUR_FUNCTION | ID_HANIM); + int32 *nodeIDs = rwNewT(int32, numNodes, + MEMDUR_FUNCTION | ID_HANIM); + for(int32 i = 0; i < numNodes; i++){ + nodeIDs[i] = stream->readI32(); + stream->readI32(); // index...unused + nodeFlags[i] = stream->readI32(); + } + hanim->hierarchy = HAnimHierarchy::create(numNodes, + nodeFlags, nodeIDs, flags, maxKeySize); + hanim->hierarchy->parentFrame = (Frame*)object; + rwFree(nodeFlags); + rwFree(nodeIDs); + } + return stream; +} + +static Stream* +writeHAnim(Stream *stream, int32, void *object, int32 offset, int32) +{ + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + stream->writeI32(256); + stream->writeI32(hanim->id); + if(hanim->hierarchy == nil){ + stream->writeI32(0); + return stream; + } + HAnimHierarchy *hier = hanim->hierarchy; + stream->writeI32(hier->numNodes); + stream->writeI32(hier->flags); + stream->writeI32(hier->interpolator->maxInterpKeyFrameSize); + for(int32 i = 0; i < hier->numNodes; i++){ + stream->writeI32(hier->nodeInfo[i].id); + stream->writeI32(hier->nodeInfo[i].index); + stream->writeI32(hier->nodeInfo[i].flags); + } + return stream; +} + +static int32 +getSizeHAnim(void *object, int32 offset, int32) +{ + HAnimData *hanim = PLUGINOFFSET(HAnimData, object, offset); + if(!hAnimDoStream || + version >= 0x35000 && hanim->id == -1 && hanim->hierarchy == nil) + return 0; + if(hanim->hierarchy) + return 12 + 8 + hanim->hierarchy->numNodes*12; + return 12; +} + +static void +hAnimFrameRead(Stream *stream, Animation *anim) +{ + HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes; + for(int32 i = 0; i < anim->numFrames; i++){ + frames[i].time = stream->readF32(); + stream->read32(&frames[i].q, 4*4); + stream->read32(&frames[i].t, 3*4); + int32 prev = stream->readI32()/0x24; + frames[i].prev = &frames[prev]; + } +} + +static void +hAnimFrameWrite(Stream *stream, Animation *anim) +{ + HAnimKeyFrame *frames = (HAnimKeyFrame*)anim->keyframes; + for(int32 i = 0; i < anim->numFrames; i++){ + stream->writeF32(frames[i].time); + stream->write32(&frames[i].q, 4*4); + stream->write32(&frames[i].t, 3*4); + stream->writeI32((frames[i].prev - frames)*0x24); + } +} + +static uint32 +hAnimFrameGetSize(Animation *anim) +{ + return anim->numFrames*(4 + 4*4 + 3*4 + 4); +} + +//void hanimBlendCB(void *out, void *in1, void *in2, float32 a); +//void hanimAddCB(void *out, void *in1, void *in2); +//void hanimMulRecipCB(void *frame, void *start); + +static void +hanimApplyCB(void *result, void *frame) +{ + Matrix *m = (Matrix*)result; + HAnimInterpFrame *f = (HAnimInterpFrame*)frame; + m->rotate(f->q, COMBINEREPLACE); + m->pos = f->t; +} + +static void +hanimInterpCB(void *vout, void *vin1, void *vin2, float32 t, void*) +{ + HAnimInterpFrame *out = (HAnimInterpFrame*)vout; + HAnimKeyFrame *in1 = (HAnimKeyFrame*)vin1; + HAnimKeyFrame *in2 = (HAnimKeyFrame*)vin2; +assert(t >= in1->time && t <= in2->time); + float32 a = (t - in1->time)/(in2->time - in1->time); + out->t = lerp(in1->t, in2->t, a); + out->q = slerp(in1->q, in2->q, a); +} + +static void* +hanimOpen(void *object, int32 offset, int32 size) +{ + AnimInterpolatorInfo *info = rwNewT(AnimInterpolatorInfo, 1, MEMDUR_GLOBAL | ID_HANIM); + info->id = 1; + info->interpKeyFrameSize = sizeof(HAnimInterpFrame); + info->animKeyFrameSize = sizeof(HAnimKeyFrame); + info->customDataSize = 0; + info->applyCB = hanimApplyCB; + info->blendCB = nil; + info->interpCB = hanimInterpCB; + info->addCB = nil; + info->mulRecipCB = nil; + info->streamRead = hAnimFrameRead; + info->streamWrite = hAnimFrameWrite; + info->streamGetSize = hAnimFrameGetSize; + AnimInterpolatorInfo::registerInterp(info); + return object; +} + +static void* +hanimClose(void *object, int32 offset, int32 size) +{ + AnimInterpolatorInfo::unregisterInterp(AnimInterpolatorInfo::find(1)); + return object; +} + + +void +registerHAnimPlugin(void) +{ + Engine::registerPlugin(0, ID_HANIM, hanimOpen, hanimClose); + hAnimOffset = Frame::registerPlugin(sizeof(HAnimData), ID_HANIM, + createHAnim, + destroyHAnim, copyHAnim); + Frame::registerPluginStream(ID_HANIM, + readHAnim, + writeHAnim, + getSizeHAnim);} + +} diff --git a/src/image.cpp b/src/image.cpp new file mode 100644 index 0000000..632ca97 --- /dev/null +++ b/src/image.cpp @@ -0,0 +1,1115 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "ps2/rwps2.h" +#include "d3d/rwd3d.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" + +#define PLUGIN_ID ID_IMAGE + +namespace rw { + +int32 Image::numAllocated; + +struct FileAssociation +{ + char *extension; + Image *(*read)(const char *afilename); + void (*write)(Image *image, const char *filename); +}; + +struct ImageGlobals +{ + char *searchPaths; + int numSearchPaths; + FileAssociation fileFormats[10]; + int numFileFormats; +}; +int32 imageModuleOffset; + +#define IMAGEGLOBAL(v) (PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset)->v) + +// Image formats are as follows: +// 32 bit has 4 bytes: 8888 RGBA +// 24 bit has 3 bytes: 888 RGB +// 16 bit has 2 bytes: 1555 ARGB stored in platform native order (TODO?) +// palettes always have 4 bytes: r, g, b, a +// 8 bit has 1 byte: x +// 4 bit has 1 byte per two pixels: 0xLR, where L and R are the left and right pixel resp. + +Image* +Image::create(int32 width, int32 height, int32 depth) +{ + Image *img = (Image*)rwMalloc(sizeof(Image), MEMDUR_EVENT | ID_IMAGE); + if(img == nil){ + RWERROR((ERR_ALLOC, sizeof(Image))); + return nil; + } + numAllocated++; + img->flags = 0; + img->width = width; + img->height = height; + img->depth = depth; + img->bpp = depth < 8 ? 1 : depth/8; + img->stride = 0; + img->pixels = nil; + img->palette = nil; + return img; +} + +void +Image::destroy(void) +{ + this->free(); + rwFree(this); + numAllocated--; +} + +void +Image::allocate(void) +{ + if(this->pixels == nil){ + this->stride = this->width*this->bpp; + this->pixels = rwNewT(uint8, this->stride*this->height, MEMDUR_EVENT | ID_IMAGE); + this->flags |= 1; + } + if(this->palette == nil){ + if(this->depth == 4 || this->depth == 8) + this->palette = rwNewT(uint8, (1 << this->depth)*4, MEMDUR_EVENT | ID_IMAGE); + this->flags |= 2; + } +} + +void +Image::free(void) +{ + if(this->flags&1){ + rwFree(this->pixels); + this->pixels = nil; + } + if(this->flags&2){ + rwFree(this->palette); + this->palette = nil; + } + this->flags = 0; +} + +void +Image::setPixels(uint8 *pixels) +{ + this->pixels = pixels; + this->flags |= 1; +} + +void +decompressDXT1(uint8 *adst, int32 w, int32 h, uint8 *src) +{ + /* j loops through old texels + * x and y loop through new texels */ + int32 x = 0, y = 0; + uint32 c[4][4]; + uint8 idx[16]; + uint8 (*dst)[4] = (uint8(*)[4])adst; + for(int32 j = 0; j < w*h/2; j += 8){ + /* calculate colors */ + uint32 col0 = *((uint16*)&src[j+0]); + uint32 col1 = *((uint16*)&src[j+2]); + c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F; + c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F; + c[0][2] = ( col0 & 0x1F)*0xFF/0x1F; + c[0][3] = 0xFF; + + c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F; + c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F; + c[1][2] = ( col1 & 0x1F)*0xFF/0x1F; + c[1][3] = 0xFF; + if(col0 > col1){ + c[2][0] = (2*c[0][0] + 1*c[1][0])/3; + c[2][1] = (2*c[0][1] + 1*c[1][1])/3; + c[2][2] = (2*c[0][2] + 1*c[1][2])/3; + c[2][3] = 0xFF; + + c[3][0] = (1*c[0][0] + 2*c[1][0])/3; + c[3][1] = (1*c[0][1] + 2*c[1][1])/3; + c[3][2] = (1*c[0][2] + 2*c[1][2])/3; + c[3][3] = 0xFF; + }else{ + c[2][0] = (c[0][0] + c[1][0])/2; + c[2][1] = (c[0][1] + c[1][1])/2; + c[2][2] = (c[0][2] + c[1][2])/2; + c[2][3] = 0xFF; + + c[3][0] = 0x00; + c[3][1] = 0x00; + c[3][2] = 0x00; + c[3][3] = 0x00; + } + + /* make index list */ + uint32 indices = *((uint32*)&src[j+4]); + for(int32 k = 0; k < 16; k++){ + idx[k] = indices & 0x3; + indices >>= 2; + } + + /* write bytes */ + for(uint32 k = 0; k < 4; k++) + for(uint32 l = 0; l < 4; l++){ + dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0]; + dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1]; + dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2]; + dst[(y+l)*w + x+k][3] = c[idx[l*4+k]][3]; + } + x += 4; + if(x >= w){ + y += 4; + x = 0; + } + } +} + +void +decompressDXT3(uint8 *adst, int32 w, int32 h, uint8 *src) +{ + /* j loops through old texels + * x and y loop through new texels */ + int32 x = 0, y = 0; + uint32 c[4][4]; + uint8 idx[16]; + uint8 a[16]; + uint8 (*dst)[4] = (uint8(*)[4])adst; + for(int32 j = 0; j < w*h; j += 16){ + /* calculate colors */ + uint32 col0 = *((uint16*)&src[j+8]); + uint32 col1 = *((uint16*)&src[j+10]); + c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F; + c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F; + c[0][2] = ( col0 & 0x1F)*0xFF/0x1F; + + c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F; + c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F; + c[1][2] = ( col1 & 0x1F)*0xFF/0x1F; + + c[2][0] = (2*c[0][0] + 1*c[1][0])/3; + c[2][1] = (2*c[0][1] + 1*c[1][1])/3; + c[2][2] = (2*c[0][2] + 1*c[1][2])/3; + + c[3][0] = (1*c[0][0] + 2*c[1][0])/3; + c[3][1] = (1*c[0][1] + 2*c[1][1])/3; + c[3][2] = (1*c[0][2] + 2*c[1][2])/3; + + /* make index list */ + uint32 indices = *((uint32*)&src[j+12]); + for(int32 k = 0; k < 16; k++){ + idx[k] = indices & 0x3; + indices >>= 2; + } + uint64 alphas = *((uint64*)&src[j+0]); + for(int32 k = 0; k < 16; k++){ + a[k] = (alphas & 0xF)*17; + alphas >>= 4; + } + + /* write bytes */ + for(uint32 k = 0; k < 4; k++) + for(uint32 l = 0; l < 4; l++){ + dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0]; + dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1]; + dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2]; + dst[(y+l)*w + x+k][3] = a[l*4+k]; + } + x += 4; + if(x >= w){ + y += 4; + x = 0; + } + } +} + +void +decompressDXT5(uint8 *adst, int32 w, int32 h, uint8 *src) +{ + /* j loops through old texels + * x and y loop through new texels */ + int32 x = 0, y = 0; + uint32 c[4][4]; + uint32 a[8]; + uint8 idx[16]; + uint8 aidx[16]; + uint8 (*dst)[4] = (uint8(*)[4])adst; + for(int32 j = 0; j < w*h; j += 16){ + /* calculate colors */ + uint32 col0 = *((uint16*)&src[j+8]); + uint32 col1 = *((uint16*)&src[j+10]); + c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F; + c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F; + c[0][2] = ( col0 & 0x1F)*0xFF/0x1F; + + c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F; + c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F; + c[1][2] = ( col1 & 0x1F)*0xFF/0x1F; + if(col0 > col1){ + c[2][0] = (2*c[0][0] + 1*c[1][0])/3; + c[2][1] = (2*c[0][1] + 1*c[1][1])/3; + c[2][2] = (2*c[0][2] + 1*c[1][2])/3; + + c[3][0] = (1*c[0][0] + 2*c[1][0])/3; + c[3][1] = (1*c[0][1] + 2*c[1][1])/3; + c[3][2] = (1*c[0][2] + 2*c[1][2])/3; + }else{ + c[2][0] = (c[0][0] + c[1][0])/2; + c[2][1] = (c[0][1] + c[1][1])/2; + c[2][2] = (c[0][2] + c[1][2])/2; + + c[3][0] = 0x00; + c[3][1] = 0x00; + c[3][2] = 0x00; + } + + a[0] = src[j+0]; + a[1] = src[j+1]; + if(a[0] > a[1]){ + a[2] = (6*a[0] + 1*a[1])/7; + a[3] = (5*a[0] + 2*a[1])/7; + a[4] = (4*a[0] + 3*a[1])/7; + a[5] = (3*a[0] + 4*a[1])/7; + a[6] = (2*a[0] + 5*a[1])/7; + a[7] = (1*a[0] + 6*a[1])/7; + }else{ + a[2] = (4*a[0] + 1*a[1])/5; + a[3] = (3*a[0] + 2*a[1])/5; + a[4] = (2*a[0] + 3*a[1])/5; + a[5] = (1*a[0] + 4*a[1])/5; + a[6] = 0; + a[7] = 0xFF; + } + + /* make index list */ + uint32 indices = *((uint32*)&src[j+12]); + for(int32 k = 0; k < 16; k++){ + idx[k] = indices & 0x3; + indices >>= 2; + } + // only 6 indices + uint64 alphas = *((uint64*)&src[j+2]); + for(int32 k = 0; k < 16; k++){ + aidx[k] = alphas & 0x7; + alphas >>= 3; + } + + /* write bytes */ + for(uint32 k = 0; k < 4; k++) + for(uint32 l = 0; l < 4; l++){ + dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0]; + dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1]; + dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2]; + dst[(y+l)*w + x+k][3] = a[aidx[l*4+k]]; + } + x += 4; + if(x >= w){ + y += 4; + x = 0; + } + } +} + +void +Image::setPixelsDXT(int32 type, uint8 *pixels) +{ + switch(type){ + case 1: + decompressDXT1(this->pixels, this->width, this->height, pixels); + break; + case 3: + decompressDXT3(this->pixels, this->width, this->height, pixels); + break; + case 5: + decompressDXT5(this->pixels, this->width, this->height, pixels); + break; + } +} + +void +Image::setPalette(uint8 *palette) +{ + this->palette = palette; + this->flags |= 2; +} + +void +Image::compressPalette(void) +{ + if(this->depth != 8) + return; + uint8 *pixels = this->pixels; + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < this->width; x++){ + if(*line > 0xF) return; + line += this->bpp; + } + pixels += this->stride; + } + this->depth = 4; +} + +bool32 +Image::hasAlpha(void) +{ + uint8 ret = 0xFF; + uint8 *pixels = this->pixels; + if(this->depth == 32){ + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < this->width; x++){ + ret &= line[3]; + line += this->bpp; + } + pixels += this->stride; + } + }else if(this->depth == 24){ + return 0; + }else if(this->depth == 16){ + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < this->width; x++){ + ret &= line[1] & 0x80; + line += this->bpp; + } + pixels += this->stride; + } + return ret != 0x80; + }else if(this->depth <= 8){ + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < this->width; x++){ + ret &= this->palette[*line*4+3]; + line += this->bpp; + } + pixels += this->stride; + } + } + return ret != 0xFF; +} + +void +Image::convertTo32(void) +{ + assert(this->pixels); + uint8 *pixels = this->pixels; + int32 newstride = this->width*4; + uint8 *newpixels; + + void (*fun)(uint8 *out, uint8 *in) = nil; + switch(this->depth){ + case 4: + case 8: + assert(this->palette); + this->unpalettize(true); + return; + case 16: + fun = conv_RGBA8888_from_ARGB1555; + break; + case 24: + fun = conv_RGBA8888_from_RGB888; + break; + default: + return; + } + + newpixels = rwNewT(uint8, newstride*this->height, MEMDUR_EVENT | ID_IMAGE); + uint8 *pixels32 = newpixels; + for(int y = 0; y < this->height; y++){ + uint8 *line = pixels; + uint8 *newline = newpixels; + for(int x = 0; x < this->width; x++){ + fun(newline, line); + line += this->bpp; + newline += 4; + } + pixels += this->stride; + newpixels += newstride; + } + + this->free(); + this->depth = 32; + this->bpp = 4; + this->stride = newstride; + this->pixels = nil; + this->palette = nil; + this->setPixels(pixels32); +} + +void +Image::palettize(int32 depth) +{ + RGBA colors[256]; + ColorQuant quant; + uint8 *newpixels; + uint32 newstride; + + quant.init(); + quant.addImage(this); + assert(depth <= 8); + quant.makePalette(1<width; + newpixels = rwNewT(uint8, newstride*this->height, MEMDUR_EVENT | ID_IMAGE); + // TODO: maybe do floyd-steinberg dithering? + quant.matchImage(newpixels, newstride, this); + + this->free(); + this->depth = depth; + this->bpp = depth < 8 ? 1 : depth/8; + this->stride = newstride; + this->pixels = nil; + this->palette = nil; + this->setPixels(newpixels); + this->allocate(); + memcpy(this->palette, colors, 4*(1<depth > 8) + return; + assert(this->pixels); + assert(this->palette); + + int32 ndepth = (forceAlpha || this->hasAlpha()) ? 32 : 24; + int32 nstride = this->width*ndepth/8; + uint8 *npixels = rwNewT(uint8, nstride*this->height, MEMDUR_EVENT | ID_IMAGE); + + uint8 *line = this->pixels; + uint8 *nline = npixels; + uint8 *p, *np; + for(int32 y = 0; y < this->height; y++){ + p = line; + np = nline; + for(int32 x = 0; x < this->width; x++){ + np[0] = this->palette[*p*4+0]; + np[1] = this->palette[*p*4+1]; + np[2] = this->palette[*p*4+2]; + np += 3; + if(ndepth == 32) + *np++ = this->palette[*p*4+3]; + p++; + } + line += this->stride; + nline += nstride; + } + this->free(); + this->depth = ndepth; + this->bpp = ndepth < 8 ? 1 : ndepth/8; + this->stride = nstride; + this->setPixels(npixels); +} + +// Copy the biggest channel value to alpha +void +Image::makeMask(void) +{ + int32 maxcol; + switch(this->depth){ + case 4: + case 8: { + assert(this->palette); + int32 pallen = 1 << this->depth; + for(int32 i = 0; i < pallen; i++){ + maxcol = this->palette[i*4+0]; + if(this->palette[i*4+1] > maxcol) maxcol = this->palette[i*4+1]; + if(this->palette[i*4+2] > maxcol) maxcol = this->palette[i*4+2]; + this->palette[i*4+3] = maxcol; + } + break; + } + + case 16: + case 24: + this->convertTo32(); + // fallthrough + + case 32: { + assert(this->pixels); + uint8 *line = this->pixels; + uint8 *p; + for(int32 y = 0; y < this->height; y++){ + p = line; + for(int32 x = 0; x < this->width; x++){ + maxcol = p[0]; + if(p[1] > maxcol) maxcol = p[1]; + if(p[2] > maxcol) maxcol = p[2]; + p[3] = maxcol; + p += this->bpp; + } + line += this->stride; + } + break; + } + } +} + +void +Image::applyMask(Image *mask) +{ + if(this->width != mask->width || this->height != mask->height) + return; // TODO: set an error + // we could use alpha with 16 bits but what's the point? + if(mask->depth == 16 || mask->depth == 24) + return; + + this->convertTo32(); + assert(this->depth == 32); + + uint8 *line = this->pixels; + uint8 *mline = mask->pixels; + uint8 *p, *m; + for(int32 y = 0; y < this->height; y++){ + p = line; + m = mline; + for(int32 x = 0; x < this->width; x++){ + if(mask->depth == 32) + p[3] = m[3]; + else if(mask->depth <= 8) + p[3] = mask->palette[m[0]*4+3]; + p += this->bpp; + m += mask->bpp; + } + line += this->stride; + mline += mask->stride; + } +} + +void +Image::removeMask(void) +{ + if(this->depth <= 8){ + assert(this->palette); + int32 pallen = 4*(1 << this->depth); + for(int32 i = 0; i < pallen; i += 4) + this->palette[i+3] = 0xFF; + return; + } + if(this->depth == 24) + return; + assert(this->pixels); + uint8 *line = this->pixels; + uint8 *p; + for(int32 y = 0; y < this->height; y++){ + p = line; + for(int32 x = 0; x < this->width; x++){ + switch(this->depth){ + case 16: + p[1] |= 0x80; + p += 2; + break; + case 32: + p[3] = 0xFF; + p += 4; + break; + } + } + line += this->stride; + } +} + +Image* +Image::extractMask(void) +{ + Image *img = Image::create(this->width, this->height, 8); + img->allocate(); + + // use an 8bit palette to store all shades of grey + for(int32 i = 0; i < 256; i++){ + img->palette[i*4+0] = i; + img->palette[i*4+1] = i; + img->palette[i*4+2] = i; + img->palette[i*4+3] = 0xFF; + } + + // Then use the alpha value as palette index + uint8 *line = this->pixels; + uint8 *nline = img->pixels; + uint8 *p, *np; + for(int32 y = 0; y < this->height; y++){ + p = line; + np = nline; + for(int32 x = 0; x < this->width; x++){ + switch(this->depth){ + case 4: + case 8: + *np++ = this->palette[*p*4+3]; + p++; + break; + case 16: + *np++ = 0xFF*!!(p[1]&0x80); + p += 2; + break; + case 24: + *np++ = 0xFF; + p += 3; + break; + case 32: + *np++ = p[3]; + p += 4; + break; + } + } + line += this->stride; + nline += img->stride; + } + return img; +} + +static char* +rwstrdup(const char *s) +{ + char *t; + size_t len = strlen(s)+1; + t = (char*)rwMalloc(len, MEMDUR_EVENT); + if(t) + memcpy(t, s, len); + return t; +} + +void +Image::setSearchPath(const char *path) +{ + char *p, *end; + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + rwFree(g->searchPaths); + g->numSearchPaths = 0; + if(path) + g->searchPaths = p = rwstrdup(path); + else{ + g->searchPaths = nil; + return; + } + while(p && *p){ + end = strchr(p, ';'); + if(end) + *end++ = '\0'; + g->numSearchPaths++; + p = end; + } +} + +void +Image::printSearchPath(void) +{ + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + char *p = g->searchPaths; + for(int i = 0; i < g->numSearchPaths; i++){ + printf("%s\n", p); + p += strlen(p) + 1; + } +} + +char* +Image::getFilename(const char *name) +{ + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + FILE *f; + char *s, *p = g->searchPaths; + size_t len = strlen(name)+1; + if(g->numSearchPaths == 0){ + s = rwstrdup(name); + makePath(s); + f = fopen(s, "rb"); + if(f){ + fclose(f); + printf("found %s\n", s); + return s; + } + rwFree(s); + return nil; + }else + for(int i = 0; i < g->numSearchPaths; i++){ + s = (char*)rwMalloc(strlen(p)+len, MEMDUR_EVENT | ID_IMAGE); + if(s == nil){ + RWERROR((ERR_ALLOC, strlen(p)+len)); + return nil; + } + strcpy(s, p); + strcat(s, name); + makePath(s); + f = fopen(s, "r"); + if(f){ + fclose(f); + printf("found %s\n", name); + return s; + } + rwFree(s); + p += strlen(p) + 1; + } + return nil; +} + +Image* +Image::readMasked(const char *imageName, const char *maskName) +{ + Image *img, *mask; + + img = read(imageName); + if(img == nil) + return nil; + if(maskName && maskName[0]){ + mask = read(maskName); + if(mask == nil) + return img; + mask->makeMask(); + int32 origDepth = img->depth; + img->applyMask(mask); + mask->destroy(); + if(origDepth <= 8 && img->depth != origDepth) + img->palettize(origDepth); + } + return img; +} + +Image* +Image::read(const char *imageName) +{ + int i; + char *filename, *ext, *found; + Image *img; + + filename = rwNewT(char, strlen(imageName) + 20, MEMDUR_FUNCTION | ID_IMAGE); + strcpy(filename, imageName); + ext = filename + strlen(filename); + *ext++ = '.'; + // Try all supported extensions + for(i = 0; i < IMAGEGLOBAL(numFileFormats); i++){ + if(IMAGEGLOBAL(fileFormats)[i].read == nil) + continue; + strncpy(ext, IMAGEGLOBAL(fileFormats)[i].extension, 19); + found = getFilename(filename); + // Found a file + if(found){ + img = IMAGEGLOBAL(fileFormats)[i].read(found); + rwFree(found); + // It was a valid image of that format + if(img){ + rwFree(filename); + return img; + } + } + } + rwFree(filename); + return nil; +} + +bool32 +Image::registerFileFormat(const char *ext, fileRead read, fileWrite write) +{ + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + if(g->numFileFormats >= nelem(g->fileFormats)) + return 0; + g->fileFormats[g->numFileFormats].extension = rwstrdup(ext); + g->fileFormats[g->numFileFormats].read = read; + g->fileFormats[g->numFileFormats].write = write; + g->numFileFormats++; + return 1; +} + +static void* +imageOpen(void *object, int32 offset, int32 size) +{ + imageModuleOffset = offset; + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + g->searchPaths = nil; + g->numSearchPaths = 0; + g->numFileFormats = 0; + return object; +} + +static void* +imageClose(void *object, int32 offset, int32 size) +{ + ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset); + int i; + rwFree(g->searchPaths); + g->searchPaths = nil; + g->numSearchPaths = 0; + for(i = 0; i < g->numFileFormats; i++) + rwFree(g->fileFormats[i].extension); + g->numFileFormats = 0; + return object; +} + +void +Image::registerModule(void) +{ + Engine::registerPlugin(sizeof(ImageGlobals), ID_IMAGEMODULE, imageOpen, imageClose); +} + + + +/* + * Color Quantization + */ + +// An address for a single level is 4 bits. +// Since we have 8 bpp that is 32 bits to address any tree node. +// The lower bits address the higher level tree nodes. +// This is essentially a bit reverse and swizzle. +static uint32 +makeTreeAddr(RGBA color) +{ + int32 i; + uint32 addr = 0; + uint32 r = 1; + uint32 g = 2; + uint32 b = 4; + uint32 a = 8; + for(i = 0; i < 8; i++){ + uint32 mask = 0x80>>i; + if(color.red & mask) addr |= r; + if(color.green & mask) addr |= g; + if(color.blue & mask) addr |= b; + if(color.alpha & mask) addr |= a; + r <<= 4; + g <<= 4; + b <<= 4; + a <<= 4; + } + return addr; +} + +void +ColorQuant::Node::destroy(void) +{ + int i; + for(i = 0; i < 16; i++) + if(this->children[i]) + this->children[i]->destroy(); + if(this->link.next) + this->link.remove(); + rwFree(this); +} + +ColorQuant::Node* +ColorQuant::createNode(int32 level) +{ + int i; + ColorQuant::Node *node = rwNewT(ColorQuant::Node, 1, MEMDUR_EVENT | ID_IMAGE); + node->parent = nil; + for(i = 0; i < 16; i++) + node->children[i] = nil; + node->r = 0; + node->g = 0; + node->b = 0; + node->a = 0; + node->numPixels = 0; + node->link.init(); + + if(level == 0) + this->leaves.append(&node->link); + + return node; +} + +ColorQuant::Node* +ColorQuant::getNode(ColorQuant::Node *root, uint32 addr, int32 level) +{ + if(level == 0) + return root; + + uint32 a = addr & 0xF; + if(root->children[a] == nil){ + root->children[a] = this->createNode(level-1); + root->children[a]->parent = root; + } + + return this->getNode(root->children[a], addr>>4, level-1); +} + +ColorQuant::Node* +ColorQuant::findNode(ColorQuant::Node *root, uint32 addr, int32 level) +{ + if(level == 0) + return root; + + uint32 a = addr & 0xF; + if(root->children[a] == nil) + return root; + + return this->findNode(root->children[a], addr>>4, level-1); +} + +void +ColorQuant::reduceNode(Node *node) +{ + int i; + assert(node->numPixels == 0); + for(i = 0; i < 16; i++) + if(node->children[i]){ + node->r += node->children[i]->r; + node->g += node->children[i]->g; + node->b += node->children[i]->b; + node->a += node->children[i]->a; + node->numPixels += node->children[i]->numPixels; + node->children[i]->destroy(); + node->children[i] = nil; + } + assert(node->link.next == nil); + assert(node->link.prev == nil); + this->leaves.append(&node->link); +} + +void +ColorQuant::Node::addColor(RGBA color) +{ + this->r += color.red; + this->g += color.green; + this->b += color.blue; + this->a += color.alpha; + this->numPixels++; +} + +void +ColorQuant::init(void) +{ + this->leaves.init(); + this->root = this->createNode(QUANTDEPTH); +} + +void +ColorQuant::destroy(void) +{ + this->root->destroy(); +} + +void +ColorQuant::addColor(RGBA color) +{ + uint32 addr = makeTreeAddr(color); + ColorQuant::Node *node = this->getNode(root, addr, QUANTDEPTH); + node->addColor(color); +} + +uint8 +ColorQuant::findColor(RGBA color) +{ + uint32 addr = makeTreeAddr(color); + ColorQuant::Node *node = this->findNode(root, addr, QUANTDEPTH); + return node->numPixels; +} + +void +ColorQuant::addImage(Image *img) +{ + RGBA col; + uint8 rgba[4]; + uint8 *pixels = img->pixels; + for(int y = 0; y < img->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < img->width; x++){ + uint8 *p = line; + switch(img->depth){ + case 4: case 8: + conv_RGBA8888_from_RGBA8888(rgba, &img->palette[p[0]*4]); + break; + case 32: + conv_RGBA8888_from_RGBA8888(rgba, p); + break; + case 24: + conv_RGBA8888_from_RGB888(rgba, p); + break; + case 16: + conv_RGBA8888_from_ARGB1555(rgba, p); + break; + default: assert(0 && "invalid depth"); + } + col.red = rgba[0]; + col.green = rgba[1]; + col.blue = rgba[2]; + col.alpha = rgba[3]; + this->addColor(col); + line += img->bpp; + } + pixels += img->stride; + } +} + +void +ColorQuant::makePalette(int32 numColors, RGBA *colors) +{ + while(this->leaves.count() > numColors){ + Node *n = LLLinkGetData(this->leaves.link.next, Node, link); + this->reduceNode(n->parent); + } + + int i = 0; + FORLIST(lnk, this->leaves){ + Node *n = LLLinkGetData(lnk, Node, link); + n->r /= n->numPixels; + n->g /= n->numPixels; + n->b /= n->numPixels; + n->a /= n->numPixels; + colors[i].red = n->r; + colors[i].green = n->g; + colors[i].blue = n->b; + colors[i].alpha = n->a; + n->numPixels = i++; + } +} + +void +ColorQuant::matchImage(uint8 *dstPixels, uint32 dstStride, Image *img) +{ + RGBA col; + uint8 rgba[4]; + uint8 *pixels = img->pixels; + for(int y = 0; y < img->height; y++){ + uint8 *line = pixels; + uint8 *dline = dstPixels; + for(int x = 0; x < img->width; x++){ + uint8 *p = line; + uint8 *d = dline; + switch(img->depth){ + case 4: case 8: + conv_RGBA8888_from_RGBA8888(rgba, &img->palette[p[0]*4]); + break; + case 32: + conv_RGBA8888_from_RGBA8888(rgba, p); + break; + case 24: + conv_RGBA8888_from_RGB888(rgba, p); + break; + case 16: + conv_RGBA8888_from_ARGB1555(rgba, p); + break; + default: assert(0 && "invalid depth"); + } + + col.red = rgba[0]; + col.green = rgba[1]; + col.blue = rgba[2]; + col.alpha = rgba[3]; + *d = this->findColor(col); + + line += img->bpp; + dline++; + } + pixels += img->stride; + dstPixels += dstStride; + } +} + + + +} diff --git a/src/light.cpp b/src/light.cpp new file mode 100644 index 0000000..ca68cda --- /dev/null +++ b/src/light.cpp @@ -0,0 +1,163 @@ +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_LIGHT + +namespace rw { + +int32 Light::numAllocated; + +PluginList Light::s_plglist(sizeof(Light)); + +static void +lightSync(ObjectWithFrame*) +{ +} + +static void +worldLightSync(ObjectWithFrame *obj) +{ + Light *light = (Light*)obj; + light->originalSync(obj); +} + +Light* +Light::create(int32 type) +{ + Light *light = (Light*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_LIGHT); + if(light == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + light->object.object.init(Light::ID, type); + light->object.syncCB = lightSync; + light->radius = 0.0f; + light->color.red = 1.0f; + light->color.green = 1.0f; + light->color.blue = 1.0f; + light->color.alpha = 1.0f; + light->minusCosAngle = 1.0f; + light->object.object.privateFlags = 1; + light->object.object.flags = LIGHTATOMICS | LIGHTWORLD; + light->inWorld.init(); + + // clump extension + light->clump = nil; + light->inClump.init(); + + // world extension + light->world = nil; + light->originalSync = light->object.syncCB; + light->object.syncCB = worldLightSync; + + s_plglist.construct(light); + return light; +} + +void +Light::destroy(void) +{ + s_plglist.destruct(this); + assert(this->clump == nil); + assert(this->world == nil); + this->setFrame(nil); + rwFree(this); + numAllocated--; +} + +void +Light::setAngle(float32 angle) +{ + this->minusCosAngle = -cosf(angle); +} + +float32 +Light::getAngle(void) +{ + return acosf(-this->minusCosAngle); +} + +void +Light::setColor(float32 r, float32 g, float32 b) +{ + this->color.red = r; + this->color.green = g; + this->color.blue = b; + this->object.object.privateFlags = r == g && r == b; +} + +struct LightChunkData +{ + float32 radius; + float32 red, green, blue; + float32 minusCosAngle; + uint32 type_flags; +}; + +Light* +Light::streamRead(Stream *stream) +{ + uint32 version; + LightChunkData buf; + + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + stream->read32(&buf, sizeof(LightChunkData)); + Light *light = Light::create(buf.type_flags>>16); + if(light == nil) + return nil; + light->radius = buf.radius; + light->setColor(buf.red, buf.green, buf.blue); + float32 a = buf.minusCosAngle; + if(version >= 0x30300) + light->minusCosAngle = a; + else + // tan -> -cos + light->minusCosAngle = -1.0f/sqrtf(a*a+1.0f); + light->object.object.flags = (uint8)buf.type_flags; + if(s_plglist.streamRead(stream, light)) + return light; + light->destroy(); + return nil; +} + +bool +Light::streamWrite(Stream *stream) +{ + LightChunkData buf; + writeChunkHeader(stream, ID_LIGHT, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, sizeof(LightChunkData)); + buf.radius = this->radius; + buf.red = this->color.red; + buf.green = this->color.green; + buf.blue = this->color.blue; + if(version >= 0x30300) + buf.minusCosAngle = this->minusCosAngle; + else + buf.minusCosAngle = tanf(acosf(-this->minusCosAngle)); + buf.type_flags = (uint32)this->object.object.flags | + (uint32)this->object.object.subType << 16; + stream->write32(&buf, sizeof(LightChunkData)); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Light::streamGetSize(void) +{ + return 12 + sizeof(LightChunkData) + 12 + s_plglist.streamGetSize(this); +} + +} diff --git a/src/lodepng/lodepng.cpp b/src/lodepng/lodepng.cpp new file mode 100644 index 0000000..ee8cf33 --- /dev/null +++ b/src/lodepng/lodepng.cpp @@ -0,0 +1,6410 @@ +/* +LodePNG version 20200306 + +Copyright (c) 2005-2020 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#ifdef LODEPNG_COMPILE_DISK +#include /* LONG_MAX */ +#include /* file handling */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +#include /* allocations */ +#endif /* LODEPNG_COMPILE_ALLOCATORS */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20200306"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) { +#ifdef LODEPNG_MAX_ALLOC + if(size > LODEPNG_MAX_ALLOC) return 0; +#endif + return malloc(size); +} + +/* NOTE: when realloc returns NULL, it leaves the original memory untouched */ +static void* lodepng_realloc(void* ptr, size_t new_size) { +#ifdef LODEPNG_MAX_ALLOC + if(new_size > LODEPNG_MAX_ALLOC) return 0; +#endif + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) { + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +/* TODO: support giving additional void* payload to the custom allocators */ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) +#define LODEPNG_INLINE inline +#else +#define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) +#define LODEPNG_RESTRICT __restrict +#else +#define LODEPNG_RESTRICT /* not available */ +#endif + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, + const void* LODEPNG_RESTRICT src, size_t size) { + size_t i; + for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; +} + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, + int value, size_t num) { + size_t i; + for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; +} + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) { + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while(*a) a++; + return (size_t)(a - orig); +} + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + +#ifdef LODEPNG_COMPILE_ZLIB +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) { + size_t d; + if(lodepng_addofl(a, b, &d)) return 1; + return d > c; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*dynamic vector of unsigned ints*/ +typedef struct uivector { + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) { + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) { + size_t allocsize = size * sizeof(unsigned); + if(allocsize > p->allocsize) { + size_t newsize = allocsize + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static void uivector_init(uivector* p) { + p->data = NULL; + p->size = p->allocsize = 0; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) { + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector { + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) { + if(size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static ucvector ucvector_init(unsigned char* buffer, size_t size) { + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/*free string pointer and set it to NULL*/ +static void string_cleanup(char** out) { + lodepng_free(*out); + *out = NULL; +} + +static char* alloc_string_sized(const char* in, size_t insize) { + char* out = (char*)lodepng_malloc(insize + 1); + if(out) { + lodepng_memcpy(out, in, insize); + out[insize] = 0; + } + return out; +} + +/* dynamically allocates a new string with a copy of the null terminated input text */ +static char* alloc_string(const char* in) { + return alloc_string_sized(in, lodepng_strlen(in)); +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) +static unsigned lodepng_read32bitInt(const unsigned char* buffer) { + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | + ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} +#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) { + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if(readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { + long size = lodepng_filesize(filename); + if(size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite(buffer, 1, buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct { + ucvector* data; + unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ +} LodePNGBitWriter; + +static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { + writer->data = data; + writer->bp = 0; +} + +/*TODO: this ignores potential out of memory errors*/ +#define WRITEBIT(writer, bit){\ + /* append new byte */\ + if(((writer->bp) & 7u) == 0) {\ + if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ + writer->data->data[writer->data->size - 1] = 0;\ + }\ + (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ + ++writer->bp;\ +} + +/* LSB of value is written first, and LSB of bytes is used first */ +static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ + WRITEBIT(writer, value); + } else { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + size_t i; + for(i = 0; i != nbits; ++i) { + WRITEBIT(writer, (unsigned char)((value >> i) & 1)); + } + } +} + +/* This one is to use for adding huffman symbol, the value bits are written MSB first */ +static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + size_t i; + for(i = 0; i != nbits; ++i) { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct { + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +} LodePNGBitReader; + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ +} + +/* +ensureBits functions: +Ensures the reader can at least read nbits bits in one or more readBits calls, +safely even if not enough bits are available. +Returns 1 if there are enough bits available, 0 if not. +*/ + +/*See ensureBits documentation above. This one ensures exactly 1 bit */ +/*static unsigned ensureBits1(LodePNGBitReader* reader) { + if(reader->bp >= reader->bitsize) return 0; + reader->buffer = (unsigned)reader->data[reader->bp >> 3u] >> (reader->bp & 7u); + return 1; +}*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static unsigned ensureBits9(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static unsigned ensureBits17(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE unsigned ensureBits25(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE unsigned ensureBits32(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + +/* Must have enough bits available with ensureBits */ +static void advanceBits(LodePNGBitReader* reader, size_t nbits) { + reader->buffer >>= nbits; + reader->bp += nbits; +} + +/* Must have enough bits available with ensureBits */ +static unsigned readBits(LodePNGBitReader* reader, size_t nbits) { + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} + +/* Public for testing only. steps and result must have numsteps values. */ +unsigned lode_png_test_bitreader(const unsigned char* data, size_t size, + size_t numsteps, const size_t* steps, unsigned* result) { + size_t i; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, data, size); + if(error) return 0; + for(i = 0; i < numsteps; i++) { + size_t step = steps[i]; + unsigned ok; + if(step > 25) ok = ensureBits32(&reader, step); + else if(step > 17) ok = ensureBits25(&reader, step); + else if(step > 9) ok = ensureBits17(&reader, step); + else ok = ensureBits9(&reader, step); + if(!ok) return 0; + result[i] = readBits(&reader, step); + } + return 1; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static unsigned reverseBits(unsigned bits, unsigned num) { + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree { + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +} HuffmanTree; + +static void HuffmanTree_init(HuffmanTree* tree) { + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) { + lodepng_free(tree->codes); + lodepng_free(tree->lengths); + lodepng_free(tree->table_len); + lodepng_free(tree->table_value); +} + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); + if(!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for(i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); + if(!tree->table_len || !tree->table_value) { + lodepng_free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for(i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (1u << (l - FIRSTBITS)); + } + lodepng_free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for(i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + unsigned reverse = reverseBits(symbol, l); + if(l == 0) continue; + numpresent++; + + if(l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for(j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for(j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if(numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) return 55; + } + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if(!error) { + for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) { + if(tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + lodepng_free(blcount); + lodepng_free(nextcode); + + if(!error) error = HuffmanTree_makeTable(tree); + return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) { + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode { + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists { + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) { + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } else { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } else if(numpresent == 1) { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } else { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if(l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + unsigned index2; + advanceBits(reader, FIRSTBITS); + index2 = value + peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[index2] - FIRSTBITS); + return codetree->table_value[index2]; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { + unsigned error = generateFixedLitLenTree(tree_ll); + if(error) return error; + return generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + LodePNGBitReader* reader) { + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if(!ensureBits17(reader, 14)) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while(!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for(i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if(code <= 15) /*a length code*/ { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if(code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, + unsigned btype) { + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + while(!error) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + ensureBits25(reader, 20); /* up to 15 for the huffman symbol, up to 5 for the length extra bits */ + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if(code_ll <= 255) /*literal symbol*/ { + if(!ucvector_resize(out, out->size + 1)) ERROR_BREAK(83 /*alloc fail*/); + out->data[out->size - 1] = (unsigned char)code_ll; + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(numextrabits_l != 0) { + /* bits already ensured above */ + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if(code_d > 29) { + if(code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + if(!ucvector_resize(out, out->size + length)) ERROR_BREAK(83 /*alloc fail*/); + if(distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for(forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if(code_ll == 256) { + break; /*end code, break the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, + const LodePNGDecompressSettings* settings) { + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + + reader->bp = bytepos << 3u; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if(error) return error; + + while(!BFINAL) { + unsigned BTYPE; + if(!ensureBits9(&reader, 3)) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE); /*compression, BTYPE 01 or 10*/ + + if(error) return error; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if(array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) { + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + size_t pos = values->size; + /*TODO: return error when this fails (out of memory)*/ + unsigned ok = uivector_resize(values, values->size + 4); + if(ok) { + values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; + values->data[pos + 1] = extra_length; + values->data[pos + 2] = dist_code; + values->data[pos + 3] = extra_distance; + } +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash { + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) { + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) { + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { + unsigned result = 0; + if(pos + 2 < size) { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= ((unsigned)data[pos + 0] << 0u); + result ^= ((unsigned)data[pos + 1] << 4u); + result ^= ((unsigned)data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = (int)wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = (int)wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) { + if(chainlength++ >= maxchainlength) break; + current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } else { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } else { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else if(length < minmatch || (length == 3 && offset > 4096)) { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + size_t pos = out->size; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + LEN = 65535; + if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); + out->data[pos + 0] = firstbyte; + out->data[pos + 1] = (unsigned char)(LEN & 255); + out->data[pos + 2] = (unsigned char)(LEN >> 8u); + out->data[pos + 3] = (unsigned char)(NLEN & 255); + out->data[pos + 4] = (unsigned char)(NLEN >> 8u); + lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); + datapos += LEN; + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) { + unsigned val = lz77_encoded->data[i]; + writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); + if(val > 256) /*for a length code, 3 more things have to be added*/ { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + writeBits(writer, length_extra_bits, n_length_extra_bits); + writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); + writeBits(writer, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lengths used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ + unsigned* frequencies_d = 0; /*frequency of dist codes*/ + unsigned* frequencies_cl = 0; /*frequency of code length codes*/ + unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ + unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ + size_t datasize = dataend - datapos; + + /* + If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent + tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are + some analogies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t i; + size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + /* could fit on stack, but >1KB is on the larger side so allocate instead */ + frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); + frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); + frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) { + lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); + lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); + lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(settings->use_lz77) { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } else { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll[symbol]; + if(symbol > 256) { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d[dist]; + i += 3; + } + } + frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); + if(error) break; + + numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); + numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); + /*store the code lengths of both generated trees in bitlen_lld*/ + numcodes_lld = numcodes_ll + numcodes_d; + bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); + /*numcodes_lld_e never needs more size than bitlen_lld*/ + bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); + if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ + numcodes_lld_e = 0; + + for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; + for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != numcodes_lld; ++i) { + unsigned j = 0; /*amount of repetitions*/ + while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; + + if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { + bitlen_lld_e[numcodes_lld_e++] = 17; + bitlen_lld_e[numcodes_lld_e++] = j - 3; + } else /*repeat code 18 supports max 138 zeroes*/ { + if(j > 138) j = 138; + bitlen_lld_e[numcodes_lld_e++] = 18; + bitlen_lld_e[numcodes_lld_e++] = j - 11; + } + i += (j - 1); + } else if(j >= 3) /*repeat code for value other than zero*/ { + size_t k; + unsigned num = j / 6u, rest = j % 6u; + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + for(k = 0; k < num; ++k) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = 6 - 3; + } + if(rest >= 3) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = rest - 3; + } + else j -= rest; + i += j; + } else /*too short to benefit from repeat code*/ { + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + for(i = 0; i != numcodes_lld_e; ++i) { + ++frequencies_cl[bitlen_lld_e[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, + NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*compute amount of code-length-code-lengths to output*/ + numcodes_cl = NUM_CODE_LENGTH_CODES; + /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ + while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { + numcodes_cl--; + } + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + writeBits(writer, BFINAL, 1); + writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ + writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies + or in the loop for numcodes_cl above, which saves space. */ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)(numcodes_cl - 4); + writeBits(writer, HLIT, 5); + writeBits(writer, HDIST, 5); + writeBits(writer, HCLEN, 4); + + /*write the code lengths of the code length alphabet ("bitlen_cl")*/ + for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); + + /*write the lengths of the lit/len AND the dist alphabet*/ + for(i = 0; i != numcodes_lld_e; ++i) { + writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); + /*extra bits of repeat codes*/ + if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); + else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); + else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); + + /*write the end code*/ + writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + lodepng_free(frequencies_ll); + lodepng_free(frequencies_d); + lodepng_free(frequencies_cl); + lodepng_free(bitlen_lld); + lodepng_free(bitlen_lld_e); + + return error; +} + +static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + error = generateFixedLitLenTree(&tree_ll); + if(!error) error = generateFixedDistanceTree(&tree_d); + + if(!error) { + writeBits(writer, BFINAL, 1); + writeBits(writer, 1, 1); /*first bit of BTYPE*/ + writeBits(writer, 0, 1); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { + writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); + } + } + /*add END code*/ + if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); + } + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + Hash hash; + LodePNGBitWriter writer; + + LodePNGBitWriter_init(&writer, out); + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8u + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + + if(!error) { + for(i = 0; i != numdeflateblocks && !error; ++i) { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); + } + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { + return settings->custom_deflate(out, outsize, in, insize, settings); + } else { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while(len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for(i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) { + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +static unsigned lodepng_zlib_decompressv(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(settings->custom_zlib) { + return settings->custom_zlib(out, outsize, in, insize, settings); + } else { + unsigned error; + ucvector v = ucvector_init(*out, *outsize); + if(expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + *out = NULL; + *outsize = 0; + if(!error) { + *outsize = deflatesize + 6; + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!*out) error = 83; /*alloc fail*/ + } + + if(!error) { + unsigned ADLER32 = adler32(in, (unsigned)insize); + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + (*out)[0] = (unsigned char)(CMFFLG >> 8); + (*out)[1] = (unsigned char)(CMFFLG & 255); + for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; + lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); + } + + lodepng_free(deflatedata); + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { + return settings->custom_zlib(out, outsize, in, insize, settings); + } else { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + (void)expected_size; + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifndef LODEPNG_NO_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#else /* !LODEPNG_NO_COMPILE_CRC */ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* !LODEPNG_NO_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) { + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { + if(lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) { + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) { + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + const unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next(chunk, end); + } +} + +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next_const(chunk, end); + } +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { + unsigned i; + size_t total_chunk_length, new_length; + unsigned char *chunk_start, *new_buffer; + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outsize) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +/*Sets length and name and allocates the space for data and crc but does not +set data or crc yet. Returns the start of the chunk in chunk. The start of +the data is at chunk + 8. To finalize chunk, add the data, then use +lodepng_chunk_generate_crc */ +static unsigned lodepng_chunk_init(unsigned char** chunk, + ucvector* out, + unsigned length, const char* type) { + size_t new_length = out->size; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; + if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ + *chunk = out->data + new_length - length - 12u; + + /*1: length*/ + lodepng_set32bitInt(*chunk, length); + + /*2: chunk name (4 letters)*/ + lodepng_memcpy(*chunk + 4, type, 4); + + return 0; +} + +/* like lodepng_chunk_create but with custom allocsize */ +static unsigned lodepng_chunk_createv(ucvector* out, + unsigned length, const char* type, const unsigned char* data) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); + + /*3: the data*/ + lodepng_memcpy(chunk + 8, data, length); + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, + unsigned length, const char* type, const unsigned char* data) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_chunk_createv(&v, length, type, data); + *out = v.data; + *outsize = v.size; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) { + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); + if(!info->palette) return; /*alloc fail*/ + for(i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if(source->palette) { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { + LodePNGColorMode result; + lodepng_color_mode_init(&result); + result.colortype = colortype; + result.bitdepth = bitdepth; + return result; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) { + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(!info->palette) /*allocate palette if empty*/ { + lodepng_color_mode_alloc_palette(info); + if(!info->palette) return 83; /*alloc fail*/ + } + if(info->palettesize >= 256) { + return 108; /*too many palette values*/ + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +/*calculate bits per pixel out of colortype and bitdepth*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) { + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { + size_t i; + for(i = 0; i != info->palettesize; ++i) { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +#ifdef LODEPNG_COMPILE_PNG + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Safely checks whether size_t overflow can be caused due to amount of pixels. +This check is overcautious rather than precise. If this check indicates no overflow, +you can safely compute in a size_t (but not an unsigned): +-(size_t)w * (size_t)h * 8 +-amount of bytes in IDAT (including filter, padding and Adam7 bytes) +-amount of bytes in raw color model +Returns 1 if overflow possible, 0 if not. +*/ +static int lodepng_pixel_overflow(unsigned w, unsigned h, + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { + size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) { + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->text_num; ++i) { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->text_keys = 0; + dest->text_strings = 0; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + + if(new_keys) info->text_keys = new_keys; + if(new_strings) info->text_strings = new_strings; + + if(!new_keys || !new_strings) return 83; /*alloc fail*/ + + ++info->text_num; + info->text_keys[info->text_num - 1] = alloc_string(key); + info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); + if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ + + return 0; +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { + return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); +} + +void lodepng_clear_text(LodePNGInfo* info) { + LodePNGText_cleanup(info); +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) { + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->itext_num; ++i) { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->itext_keys = 0; + dest->itext_langtags = 0; + dest->itext_transkeys = 0; + dest->itext_strings = 0; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) { + LodePNGIText_cleanup(info); +} + +static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + + if(new_keys) info->itext_keys = new_keys; + if(new_langtags) info->itext_langtags = new_langtags; + if(new_transkeys) info->itext_transkeys = new_transkeys; + if(new_strings) info->itext_strings = new_strings; + + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ + + ++info->itext_num; + + info->itext_keys[info->itext_num - 1] = alloc_string(key); + info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); + info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); + info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); + + return 0; +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) { + return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); +} + +/* same as set but does not delete */ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(profile_size == 0) return 100; /*invalid ICC profile size*/ + + info->iccp_name = alloc_string(name); + info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); + + if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ + + lodepng_memcpy(info->iccp_profile, profile, profile_size); + info->iccp_profile_size = profile_size; + + return 0; /*ok*/ +} + +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; + + return lodepng_assign_icc(info, name, profile, profile_size); +} + +void lodepng_clear_icc(LodePNGInfo* info) { + string_cleanup(&info->iccp_name); + lodepng_free(info->iccp_profile); + info->iccp_profile = NULL; + info->iccp_profile_size = 0; + info->iccp_defined = 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) { + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + info->gama_defined = 0; + info->chrm_defined = 0; + info->srgb_defined = 0; + info->iccp_defined = 0; + info->iccp_name = NULL; + info->iccp_profile = NULL; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) { + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + lodepng_clear_icc(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + lodepng_info_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + if(source->iccp_defined) { + CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); + } + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree { + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) { + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) { + int i; + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int bit = 0; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") +Returns error code, or 0 if ok*/ +static unsigned color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { + int bit; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + if(!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) out[i] = gray; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if(mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if(mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if(mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if(mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_PALETTE) { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to the common case of RGBA with 8 bit per channel. buffer must be RGBA with +enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + unsigned num_channels = 4; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 3], 3); + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + const unsigned num_channels = 3; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if(mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if(lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if(error) break; + } + } + + if(!error) { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + } + + if(mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + + +/* Converts a single rgb color without alpha from one type to another, color bits truncated to +their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow +function, do not use to process all pixels of an image. Alpha channel not supported on purpose: +this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the +specification it looks like bKGD should ignore the alpha values of the palette since it can use +any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ +unsigned lodepng_convert_rgb( + unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { + unsigned r = 0, g = 0, b = 0; + unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ + unsigned shift = 16 - mode_out->bitdepth; + + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { + r = g = b = r_in * mul; + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { + r = r_in * mul; + g = g_in * mul; + b = b_in * mul; + } else if(mode_in->colortype == LCT_PALETTE) { + if(r_in >= mode_in->palettesize) return 82; + r = mode_in->palette[r_in * 4 + 0] * 257u; + g = mode_in->palette[r_in * 4 + 1] * 257u; + b = mode_in->palette[r_in * 4 + 2] * 257u; + } else { + return 31; + } + + /* now convert to output format */ + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { + *r_out = r >> shift ; + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { + *r_out = r >> shift ; + *g_out = g >> shift ; + *b_out = b >> shift ; + } else if(mode_out->colortype == LCT_PALETTE) { + unsigned i; + /* a 16-bit color cannot be in the palette */ + if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; + for(i = 0; i < mode_out->palettesize; i++) { + unsigned j = i * 4; + if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && + (b >> 8) == mode_out->palette[j + 2]) { + *r_out = i; + return 0; + } + } + return 82; + } else { + return 31; + } + + return 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_stats_init(LodePNGColorStats* stats) { + /*stats*/ + stats->colored = 0; + stats->key = 0; + stats->key_r = stats->key_g = stats->key_b = 0; + stats->alpha = 0; + stats->numcolors = 0; + stats->bits = 1; + stats->numpixels = 0; + /*settings*/ + stats->allow_palette = 1; + stats->allow_greyscale = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorStats(LodePNGColorStats* p) { + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) { + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*stats must already have been inited. */ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + /* mark things as done already if it would be impossible to have a more expensive case */ + unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode_in); + unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; + unsigned sixteen = 0; /* whether the input image is 16 bit */ + unsigned maxnumcolors = 257; + if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); + + stats->numpixels += numpixels; + + /*if palette not allowed, no need to compute numcolors*/ + if(!stats->allow_palette) numcolors_done = 1; + + color_tree_init(&tree); + + /*If the stats was already filled in from previous data, fill its palette in tree + and mark things as done already if we know they are the most expensive case already*/ + if(stats->alpha) alpha_done = 1; + if(stats->colored) colored_done = 1; + if(stats->bits == 16) numcolors_done = 1; + if(stats->bits >= bpp) bits_done = 1; + if(stats->numcolors >= maxnumcolors) numcolors_done = 1; + + if(!numcolors_done) { + for(i = 0; i < stats->numcolors; i++) { + const unsigned char* color = &stats->palette[i * 4]; + error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); + if(error) goto cleanup; + } + } + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode_in->bitdepth == 16 && !sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { + stats->bits = 16; + sixteen = 1; + bits_done = 1; + numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + break; + } + } + } + + if(sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 65535 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + } + } else /* < 16-bit */ { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + + if(!bits_done && stats->bits < 8) { + /*only r is checked, < 8 bits is only relevant for grayscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > stats->bits) stats->bits = bits; + } + bits_done = (stats->bits >= bpp); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 255 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 255 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { + error = color_tree_add(&tree, r, g, b, a, stats->numcolors); + if(error) goto cleanup; + if(stats->numcolors < 256) { + unsigned char* p = stats->palette; + unsigned n = stats->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++stats->numcolors; + numcolors_done = stats->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ + stats->key_r += (stats->key_r << 8); + stats->key_g += (stats->key_g << 8); + stats->key_b += (stats->key_b << 8); + } + +cleanup: + color_tree_cleanup(&tree); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit +(with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for +all pixels of an image but only for a few additional values. */ +static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, + unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned error = 0; + unsigned char image[8]; + LodePNGColorMode mode; + lodepng_color_mode_init(&mode); + image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; + image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; + mode.bitdepth = 16; + mode.colortype = LCT_RGBA; + error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); + lodepng_color_mode_cleanup(&mode); + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Computes a minimal PNG color model that can contain all colors as indicated by the stats. +The stats should be computed with lodepng_compute_color_stats. +mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. +Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, +e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... +This is used if auto_convert is enabled (it is by default). +*/ +static unsigned auto_choose_color(LodePNGColorMode* mode_out, + const LodePNGColorMode* mode_in, + const LodePNGColorStats* stats) { + unsigned error = 0; + unsigned palettebits; + size_t i, n; + size_t numpixels = stats->numpixels; + unsigned palette_ok, gray_ok; + + unsigned alpha = stats->alpha; + unsigned key = stats->key; + unsigned bits = stats->bits; + + mode_out->key_defined = 0; + + if(key && numpixels <= 16) { + alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + key = 0; + if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + + gray_ok = !stats->colored; + if(!stats->allow_greyscale) gray_ok = 0; + if(!gray_ok && bits < 8) bits = 8; + + n = stats->numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ + if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ + if(!stats->allow_palette) palette_ok = 0; + + if(palette_ok) { + const unsigned char* p = stats->palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != stats->numcolors; ++i) { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } else /*8-bit or 16-bit per channel*/ { + mode_out->bitdepth = bits; + mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) + : (gray_ok ? LCT_GREY : LCT_RGB); + if(key) { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ + mode_out->key_r = stats->key_r & mask; + mode_out->key_g = stats->key_g & mask; + mode_out->key_b = stats->key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predictor, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) { + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if(pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if(w) *w = width; + if(h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if(state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + if(!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) { + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1u); + } else { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1u); + } + break; + case 4: + if(precon) { + for(i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version would also be possible but that + adds too much code. Whether this actually speeds anything up at all depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for(; i != length; ++i) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for(i = bytewidth; i < length; ++i) { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /*error: invalid filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for(y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) { + size_t x; + for(x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) { + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ + lodepng_color_mode_alloc_palette(color); + if(!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + + for(i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned i; + if(color->colortype == LCT_PALETTE) { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 39; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if(color->colortype == LCT_GREY) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if(color->colortype == LCT_RGB) { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ + if(data[0] >= info->color.palettesize) return 103; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 44; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 6 bytes for grayscale image*/ + if(chunkLength != 6) return 45; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + char *key = 0, *str = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(str, data + string2_begin, length); + str[length] = 0; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + + unsigned length, string2_begin; + char *key = 0; + unsigned char* str = 0; + size_t size = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[string2_begin], + length, zlibsettings); + if(error) break; + error = lodepng_add_text_sized(info, key, (char*)str, size); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(langtag, data + begin, length); + langtag[length] = 0; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(transkey, data + begin, length); + transkey[length] = 0; + + /*read the actual text*/ + begin += length + 1; + + length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; + + if(compressed) { + unsigned char* str = 0; + size_t size = 0; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[begin], + length, zlibsettings); + if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); + lodepng_free(str); + } else { + error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); + } + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} + +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ + + info->gama_defined = 1; + info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + + return 0; /* OK */ +} + +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ + + info->chrm_defined = 1; + info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; + info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; + info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; + info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; + info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; + info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; + info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; + info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; + + return 0; /* OK */ +} + +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ + + info->srgb_defined = 1; + info->srgb_intent = data[0]; + + return 0; /* OK */ +} + +static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + size_t size = 0; + + unsigned length, string2_begin; + + info->iccp_defined = 1; + if(info->iccp_name) lodepng_clear_icc(info); + + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ + if(length < 1 || length > 79) return 89; /*keyword too short or long*/ + + info->iccp_name = (char*)lodepng_malloc(length + 1); + if(!info->iccp_name) return 83; /*alloc fail*/ + + info->iccp_name[length] = 0; + for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; + + if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + error = zlib_decompress(&info->iccp_profile, &size, 0, + &data[string2_begin], + length, zlibsettings); + info->iccp_profile_size = size; + if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize) { + const unsigned char* chunk = in + pos; + unsigned chunkLength; + const unsigned char* data; + unsigned unhandled = 0; + unsigned error = 0; + + if(pos + 4 > insize) return 30; + chunkLength = lodepng_chunk_length(chunk); + if(chunkLength > 2147483647) return 63; + data = lodepng_chunk_data_const(chunk); + if(data + chunkLength + 4 > in + insize) return 30; + + if(lodepng_chunk_type_equals(chunk, "PLTE")) { + error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + error = readChunk_tRNS(&state->info_png.color, data, chunkLength); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + error = readChunk_bKGD(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + error = readChunk_tEXt(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + error = readChunk_tIME(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + error = readChunk_pHYs(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + error = readChunk_gAMA(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + error = readChunk_cHRM(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + error = readChunk_sRGB(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + error = readChunk_iCCP(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else { + /* unhandled chunk is ok (is not an error) */ + unhandled = 1; + } + + if(!error && !unhandled && !state->decoder.ignore_crc) { + if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned char IEND = 0; + const unsigned char* chunk; + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)lodepng_malloc(insize); + if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if(newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + state->error = readChunk_gAMA(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + state->error = readChunk_cHRM(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + state->error = readChunk_sRGB(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + state->error = readChunk_iCCP(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if(state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if(!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + lodepng_free(idat); + + if(!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + lodepng_free(scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) { +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) { + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +static unsigned writeSignature(ucvector* out) { + size_t pos = out->size; + const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; + /*8 bytes PNG signature, aka the magic bytes*/ + if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ + lodepng_memcpy(out->data + pos, signature, 8); + return 0; +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { + unsigned char *chunk, *data; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); + data = chunk + 8; + + lodepng_set32bitInt(data + 0, w); /*width*/ + lodepng_set32bitInt(data + 4, h); /*height*/ + data[8] = (unsigned char)bitdepth; /*bit depth*/ + data[9] = (unsigned char)colortype; /*color type*/ + data[10] = 0; /*compression method*/ + data[11] = 0; /*filter method*/ + data[12] = interlace_method; /*interlace method*/ + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +/* only adds the chunk if needed (there is a key or palette with alpha) */ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk; + size_t i, j = 8; + + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); + + for(i = 0; i != info->palettesize; ++i) { + /*add all channels except alpha channel*/ + chunk[j++] = info->palette[i * 4 + 0]; + chunk[j++] = info->palette[i * 4 + 1]; + chunk[j++] = info->palette[i * 4 + 2]; + } + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk = 0; + + if(info->colortype == LCT_PALETTE) { + size_t i, amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) { + if(info->palette[4 * (i - 1) + 3] != 255) break; + --amount; + } + if(amount) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); + /*add the alpha channel values from the palette*/ + for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; + } + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + } + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + chunk[10] = (unsigned char)(info->key_g >> 8); + chunk[11] = (unsigned char)(info->key_g & 255); + chunk[12] = (unsigned char)(info->key_b >> 8); + chunk[13] = (unsigned char)(info->key_b & 255); + } + } + + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* zlib = 0; + size_t zlibsize = 0; + + error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); + if(!error) { + error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); + } + lodepng_free(zlib); + return error; +} + +static unsigned addChunk_IEND(ucvector* out) { + return lodepng_chunk_createv(out, 0, "IEND", 0); +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { + unsigned char* chunk = 0; + size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); + size_t size = keysize + 1 + textsize; + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword); + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "zTXt"); + } + if(!error) { + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + if(compress) { + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + } + if(!error) { + size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); + error = lodepng_chunk_init(&chunk, out, size, "iTXt"); + } + if(!error) { + size_t pos = 8; + lodepng_memcpy(chunk + pos, keyword, keysize); + pos += keysize; + chunk[pos++] = 0; /*null termination char*/ + chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ + chunk[pos++] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + pos, langtag, langsize); + pos += langsize; + chunk[pos++] = 0; /*null termination char*/ + lodepng_memcpy(chunk + pos, transkey, transsize); + pos += transsize; + chunk[pos++] = 0; /*null termination char*/ + if(compress) { + lodepng_memcpy(chunk + pos, compressed, compressedsize); + } else { + lodepng_memcpy(chunk + pos, textstring, textsize); + } + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + chunk[10] = (unsigned char)(info->background_g >> 8); + chunk[11] = (unsigned char)(info->background_g & 255); + chunk[12] = (unsigned char)(info->background_b >> 8); + chunk[13] = (unsigned char)(info->background_b & 255); + } else if(info->color.colortype == LCT_PALETTE) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); + chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); + chunk[8] = (unsigned char)(time->year >> 8); + chunk[9] = (unsigned char)(time->year & 255); + chunk[10] = (unsigned char)time->month; + chunk[11] = (unsigned char)time->day; + chunk[12] = (unsigned char)time->hour; + chunk[13] = (unsigned char)time->minute; + chunk[14] = (unsigned char)time->second; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); + lodepng_set32bitInt(chunk + 8, info->phys_x); + lodepng_set32bitInt(chunk + 12, info->phys_y); + chunk[16] = info->phys_unit; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); + lodepng_set32bitInt(chunk + 8, info->gama_gamma); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); + lodepng_set32bitInt(chunk + 8, info->chrm_white_x); + lodepng_set32bitInt(chunk + 12, info->chrm_white_y); + lodepng_set32bitInt(chunk + 16, info->chrm_red_x); + lodepng_set32bitInt(chunk + 20, info->chrm_red_y); + lodepng_set32bitInt(chunk + 24, info->chrm_green_x); + lodepng_set32bitInt(chunk + 28, info->chrm_green_y); + lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); + lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { + unsigned char data = info->srgb_intent; + return lodepng_chunk_createv(out, 1, "sRGB", &data); +} + +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t keysize = lodepng_strlen(info->iccp_name); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + error = zlib_compress(&compressed, &compressedsize, + info->iccp_profile, info->iccp_profile_size, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "iCCP"); + } + if(!error) { + lodepng_memcpy(chunk + 8, info->iccp_name, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) { + size_t i; + switch(filterType) { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } else { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*invalid filter type given*/ + } +} + +/* integer binary logarithm, max return value is 31 */ +static size_t ilog2(size_t i) { + size_t result = 0; + if(i >= 65536) { result += 16; i >>= 16; } + if(i >= 256) { result += 8; i >>= 8; } + if(i >= 16) { result += 4; i >>= 4; } + if(i >= 4) { result += 2; i >>= 2; } + if(i >= 2) { result += 1; /*i >>= 1;*/ } + return result; +} + +/* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ +static size_t ilog2i(size_t i) { + size_t l; + if(i == 0) return 0; + l = ilog2(i); + /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) + linearly approximates the missing fractional part multiplied by i */ + return i * l + ((i - (1u << l)) << 1u); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(color); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { + unsigned char type = (unsigned char)strategy; + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_MINSUM) { + /*adaptive filtering*/ + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + if(type == 0) { + for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); + } else { + for(x = 0; x != linebytes; ++x) { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum < smallest) { + bestType = type; + smallest = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_ENTROPY) { + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t bestSum = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + lodepng_memset(count, 0, 256 * sizeof(*count)); + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + for(x = 0; x != 256; ++x) { + sum += ilog2i(count[x]); + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum > bestSum) { + bestType = type; + bestSum = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_BRUTE_FORCE) { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings; + lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + if(!error) { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { + unsigned testsize = (unsigned)linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) { + size_t x; + for(x = 0; x < ilinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) { + *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) { + addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } else { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) { + if(bpp < 8) { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } else { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk, data + datasize); + } + return 0; +} + +static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { + /* + It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 + are "RGB ". We do not perform any full parsing of the ICC profile here, other + than check those 4 bytes to grayscale profile. Other than that, validity of + the profile is not checked. This is needed only because the PNG specification + requires using a non-gray color model if there is an ICC profile with "RGB " + (sadly limiting compression opportunities if the input data is grayscale RGB + data), and requires using a gray color model if it is "GRAY". + */ + if(size < 20) return 0; + return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; +} + +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { + /* See comment in isGrayICCProfile*/ + if(size < 20) return 0; + return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) { + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + ucvector outv = ucvector_init(NULL, 0); + LodePNGInfo info; + const LodePNGInfo* info_png = &state->info_png; + + lodepng_info_init(&info); + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + goto cleanup; + } + if(state->encoder.zlibsettings.btype > 2) { + state->error = 61; /*error: invalid btype*/ + goto cleanup; + } + if(info_png->interlace_method > 1) { + state->error = 71; /*error: invalid interlace mode*/ + goto cleanup; + } + state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) { + LodePNGColorStats stats; + lodepng_color_stats_init(&stats); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined && + isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use palette with a GRAY ICC profile, even + if the palette has only gray colors, so disallow it.*/ + stats.allow_palette = 0; + } + if(info_png->iccp_defined && + isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ + stats.allow_greyscale = 0; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->background_defined) { + /*the background chunk's color must be taken into account as well*/ + unsigned r = 0, g = 0, b = 0; + LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); + lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); + state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); + if(state->error) goto cleanup; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = auto_choose_color(&info.color, &state->info_raw, &stats); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*also convert the background chunk*/ + if(info_png->background_defined) { + if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, + info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { + state->error = 104; + goto cleanup; + } + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined) { + unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; + if(!gray_icc && !rgb_icc) { + state->error = 100; /* Disallowed profile color type for PNG */ + goto cleanup; + } + if(gray_icc != gray_png) { + /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, + or in case of auto_convert, it wasn't possible to find appropriate model*/ + state->error = state->encoder.auto_convert ? 102 : 101; + goto cleanup; + } + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { + unsigned char* converted; + size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + if(state->error) goto cleanup; + } else { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + if(state->error) goto cleanup; + } + + /* output all PNG chunks */ { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + state->error = writeSignature(&outv); + if(state->error) goto cleanup; + /*IHDR*/ + state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) goto cleanup; + } + /*color profile chunks must come before PLTE */ + if(info.iccp_defined) { + state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + if(info.srgb_defined) { + state->error = addChunk_sRGB(&outv, &info); + if(state->error) goto cleanup; + } + if(info.gama_defined) { + state->error = addChunk_gAMA(&outv, &info); + if(state->error) goto cleanup; + } + if(info.chrm_defined) { + state->error = addChunk_cHRM(&outv, &info); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) { + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { + /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + /*tRNS (this will only add if when necessary) */ + state->error = addChunk_tRNS(&outv, &info.color); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) { + state->error = addChunk_bKGD(&outv, &info); + if(state->error) goto cleanup; + } + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) { + state->error = addChunk_pHYs(&outv, &info); + if(state->error) goto cleanup; + } + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) { + state->error = addChunk_tIME(&outv, &info.time); + if(state->error) goto cleanup; + } + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) { + if(lodepng_strlen(info.text_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.text_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + if(state->encoder.text_compression) { + state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } else { + state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + if(state->error) goto cleanup; + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) { + unsigned already_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) { + const char* k = info.text_keys[i]; + /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ + if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && + k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { + already_added_id_text = 1; + break; + } + } + if(already_added_id_text == 0) { + state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + if(state->error) goto cleanup; + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) { + if(lodepng_strlen(info.itext_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.itext_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + state->error = addChunk_iTXt( + &outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + state->error = addChunk_IEND(&outv); + if(state->error) goto cleanup; + } + +cleanup: + lodepng_info_cleanup(&info); + lodepng_free(data); + + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) { + switch(code) { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ + case 16: return "invalid code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ + case 39: return "tRNS chunk before PLTE or has more entries than palette size"; + case 40: return "tRNS chunk has wrong size for grayscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for grayscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ + case 62: return "conversion from color to grayscale not supported"; + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "integer overflow due to too many pixels"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + case 95: return "integer overflow with combined idat chunk size"; + case 96: return "invalid gAMA chunk size"; + case 97: return "invalid cHRM chunk size"; + case 98: return "invalid sRGB chunk size"; + case 99: return "invalid sRGB rendering intent"; + case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; + case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; + case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; + case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; + case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; + case 105: return "integer overflow of bitsize"; + case 106: return "PNG file must have PLTE chunk if color type is palette"; + case 107: return "color convert from palette mode requested without setting the palette data in it"; + case 108: return "tried to add more than 256 values to a palette"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector& buffer, const std::string& filename) { + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector& buffer, const std::string& filename) { + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() { + lodepng_state_init(this); +} + +State::State(const State& other) { + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() { + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) { + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) { + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + /* safe output values in case error happens */ + w = h = 0; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) { + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/src/lodepng/lodepng.h b/src/lodepng/lodepng.h new file mode 100644 index 0000000..a386459 --- /dev/null +++ b/src/lodepng/lodepng.h @@ -0,0 +1,1945 @@ +/* +LodePNG version 20200306 + +Copyright (c) 2005-2020 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to +allow implementing a custom lodepng_crc32. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif + +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif + +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif + +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif + +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif + +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif + +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif + +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif + +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw image).*/ +typedef enum LodePNGColorType { + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings { + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*use custom zlib decoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate)*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ { + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode { + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); +/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a grayscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime { + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo { + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + Suggested background color chunk (bKGD) + + This uses the same color mode and bit depth as the PNG (except no alpha channel), + with values truncated to the bit depth in the unsigned integer. + + For grayscale and palette PNGs, the value is stored in background_r. The values + in background_g and background_b are then unused. + + So when decoding, you may get these in a different color mode than the one you requested + for the raw pixels. + + When encoding with auto_convert, you must use the color model defined in info_png.color for + these values. The encoder normally ignores info_png.color when auto_convert is on, but will + use it to interpret these values (and convert copies of them to its chosen color model). + + When encoding, avoid setting this to an expensive color, such as a non-gray value + when the image is gray, or the compression will be worse since it will be forced to + write the PNG with a more expensive color mode (when auto_convert is on). + + The decoder does not use this background color to edit the color of pixels. This is a + completely optional metadata feature. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red/gray/palette component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + All the string fields below including keys, names and language tags are null terminated. + The PNG specification uses null characters for the keys, names and tags, and forbids null + characters to appear in the main text which is why we can use null termination everywhere here. + + A keyword is minimum 1 character and maximum 79 characters long. It's + discouraged to use a single line length longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + international text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys". + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + Color profile related chunks: gAMA, cHRM, sRGB, iCPP + + LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color + profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please + use these values with a color management library. + + See the PNG, ICC and sRGB specifications for more information about the meaning of these values. + */ + + /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ + unsigned gama_gamma; /* Gamma exponent times 100000 */ + + /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ + unsigned chrm_white_x; /* White Point x times 100000 */ + unsigned chrm_white_y; /* White Point y times 100000 */ + unsigned chrm_red_x; /* Red x times 100000 */ + unsigned chrm_red_y; /* Red y times 100000 */ + unsigned chrm_green_x; /* Green x times 100000 */ + unsigned chrm_green_y; /* Green y times 100000 */ + unsigned chrm_blue_x; /* Blue x times 100000 */ + unsigned chrm_blue_y; /* Blue y times 100000 */ + + /* + sRGB chunk: optional. May not appear at the same time as iCCP. + If gAMA is also present gAMA must contain value 45455. + If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. + */ + unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ + unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ + + /* + iCCP chunk: optional. May not appear at the same time as sRGB. + + LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a + separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color + management and conversions. + + For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC + profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and + enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. + + For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray + PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure + the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is + enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder + error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel + data if the pixels could be encoded as grayscale but the ICC profile is RGB. + + To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so + make sure you compute it carefully to avoid the above problems. + */ + unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ + char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ + /* + The ICC profile in iccp_profile_size bytes. + Don't allocate this buffer yourself. Use the init/cleanup functions + correctly and use lodepng_set_icc and lodepng_clear_icc. + */ + unsigned char* iccp_profile; + unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ + + /* End of color profile related chunks */ + + + /* + unknown chunks: chunks not known by LodePNG, passed on byte for byte. + + There are 3 buffers, one for each position in the PNG where unknown chunks can appear. + Each buffer contains all unknown chunks for that position consecutively. + The 3 positions are: + 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. + + For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag + above in here, since the encoder will blindly follow this and could then encode an invalid PNG file + (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use + this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), + or any non-standard PNG chunk. + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ + +/*replaces if exists*/ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); +void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings { + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy { + /*every filter at zero*/ + LFS_ZERO = 0, + /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ + LFS_ONE = 1, + LFS_TWO = 2, + LFS_THREE = 3, + LFS_FOUR = 4, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), +which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorStats { + unsigned colored; /*not grayscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ + size_t numpixels; + + /*user settings for computing/using the stats*/ + unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ + unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ +} LodePNGColorStats; + +void lodepng_color_stats_init(LodePNGColorStats* stats); + +/*Get a LodePNGColorStats of the image. The stats must already have been inited. +Returns error code (e.g. alloc fail) or 0 if ok.*/ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings { + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState { +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the IHDR chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* +Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it +read in the state. Returns error code on failure. +Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const +to find the desired chunk type, and if non null use lodepng_inspect_chunk (with +chunk_pointer - start_of_file as pos). +Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). +Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). +Requirements: &in[pos] must point to start of a chunk, must use regular +lodepng_inspect first since format of most other chunks depends on IHDR, and if +there is a PLTE chunk, that one must be inspected before tRNS or bKGD. +*/ +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +The chunk pointer always points to the beginning of the chunk itself, that is +the first byte of the 4 length bytes. + +In the PNG file format, chunks have the following format: +-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) +-4 bytes chunk type (ASCII a-z,A-Z only, see below) +-length bytes of data (may be 0 bytes if length was 0) +-4 bytes of CRC, computed on chunk name + data + +The first chunk starts at the 8th byte of the PNG file, the entire rest of the file +exists out of concatenated chunks with the above format. + +PNG standard chunk ASCII naming conventions: +-First byte: uppercase = critical, lowercase = ancillary +-Second byte: uppercase = public, lowercase = private +-Third byte: must be uppercase +-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/* +Iterate to next chunks, allows iterating through all chunks of the PNG file. +Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, +or the 8th byte of a PNG file which always has the first chunk), or alternatively may +point to the first byte of the PNG file (which is not a chunk but the magic header, the +function will then skip over it and return the first real chunk). +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. +In a non-corrupt PNG file, the last chunk should have name "IEND". +*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); + +/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng { +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState { + public: + State(); + State(const State& other); + ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) +*/ +unsigned load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. +*/ +unsigned save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[X] support color profile chunk types (but never let them touch RGB values by default) +[ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +[X] provide alternatives for C library functions not present on some platforms (memcpy, ...) +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG: + sBIT + hIST + sPLT + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to grayscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, grayscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to grayscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: grayscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: grayscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +Non supported color conversions: +-color to grayscale when non-gray pixels are present: no error is thrown, but +the result will look ugly because only the red channel is taken (it assumes all +three channels are the same in this case so ignores green and blue). The reason +no error is given is to allow converting from three-channel grayscale images to +one-channel even if there are numerical imprecisions. +-anything to palette when the palette does not have an exact match for a from-color +in it: in this case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any gray or gray+alpha, to gray or gray+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outsize. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distinction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards compliant. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) { + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) { + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +Not all changes are listed here, the commit history in github lists more: +https://github.com/lvandeve/lodepng + +*) 06 mar 2020: simplified some of the dynamic memory allocations. +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. +*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. +*) 10 sep 2018: added way to inspect metadata chunks without full decoding. +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use + palette index in case of palette. +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This + change is backwards compatible unless you relied on unknown_chunks for those. +*) 11 jun 2018: less restrictive check for pixel size integer overflow +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 24 aug 2014: Moved to github +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011: (!) changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2020 Lode Vandevenne +*/ diff --git a/src/matfx.cpp b/src/matfx.cpp new file mode 100644 index 0000000..3227d09 --- /dev/null +++ b/src/matfx.cpp @@ -0,0 +1,637 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" +#include "ps2/rwps2.h" +#include "ps2/rwps2plg.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwwdgl.h" +#include "gl/rwgl3.h" +#include "gl/rwgl3plg.h" + +#define PLUGIN_ID ID_MATFX + +namespace rw { + +bool32 MatFX::modulateEnvMap; + +// Atomic + +static void* +createAtomicMatFX(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(int32, object, offset) = 0; + return object; +} + +static void* +copyAtomicMatFX(void *dst, void *src, int32 offset, int32) +{ + // don't call seteffects, it will override the pipeline + if(*PLUGINOFFSET(int32, src, offset)) + *PLUGINOFFSET(int32, dst, offset) = 1; + return dst; +} + +static Stream* +readAtomicMatFX(Stream *stream, int32, void *object, int32, int32) +{ + if(stream->readI32()) + MatFX::enableEffects((Atomic*)object); + return stream; +} + +static Stream* +writeAtomicMatFX(Stream *stream, int32, void *object, int32 offset, int32) +{ + stream->writeI32(*PLUGINOFFSET(int32, object, offset)); + return stream; +} + +static int32 +getSizeAtomicMatFX(void *object, int32 offset, int32) +{ + int32 flag = *PLUGINOFFSET(int32, object, offset); + // TODO: not sure which version + return flag || rw::version < 0x34000 ? 4 : 0; +} + +// Material + +MatFXGlobals matFXGlobals = { 0, 0, { nil } }; + +// TODO: Frames and Matrices? +static void +clearMatFX(MatFX *matfx) +{ + for(int i = 0; i < 2; i++) + switch(matfx->fx[i].type){ + case MatFX::BUMPMAP: + if(matfx->fx[i].bump.bumpedTex) + matfx->fx[i].bump.bumpedTex->destroy(); + if(matfx->fx[i].bump.tex) + matfx->fx[i].bump.tex->destroy(); + break; + + case MatFX::ENVMAP: + if(matfx->fx[i].env.tex) + matfx->fx[i].env.tex->destroy(); + break; + + case MatFX::DUAL: + if(matfx->fx[i].dual.tex) + matfx->fx[i].dual.tex->destroy(); + break; + } + memset(matfx, 0, sizeof(MatFX)); +} + +void +MatFX::setEffects(Material *mat, uint32 type) +{ + MatFX *matfx; + + matfx = MatFX::get(mat); + if(matfx == nil){ + matfx = rwNewT(MatFX, 1, MEMDUR_EVENT | ID_MATFX); + memset(matfx, 0, sizeof(MatFX)); + *PLUGINOFFSET(MatFX*, mat, matFXGlobals.materialOffset) = matfx; + } + + if(matfx->type != 0 && matfx->type != type) + clearMatFX(matfx); + matfx->type = type; + switch(type){ + case BUMPMAP: + case ENVMAP: + case DUAL: + case UVTRANSFORM: + matfx->fx[0].type = type; + matfx->fx[1].type = NOTHING; + break; + + case BUMPENVMAP: + matfx->fx[0].type = BUMPMAP; + matfx->fx[1].type = ENVMAP; + break; + + case DUALUVTRANSFORM: + matfx->fx[0].type = UVTRANSFORM; + matfx->fx[1].type = DUAL; + break; + } +} + +uint32 +MatFX::getEffects(const Material *m) +{ + MatFX *fx = *PLUGINOFFSET(MatFX*, m, matFXGlobals.materialOffset); + if(fx) + return fx->type; + return 0; +} + +MatFX* +MatFX::get(const Material *m) +{ + return *PLUGINOFFSET(MatFX*, m, matFXGlobals.materialOffset); +} + +int32 +MatFX::getEffectIndex(uint32 type) +{ + for(int i = 0; i < 2; i++) + if(this->fx[i].type == type) + return i; + return -1; +} + +void +MatFX::setBumpTexture(Texture *t) +{ + int32 i = this->getEffectIndex(BUMPMAP); + if(i >= 0){ + if(this->fx[i].bump.tex) + this->fx[i].bump.tex->destroy(); + this->fx[i].bump.tex = t; + if(t) + t->addRef(); + } +} + +void +MatFX::setBumpCoefficient(float32 coef) +{ + int32 i = this->getEffectIndex(BUMPMAP); + if(i >= 0) + this->fx[i].bump.coefficient = coef; +} + +Texture* +MatFX::getBumpTexture(void) +{ + int32 i = this->getEffectIndex(BUMPMAP); + if(i >= 0) + return this->fx[i].bump.tex; + return nil; +} + +float32 +MatFX::getBumpCoefficient(void) +{ + int32 i = this->getEffectIndex(BUMPMAP); + if(i >= 0) + return this->fx[i].bump.coefficient; + return 0.0f; +} + +void +MatFX::setEnvTexture(Texture *t) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0){ + if(this->fx[i].env.tex) + this->fx[i].env.tex->destroy(); + this->fx[i].env.tex = t; + if(t) + t->addRef(); + } +} + +void +MatFX::setEnvFrame(Frame *f) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + this->fx[i].env.frame = f; +} + +void +MatFX::setEnvCoefficient(float32 coef) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + this->fx[i].env.coefficient = coef; +} + +void +MatFX::setEnvFBAlpha(bool32 useFBAlpha) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + this->fx[i].env.fbAlpha = useFBAlpha; +} + +Texture* +MatFX::getEnvTexture(void) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + return this->fx[i].env.tex; + return nil; +} + +Frame* +MatFX::getEnvFrame(void) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + return this->fx[i].env.frame; + return nil; +} + +float32 +MatFX::getEnvCoefficient(void) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + return this->fx[i].env.coefficient; + return 0.0f; +} + +bool32 +MatFX::getEnvFBAlpha(void) +{ + int32 i = this->getEffectIndex(ENVMAP); + if(i >= 0) + return this->fx[i].env.fbAlpha; + return 0; +} + + +void +MatFX::setDualTexture(Texture *t) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0){ + if(this->fx[i].dual.tex) + this->fx[i].dual.tex->destroy(); + this->fx[i].dual.tex = t; + if(t) + t->addRef(); + } +} + +void +MatFX::setDualSrcBlend(int32 blend) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + this->fx[i].dual.srcBlend = blend; +} + +void +MatFX::setDualDestBlend(int32 blend) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + this->fx[i].dual.dstBlend = blend; +} + +Texture* +MatFX::getDualTexture(void) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + return this->fx[i].dual.tex; + return nil; +} + +int32 +MatFX::getDualSrcBlend(void) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + return this->fx[i].dual.srcBlend; + return 0; +} + +int32 +MatFX::getDualDestBlend(void) +{ + int32 i = this->getEffectIndex(DUAL); + if(i >= 0) + return this->fx[i].dual.dstBlend; + return 0; +} + +void +MatFX::setUVTransformMatrices(Matrix *base, Matrix *dual) +{ + int32 i = this->getEffectIndex(UVTRANSFORM); + if(i >= 0){ + this->fx[i].uvtransform.baseTransform = base; + this->fx[i].uvtransform.dualTransform = dual; + } +} + +void +MatFX::getUVTransformMatrices(Matrix **base, Matrix **dual) +{ + int32 i = this->getEffectIndex(UVTRANSFORM); + if(i >= 0){ + if(base) *base = this->fx[i].uvtransform.baseTransform; + if(dual) *dual = this->fx[i].uvtransform.dualTransform; + return; + } + if(base) *base = nil; + if(dual) *dual = nil; +} + +static void* +createMaterialMatFX(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(MatFX*, object, offset) = nil; + return object; +} + +static void* +destroyMaterialMatFX(void *object, int32 offset, int32) +{ + MatFX *matfx = *PLUGINOFFSET(MatFX*, object, offset); + if(matfx){ + clearMatFX(matfx); + rwFree(matfx); + } + return object; +} + +static void* +copyMaterialMatFX(void *dst, void *src, int32 offset, int32) +{ + MatFX *srcfx = *PLUGINOFFSET(MatFX*, src, offset); + if(srcfx == nil) + return dst; + MatFX *dstfx = rwNewT(MatFX, 1, MEMDUR_EVENT | ID_MATFX); + *PLUGINOFFSET(MatFX*, dst, offset) = dstfx; + memcpy(dstfx, srcfx, sizeof(MatFX)); + for(int i = 0; i < 2; i++) + switch(dstfx->fx[i].type){ + case MatFX::BUMPMAP: + if(dstfx->fx[i].bump.bumpedTex) + dstfx->fx[i].bump.bumpedTex->addRef(); + if(dstfx->fx[i].bump.tex) + dstfx->fx[i].bump.tex->addRef(); + break; + + case MatFX::ENVMAP: + if(dstfx->fx[i].env.tex) + dstfx->fx[i].env.tex->addRef(); + break; + + case MatFX::DUAL: + if(dstfx->fx[i].dual.tex) + dstfx->fx[i].dual.tex->addRef(); + break; + } + return dst; +} + +static Stream* +readMaterialMatFX(Stream *stream, int32, void *object, int32 offset, int32) +{ + Material *mat; + MatFX *matfx; + Texture *tex, *bumpedTex; + float coefficient; + int32 fbAlpha; + int32 srcBlend, dstBlend; + int32 idx; + + mat = (Material*)object; + MatFX::setEffects(mat, stream->readU32()); + matfx = MatFX::get(mat); + + for(int i = 0; i < 2; i++){ + uint32 type = stream->readU32(); + switch(type){ + case MatFX::BUMPMAP: + coefficient = stream->readF32(); + bumpedTex = tex = nil; + if(stream->readI32()){ + if(!findChunk(stream, ID_TEXTURE, + nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + return nil; + } + bumpedTex = Texture::streamRead(stream); + } + if(stream->readI32()){ + if(!findChunk(stream, ID_TEXTURE, + nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + return nil; + } + tex = Texture::streamRead(stream); + } + idx = matfx->getEffectIndex(type); + assert(idx >= 0); + matfx->fx[idx].bump.bumpedTex = bumpedTex; + matfx->fx[idx].bump.tex = tex; + matfx->fx[idx].bump.coefficient = coefficient; + break; + + case MatFX::ENVMAP: + coefficient = stream->readF32(); + fbAlpha = stream->readI32(); + tex = nil; + if(stream->readI32()){ + if(!findChunk(stream, ID_TEXTURE, + nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + return nil; + } + tex = Texture::streamRead(stream); + } + idx = matfx->getEffectIndex(type); + assert(idx >= 0); + matfx->fx[idx].env.tex = tex; + matfx->fx[idx].env.fbAlpha = fbAlpha; + matfx->fx[idx].env.coefficient = coefficient; + break; + + case MatFX::DUAL: + srcBlend = stream->readI32(); + dstBlend = stream->readI32(); + tex = nil; + if(stream->readI32()){ + if(!findChunk(stream, ID_TEXTURE, + nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURE")); + return nil; + } + tex = Texture::streamRead(stream); + } + idx = matfx->getEffectIndex(type); + assert(idx >= 0); + matfx->fx[idx].dual.tex = tex; + matfx->fx[idx].dual.srcBlend = srcBlend; + matfx->fx[idx].dual.dstBlend = dstBlend; + break; + } + } + return stream; +} + +static Stream* +writeMaterialMatFX(Stream *stream, int32, void *object, int32 offset, int32) +{ + MatFX *matfx = *PLUGINOFFSET(MatFX*, object, offset); + + stream->writeU32(matfx->type); + for(int i = 0; i < 2; i++){ + stream->writeU32(matfx->fx[i].type); + switch(matfx->fx[i].type){ + case MatFX::BUMPMAP: + stream->writeF32(matfx->fx[i].bump.coefficient); + stream->writeI32(matfx->fx[i].bump.bumpedTex != nil); + if(matfx->fx[i].bump.bumpedTex) + matfx->fx[i].bump.bumpedTex->streamWrite(stream); + stream->writeI32(matfx->fx[i].bump.tex != nil); + if(matfx->fx[i].bump.tex) + matfx->fx[i].bump.tex->streamWrite(stream); + break; + + case MatFX::ENVMAP: + stream->writeF32(matfx->fx[i].env.coefficient); + stream->writeI32(matfx->fx[i].env.fbAlpha); + stream->writeI32(matfx->fx[i].env.tex != nil); + if(matfx->fx[i].env.tex) + matfx->fx[i].env.tex->streamWrite(stream); + break; + + case MatFX::DUAL: + stream->writeI32(matfx->fx[i].dual.srcBlend); + stream->writeI32(matfx->fx[i].dual.dstBlend); + stream->writeI32(matfx->fx[i].dual.tex != nil); + if(matfx->fx[i].dual.tex) + matfx->fx[i].dual.tex->streamWrite(stream); + break; + } + } + return stream; +} + +static int32 +getSizeMaterialMatFX(void *object, int32 offset, int32) +{ + MatFX *matfx = *PLUGINOFFSET(MatFX*, object, offset); + if(matfx == nil) + return -1; + int32 size = 4 + 4 + 4; + + for(int i = 0; i < 2; i++){ + switch(matfx->fx[i].type){ + case MatFX::BUMPMAP: + size += 4 + 4 + 4; + if(matfx->fx[i].bump.bumpedTex) + size += 12 + + matfx->fx[i].bump.bumpedTex->streamGetSize(); + if(matfx->fx[i].bump.tex) + size += 12 + + matfx->fx[i].bump.tex->streamGetSize(); + break; + + case MatFX::ENVMAP: + size += 4 + 4 + 4; + if(matfx->fx[i].env.tex) + size += 12 + + matfx->fx[i].env.tex->streamGetSize(); + break; + + case MatFX::DUAL: + size += 4 + 4 + 4; + if(matfx->fx[i].dual.tex) + size += 12 + + matfx->fx[i].dual.tex->streamGetSize(); + break; + } + } + return size; +} + +void +MatFX::enableEffects(Atomic *atomic) +{ + *PLUGINOFFSET(int32, atomic, matFXGlobals.atomicOffset) = 1; + atomic->pipeline = matFXGlobals.pipelines[rw::platform]; +} + +// This prevents setting the pipeline on clone +void +MatFX::disableEffects(Atomic *atomic) +{ + *PLUGINOFFSET(int32, atomic, matFXGlobals.atomicOffset) = 0; +} + +bool32 +MatFX::getEffects(Atomic *atomic) +{ + return *PLUGINOFFSET(int32, atomic, matFXGlobals.atomicOffset); +} + +static void* +matfxOpen(void *o, int32, int32) +{ + // init dummy pipelines + matFXGlobals.dummypipe = ObjPipeline::create(); + matFXGlobals.dummypipe->pluginID = 0; //ID_MATFX; + matFXGlobals.dummypipe->pluginData = 0; + for(uint i = 0; i < nelem(matFXGlobals.pipelines); i++) + matFXGlobals.pipelines[i] = matFXGlobals.dummypipe; + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + for(uint i = 0; i < nelem(matFXGlobals.pipelines); i++) + if(matFXGlobals.pipelines[i] == matFXGlobals.dummypipe) + matFXGlobals.pipelines[i] = nil; + matFXGlobals.dummypipe->destroy(); + matFXGlobals.dummypipe = nil; + return o; +} + +void +registerMatFXPlugin(void) +{ + Driver::registerPlugin(PLATFORM_NULL, 0, ID_MATFX, + matfxOpen, matfxClose); + ps2::initMatFX(); + xbox::initMatFX(); + d3d8::initMatFX(); + d3d9::initMatFX(); + wdgl::initMatFX(); + gl3::initMatFX(); + + matFXGlobals.atomicOffset = + Atomic::registerPlugin(sizeof(int32), ID_MATFX, + createAtomicMatFX, nil, copyAtomicMatFX); + Atomic::registerPluginStream(ID_MATFX, + readAtomicMatFX, + writeAtomicMatFX, + getSizeAtomicMatFX); + + matFXGlobals.materialOffset = + Material::registerPlugin(sizeof(MatFX*), ID_MATFX, + createMaterialMatFX, destroyMaterialMatFX, + copyMaterialMatFX); + Material::registerPluginStream(ID_MATFX, + readMaterialMatFX, + writeMaterialMatFX, + getSizeMaterialMatFX); +} + +} diff --git a/src/pipeline.cpp b/src/pipeline.cpp new file mode 100644 index 0000000..6284f68 --- /dev/null +++ b/src/pipeline.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define COLOR_ARGB(a,r,g,b) \ + ((uint32)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff))) + +namespace rw { + +static void nothing(ObjPipeline *, Atomic*) {} + +void +ObjPipeline::init(uint32 platform) +{ + Pipeline::init(platform); + this->impl.instance = nothing; + this->impl.uninstance = nothing; + this->impl.render = nothing; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(PLATFORM_NULL); + return pipe; +} + +void +ObjPipeline::destroy(void) +{ + rwFree(this); +} + +// helper functions + +void +findMinVertAndNumVertices(uint16 *indices, uint32 numIndices, uint32 *minVert, int32 *numVertices) +{ + uint32 min = 0xFFFFFFFF; + uint32 max = 0; + while(numIndices--){ + if(*indices < min) + min = *indices; + if(*indices > max) + max = *indices; + indices++; + } + uint32 num = max - min + 1; + // if mesh is empty, this can happen + if(min > max){ + min = 0; + num = 0; + } + if(minVert) + *minVert = min; + if(numVertices) + *numVertices = num; +} + +void +instV4d(int type, uint8 *dst, V4d *src, uint32 numVertices, uint32 stride) +{ + if(type == VERT_FLOAT4) + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 16); + dst += stride; + src++; + } + else + assert(0 && "unsupported instV4d type"); +} + +void +instV3d(int type, uint8 *dst, V3d *src, uint32 numVertices, uint32 stride) +{ + if(type == VERT_FLOAT3) + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 12); + dst += stride; + src++; + } + else if(type == VERT_COMPNORM) + for(uint32 i = 0; i < numVertices; i++){ + uint32 n = ((((uint32)(src->z * 511.0f)) & 0x3ff) << 22) | + ((((uint32)(src->y * 1023.0f)) & 0x7ff) << 11) | + ((((uint32)(src->x * 1023.0f)) & 0x7ff) << 0); + *(uint32*)dst = n; + dst += stride; + src++; + } + else + assert(0 && "unsupported instV3d type"); +} + +void +uninstV3d(int type, V3d *dst, uint8 *src, uint32 numVertices, uint32 stride) +{ + if(type == VERT_FLOAT3) + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 12); + src += stride; + dst++; + } + else if(type == VERT_COMPNORM) + for(uint32 i = 0; i < numVertices; i++){ + uint32 n = *(uint32*)src; + int32 normal[3]; + normal[0] = n & 0x7FF; + normal[1] = (n >> 11) & 0x7FF; + normal[2] = (n >> 22) & 0x3FF; + // sign extend + if(normal[0] & 0x400) normal[0] |= ~0x7FF; + if(normal[1] & 0x400) normal[1] |= ~0x7FF; + if(normal[2] & 0x200) normal[2] |= ~0x3FF; + dst->x = normal[0] / 1023.0f; + dst->y = normal[1] / 1023.0f; + dst->z = normal[2] / 511.0f; + src += stride; + dst++; + } + else + assert(0 && "unsupported uninstV3d type"); +} + +void +instTexCoords(int type, uint8 *dst, TexCoords *src, uint32 numVertices, uint32 stride) +{ + assert(type == VERT_FLOAT2); + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 8); + dst += stride; + src++; + } +} + +void +uninstTexCoords(int type, TexCoords *dst, uint8 *src, uint32 numVertices, uint32 stride) +{ + assert(type == VERT_FLOAT2); + for(uint32 i = 0; i < numVertices; i++){ + memcpy(dst, src, 8); + src += stride; + dst++; + } +} + +bool32 +instColor(int type, uint8 *dst, RGBA *src, uint32 numVertices, uint32 stride) +{ + uint8 alpha = 0xFF; + if(type == VERT_ARGB){ + for(uint32 i = 0; i < numVertices; i++){ + dst[0] = src->blue; + dst[1] = src->green; + dst[2] = src->red; + dst[3] = src->alpha; + alpha &= src->alpha; + dst += stride; + src++; + } + }else if(type == VERT_RGBA){ + for(uint32 i = 0; i < numVertices; i++){ + dst[0] = src->red; + dst[1] = src->green; + dst[2] = src->blue; + dst[3] = src->alpha; + alpha &= src->alpha; + dst += stride; + src++; + } + }else + assert(0 && "unsupported color type"); + return alpha != 0xFF; +} + +void +uninstColor(int type, RGBA *dst, uint8 *src, uint32 numVertices, uint32 stride) +{ + assert(type == VERT_ARGB); + for(uint32 i = 0; i < numVertices; i++){ + dst->red = src[2]; + dst->green = src[1]; + dst->blue = src[0]; + dst->alpha = src[3]; + src += stride; + dst++; + } +} + +} diff --git a/src/plg.cpp b/src/plg.cpp new file mode 100644 index 0000000..4c757c8 --- /dev/null +++ b/src/plg.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +namespace rw { + +static void *defCtor(void *object, int32, int32) { return object; } +static void *defDtor(void *object, int32, int32) { return object; } +static void *defCopy(void *dst, void*, int32, int32) { return dst; } + +static LinkList allPlugins; + +#define PLG(lnk) LLLinkGetData(lnk, Plugin, inParentList) + +void +PluginList::open(void) +{ + allPlugins.init(); +} + +void +PluginList::close(void) +{ + PluginList *l; + Plugin *p; + FORLIST(lnk, allPlugins){ + p = LLLinkGetData(lnk, Plugin, inGlobalList); + l = p->parentList; + p->inParentList.remove(); + p->inGlobalList.remove(); + rwFree(p); + if(l->plugins.isEmpty()) + l->size = l->defaultSize; + } + assert(allPlugins.isEmpty()); +} + +void +PluginList::construct(void *object) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + p->constructor(object, p->offset, p->size); + } +} + +void +PluginList::destruct(void *object) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + p->destructor(object, p->offset, p->size); + } +} + +void +PluginList::copy(void *dst, void *src) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + p->copy(dst, src, p->offset, p->size); + } +} + +bool +PluginList::streamRead(Stream *stream, void *object) +{ + int32 length; + ChunkHeaderInfo header; + if(!findChunk(stream, ID_EXTENSION, (uint32*)&length, nil)) + return false; + while(length > 0){ + if(!readChunkHeaderInfo(stream, &header)) + return false; + length -= 12; + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == header.type && p->read){ + p->read(stream, header.length, + object, p->offset, p->size); + goto cont; + } + } + stream->seek(header.length); +cont: + length -= header.length; + } + return true; +} + +void +PluginList::streamWrite(Stream *stream, void *object) +{ + int size = this->streamGetSize(object); + writeChunkHeader(stream, ID_EXTENSION, size); + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->getSize == nil || + (size = p->getSize(object, p->offset, p->size)) <= 0) + continue; + writeChunkHeader(stream, p->id, size); + p->write(stream, size, object, p->offset, p->size); + } +} + +int +PluginList::streamGetSize(void *object) +{ + int32 size = 0; + int32 plgsize; + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->getSize && + (plgsize = p->getSize(object, p->offset, p->size)) > 0) + size += 12 + plgsize; + } + return size; +} + +void +PluginList::streamSkip(Stream *stream) +{ + int32 length; + ChunkHeaderInfo header; + if(!findChunk(stream, ID_EXTENSION, (uint32*)&length, nil)) + return; + while(length > 0){ + if(!readChunkHeaderInfo(stream, &header)) + return; + stream->seek(header.length); + length -= 12 + header.length; + } +} + +void +PluginList::assertRights(void *object, uint32 pluginID, uint32 data) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == pluginID){ + if(p->rightsCallback) + p->rightsCallback(object, + p->offset, p->size, data); + return; + } + } +} + + +int32 +PluginList::registerPlugin(int32 size, uint32 id, + Constructor ctor, Destructor dtor, CopyConstructor copy) +{ + Plugin *p = (Plugin*)rwMalloc(sizeof(Plugin), MEMDUR_GLOBAL); + p->offset = this->size; + this->size += size; + int32 round = sizeof(void*)-1; + this->size = (this->size + round)&~round; + + p->size = size; + p->id = id; + p->constructor = ctor ? ctor : defCtor; + p->destructor = dtor ? dtor : defDtor; + p->copy = copy ? copy : defCopy; + p->read = nil; + p->write = nil; + p->getSize = nil; + p->rightsCallback = nil; + p->parentList = this; + this->plugins.add(&p->inParentList); + allPlugins.add(&p->inGlobalList); + return p->offset; +} + +int32 +PluginList::registerStream(uint32 id, + StreamRead read, StreamWrite write, StreamGetSize getSize) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == id){ + p->read = read; + p->write = write; + p->getSize = getSize; + return p->offset; + } + } + return -1; +} + +int32 +PluginList::setStreamRightsCallback(uint32 id, RightsCallback cb) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == id){ + p->rightsCallback = cb; + return p->offset; + } + } + return -1; +} + +int32 +PluginList::getPluginOffset(uint32 id) +{ + FORLIST(lnk, this->plugins){ + Plugin *p = PLG(lnk); + if(p->id == id) + return p->offset; + } + return -1; +} + +} diff --git a/src/png.cpp b/src/png.cpp new file mode 100644 index 0000000..5749a32 --- /dev/null +++ b/src/png.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#include "lodepng/lodepng.h" + +#ifdef _WIN32 +/* srsly? */ +#define strdup _strdup +#endif + +#define PLUGIN_ID 0 + +namespace rw { + + +Image* +readPNG(const char *filename) +{ + Image *image; + uint32 length; + uint8 *data = getFileContents(filename, &length); + assert(data != nil); + + LodePNGState state; + lodepng_state_init(&state); + uint8 *raw = nil; + uint32 w, h; + + // First try: decode without conversion to see if we understand the format + state.decoder.color_convert = 0; + uint32 error = lodepng_decode(&raw, &w, &h, &state, data, length); + if(error){ + RWERROR((ERR_GENERAL, lodepng_error_text(error))); + return nil; + } + + if(state.info_raw.bitdepth == 4 && state.info_raw.colortype == LCT_PALETTE){ + image = Image::create(w, h, 4); + image->allocate(); + memcpy(image->palette, state.info_raw.palette, state.info_raw.palettesize*4); + expandPal4_BE(image->pixels, image->stride, raw, w/2, w, h); + }else if(state.info_raw.bitdepth == 8){ + switch(state.info_raw.colortype){ + case LCT_PALETTE: + image = Image::create(w, h, state.info_raw.palettesize <= 16 ? 4 : 8); + image->allocate(); + memcpy(image->palette, state.info_raw.palette, state.info_raw.palettesize*4); + memcpy(image->pixels, raw, w*h); + break; + case LCT_RGB: + image = Image::create(w, h, 24); + image->allocate(); + memcpy(image->pixels, raw, w*h*3); + break; + default: + // Second try: just load as 32 bit + free(raw); + lodepng_state_init(&state); + error = lodepng_decode(&raw, &w, &h, &state, data, length); + if(error){ + RWERROR((ERR_GENERAL, lodepng_error_text(error))); + return nil; + } + // fall through + case LCT_RGBA: + image = Image::create(w, h, 32); + image->allocate(); + memcpy(image->pixels, raw, w*h*4); + break; + } + } + + free(raw); // TODO: maybe override lodepng allocator + + return image; +} + +void +writePNG(Image *image, const char *filename) +{ + int32 i; + StreamFile file; + if(!file.open(filename, "wb")){ + RWERROR((ERR_FILE, filename)); + return; + } + + size_t rawsize; + uint8 *raw = nil; + uint8 *pixels; + LodePNGState state; + lodepng_state_init(&state); + + pixels = image->pixels; + switch(image->depth){ + case 4: + state.info_raw.bitdepth = 4; + state.info_raw.colortype = LCT_PALETTE; + state.info_png.color.bitdepth = 4; + state.info_png.color.colortype = LCT_PALETTE; + state.encoder.auto_convert = 0; + for(i = 0; i < (1<depth); i++){ + uint8 *col = &image->palette[i*4]; + lodepng_palette_add(&state.info_png.color, col[0], col[1], col[2], col[3]); + lodepng_palette_add(&state.info_raw, col[0], col[1], col[2], col[3]); + } + pixels = rwNewT(uint8, image->width/2*image->height, ID_IMAGE | MEMDUR_FUNCTION); + compressPal4_BE(pixels, image->width/2, image->pixels, image->width, image->width, image->height); + break; + case 8: + state.info_raw.colortype = LCT_PALETTE; + state.info_png.color.colortype = LCT_PALETTE; + state.encoder.auto_convert = 0; + for(i = 0; i < (1<depth); i++){ + uint8 *col = &image->palette[i*4]; + lodepng_palette_add(&state.info_png.color, col[0], col[1], col[2], col[3]); + lodepng_palette_add(&state.info_raw, col[0], col[1], col[2], col[3]); + } + break; + case 16: + // Don't think we can have 16 bits with PNG + // TODO: don't change original image + image->convertTo32(); + break; + case 24: + state.info_raw.colortype = LCT_RGB; + state.info_png.color.colortype = LCT_RGB; + state.encoder.auto_convert = 0; + break; + case 32: + // already done + break; + } + + uint32 error = lodepng_encode(&raw, &rawsize, pixels, image->width, image->height, &state); + if(error){ + RWERROR((ERR_GENERAL, lodepng_error_text(error))); + return; + } + if(pixels != image->pixels) + rwFree(pixels); + file.write8(raw, rawsize); + file.close(); + free(raw); +} + +} diff --git a/src/prim.cpp b/src/prim.cpp new file mode 100644 index 0000000..eb353d9 --- /dev/null +++ b/src/prim.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 0 + +namespace rw { + + +void +BBox::initialize(V3d *point) +{ + this->inf = *point; + this->sup = *point; +} + +void +BBox::addPoint(V3d *point) +{ + if(point->x < this->inf.x) + this->inf.x = point->x; + if(point->y < this->inf.y) + this->inf.y = point->y; + if(point->z < this->inf.z) + this->inf.z = point->z; + if(point->x > this->sup.x) + this->sup.x = point->x; + if(point->y > this->sup.y) + this->sup.y = point->y; + if(point->z > this->sup.z) + this->sup.z = point->z; +} + +void +BBox::calculate(V3d *points, int32 n) +{ + this->inf = points[0]; + this->sup = points[0]; + while(--n){ + points++; + if(points->x < this->inf.x) + this->inf.x = points->x; + if(points->y < this->inf.y) + this->inf.y = points->y; + if(points->z < this->inf.z) + this->inf.z = points->z; + if(points->x > this->sup.x) + this->sup.x = points->x; + if(points->y > this->sup.y) + this->sup.y = points->y; + if(points->z > this->sup.z) + this->sup.z = points->z; + } +} + +bool +BBox::containsPoint(V3d *point) +{ + return point->x >= this->inf.x && point->x <= this->sup.x && + point->y >= this->inf.y && point->y <= this->sup.y && + point->z >= this->inf.z && point->z <= this->sup.z; +} + +} diff --git a/src/ps2/pds.cpp b/src/ps2/pds.cpp new file mode 100644 index 0000000..afdc6e8 --- /dev/null +++ b/src/ps2/pds.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "../rwanim.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +namespace rw { +namespace ps2 { + +struct PdsGlobals +{ + Pipeline **pipes; + int32 maxPipes; + int32 numPipes; +}; +static PdsGlobals pdsGlobals; + +Pipeline* +getPDSPipe(uint32 data) +{ + for(int32 i = 0; i < pdsGlobals.numPipes; i++) + if(pdsGlobals.pipes[i]->pluginData == data) + return pdsGlobals.pipes[i]; + return nil; +} + +void +registerPDSPipe(Pipeline *pipe) +{ + if(pdsGlobals.pipes == nil) + pdsGlobals.pipes = rwNewT(Pipeline*, pdsGlobals.maxPipes, MEMDUR_GLOBAL | ID_PDS); + assert(pdsGlobals.numPipes < pdsGlobals.maxPipes); + pdsGlobals.pipes[pdsGlobals.numPipes++] = pipe; +} + +static void +atomicPDSRights(void *object, int32, int32, uint32 data) +{ + Atomic *a = (Atomic*)object; + a->pipeline = (ObjPipeline*)getPDSPipe(data); +// printf("atm pds: %x %x %x\n", data, a->pipeline->pluginID, a->pipeline->pluginData); +} + +static void +materialPDSRights(void *object, int32, int32, uint32 data) +{ + Material *m = (Material*)object; + m->pipeline = (ObjPipeline*)getPDSPipe(data); +// printf("mat pds: %x %x %x\n", data, m->pipeline->pluginID, m->pipeline->pluginData); +} + +static void *pdsOpen(void *object, int32 offset, int32 size) { return object; } +static void* +pdsClose(void *object, int32 offset, int32 size) +{ + // TODO MEMORY: free registered pipelines + rwFree(pdsGlobals.pipes); + return object; +} + +void +registerPDSPlugin(int32 n) +{ + pdsGlobals.maxPipes = n; + pdsGlobals.numPipes = 0; + pdsGlobals.pipes = nil; + Engine::registerPlugin(0, ID_PDS, pdsOpen, pdsClose); + Atomic::registerPlugin(0, ID_PDS, nil, nil, nil); + Atomic::setStreamRightsCallback(ID_PDS, atomicPDSRights); + + Material::registerPlugin(0, ID_PDS, nil, nil, nil); + Material::setStreamRightsCallback(ID_PDS, materialPDSRights); +} + +void +registerPluginPDSPipes(void) +{ + // TODO: how do we destroy them? + + // rwPDS_G3_Skin_GrpMatPipeID + MatPipeline *pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x11001; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + pipe->attribs[AT_NORMAL+1] = &attribWeights; + uint32 vertCount = MatPipeline::getVertCount(VU_Lights-0x100, 5, 3, 2); + pipe->setTriBufferSizes(5, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = genericUninstanceCB; + pipe->preUninstCB = skinPreCB; + pipe->postUninstCB = skinPostCB; + registerPDSPipe(pipe); + + // rwPDS_G3_Skin_GrpAtmPipeID + ObjPipeline *opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x11002; + opipe->groupPipeline = pipe; + registerPDSPipe(opipe); + + // rwPDS_G3_MatfxUV1_GrpMatPipeID + pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x1100b; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + vertCount = MatPipeline::getVertCount(0x3C5, 4, 3, 3); + pipe->setTriBufferSizes(4, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->uninstanceCB = genericUninstanceCB; + registerPDSPipe(pipe); + + // rwPDS_G3_MatfxUV1_GrpAtmPipeID + opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x1100d; + opipe->groupPipeline = pipe; + registerPDSPipe(opipe); + + // rwPDS_G3_MatfxUV2_GrpMatPipeID + pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x1100c; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV2; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + vertCount = MatPipeline::getVertCount(0x3C5, 4, 3, 3); + pipe->setTriBufferSizes(4, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->uninstanceCB = genericUninstanceCB; + registerPDSPipe(pipe); + + // rwPDS_G3_MatfxUV2_GrpAtmPipeID + opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x1100e; + opipe->groupPipeline = pipe; + registerPDSPipe(opipe); + + // RW World plugin + + // rwPDS_G3x_Generic_AtmPipeID + opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x50001; + registerPDSPipe(opipe); + + // rwPDS_G3x_Skin_AtmPipeID + opipe = ObjPipeline::create(); + opipe->pluginID = ID_PDS; + opipe->pluginData = 0x5000b; + registerPDSPipe(opipe); + + // rwPDS_G3xd_A4D_MatPipeID + pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x5002f; + pipe->attribs[0] = &attribXYZW; + pipe->attribs[1] = &attribUV; + pipe->attribs[2] = &attribNormal; + vertCount = 0x50; + pipe->setTriBufferSizes(3, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; // 0xF0 + pipe->uninstanceCB = genericUninstanceCB; + pipe->preUninstCB = genericPreCB; + registerPDSPipe(pipe); + + // rwPDS_G3xd_A4DSkin_MatPipeID + pipe = MatPipeline::create(); + pipe->pluginID = ID_PDS; + pipe->pluginData = 0x5003e; + pipe->attribs[0] = &attribXYZW; + pipe->attribs[1] = &attribUV; + pipe->attribs[2] = &attribNormal; + pipe->attribs[3] = &attribWeights; + vertCount = 0x30; + pipe->setTriBufferSizes(4, vertCount); // 0xC0 + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = genericUninstanceCB; + pipe->preUninstCB = genericPreCB; + pipe->postUninstCB = skinPostCB; + registerPDSPipe(pipe); +} + +} +} diff --git a/src/ps2/ps2.cpp b/src/ps2/ps2.cpp new file mode 100644 index 0000000..dbbcd0a --- /dev/null +++ b/src/ps2/ps2.cpp @@ -0,0 +1,1571 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "../rwanim.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +#include "rwps2impl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace ps2 { + +#define ALIGNPTR(p,a) ((uint8*)(((uintptr)(p)+a-1) & ~(uintptr)(a-1))) + +static void* +driverOpen(void *o, int32, int32) +{ + engine->driver[PLATFORM_PS2]->defaultPipeline = makeDefaultPipeline(); + + engine->driver[PLATFORM_PS2]->rasterNativeOffset = nativeRasterOffset; + engine->driver[PLATFORM_PS2]->rasterCreate = rasterCreate; + engine->driver[PLATFORM_PS2]->rasterLock = rasterLock; + engine->driver[PLATFORM_PS2]->rasterUnlock = rasterUnlock; + engine->driver[PLATFORM_PS2]->rasterLockPalette = rasterLockPalette; + engine->driver[PLATFORM_PS2]->rasterUnlockPalette = rasterUnlockPalette; + engine->driver[PLATFORM_PS2]->rasterNumLevels = rasterNumLevels; + engine->driver[PLATFORM_PS2]->imageFindRasterFormat = imageFindRasterFormat; + engine->driver[PLATFORM_PS2]->rasterFromImage = rasterFromImage; + engine->driver[PLATFORM_PS2]->rasterToImage = rasterToImage; + + return o; +} + +static void* +driverClose(void *o, int32, int32) +{ + return o; +} + +void +registerPlatformPlugins(void) +{ + Driver::registerPlugin(PLATFORM_PS2, 0, PLATFORM_PS2, + driverOpen, driverClose); + + registerNativeRaster(); +} + +ObjPipeline *defaultObjPipe; +MatPipeline *defaultMatPipe; + +void* +destroyNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_PS2) + return object; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + for(uint32 i = 0; i < header->numMeshes; i++) + rwFree(header->instanceMeshes[i].dataRaw); + rwFree(header->instanceMeshes); + rwFree(header); + geometry->instData = nil; + return object; +} + +Stream* +readNativeData(Stream *stream, int32, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_PS2){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geometry->instData = header; + header->platform = PLATFORM_PS2; + assert(geometry->meshHeader != nil); + header->numMeshes = geometry->meshHeader->numMeshes; + header->instanceMeshes = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + Mesh *m = geometry->meshHeader->getMeshes(); + for(uint32 i = 0; i < header->numMeshes; i++){ + InstanceData *instance = &header->instanceMeshes[i]; + uint32 buf[2]; + stream->read32(buf, 8); + instance->dataSize = buf[0]; + instance->dataRaw = rwNewT(uint8, instance->dataSize+0x7F, MEMDUR_EVENT | ID_GEOMETRY); + instance->data = ALIGNPTR(instance->dataRaw, 0x80); +#ifdef RW_PS2 + uint32 a = (uint32)instance->data; + assert(a % 0x10 == 0); +#endif + stream->read8(instance->data, instance->dataSize); +#ifdef RW_PS2 + if(!buf[1]) + fixDmaOffsets(instance); +#endif + instance->material = m->material; +// sizedebug(instance); + m++; + } + return stream; +} + +Stream* +writeNativeData(Stream *stream, int32 len, void *object, int32, int32) +{ + ASSERTLITTLE; + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_STRUCT, len-12); + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_PS2) + return stream; + stream->writeU32(PLATFORM_PS2); + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + for(uint32 i = 0; i < header->numMeshes; i++){ + InstanceData *instance = &header->instanceMeshes[i]; + uint32 buf[2]; + buf[0] = instance->dataSize; + buf[1] = unfixDmaOffsets(instance); + stream->write32(buf, 8); + stream->write8(instance->data, instance->dataSize); +#ifdef RW_PS2 + if(!buf[1]) + fixDmaOffsets(instance); +#endif + } + return stream; +} + +int32 +getSizeNativeData(void *object, int32, int32) +{ + Geometry *geometry = (Geometry*)object; + int32 size = 16; + if(geometry->instData == nil || + geometry->instData->platform != PLATFORM_PS2) + return 0; + InstanceDataHeader *header = (InstanceDataHeader*)geometry->instData; + for(uint32 i = 0; i < header->numMeshes; i++){ + InstanceData *instance = &header->instanceMeshes[i]; + size += 8; + size += instance->dataSize; + } + return size; +} + +void +registerNativeDataPlugin(void) +{ + Geometry::registerPlugin(0, ID_NATIVEDATA, + nil, destroyNativeData, nil); + Geometry::registerPluginStream(ID_NATIVEDATA, + readNativeData, + writeNativeData, + getSizeNativeData); +} + +// Patch DMA ref ADDR fields to point to the actual data. +#ifdef RW_PS2 +void +fixDmaOffsets(InstanceData *inst) +{ + uint32 base = (uint32)inst->data; + uint32 *tag = (uint32*)inst->data; + for(;;){ + switch(tag[0]&0x70000000){ + // DMAcnt + case 0x10000000: + // no need to fix + tag += (1+(tag[0]&0xFFFF))*4; + break; + + // DMAref + case 0x30000000: + // fix address and jump to next + tag[1] = base + tag[1]<<4; + tag += 4; + break; + + // DMAret + case 0x60000000: + // we're done + return; + + default: + fprintf(stderr, "error: unknown DMAtag %X\n", tag[0]); + return; + } + } +} +#endif + +// Patch DMA ref ADDR fields to qword offsets and return whether +// no ref tags were found. +// Only under RW_PS2 are the addresses actually patched but we need +// the return value for streaming out. +bool32 +unfixDmaOffsets(InstanceData *inst) +{ + bool32 norefs = 1; +#ifdef RW_PS2 + uint32 base = (uint32)inst->data; +#endif + uint32 *tag = (uint32*)inst->data; + for(;;){ + switch(tag[0]&0x70000000){ + // DMAcnt + case 0x10000000: + // no need to unfix + tag += (1+(tag[0]&0xFFFF))*4; + break; + + // DMAref + case 0x30000000: + norefs = 0; + // unfix address and jump to next +#ifdef RW_PS2 + tag[1] = (tag[1] - base)>>4; +#endif + tag += 4; + break; + + // DMAret + case 0x60000000: + return norefs; + + default: + fprintf(stderr, "error: unknown DMAtag %X\n", tag[0]); + return norefs; + } + } +} + +// Pipeline + +PipeAttribute attribXYZ = { + "XYZ", + AT_V3_32 +}; + +PipeAttribute attribXYZW = { + "XYZW", + AT_V4_32 +}; + +PipeAttribute attribUV = { + "UV", + AT_V2_32 +}; + +PipeAttribute attribUV2 = { + "UV2", + AT_V4_32 +}; + +PipeAttribute attribRGBA = { + "RGBA", + AT_V4_8 | AT_UNSGN +}; + +PipeAttribute attribNormal = { + "Normal", + AT_V3_8 // RW has V4_8 but uses V3_8, wtf? +}; + +PipeAttribute attribWeights = { + "Weights", + AT_V4_32 | AT_RW +}; + +static uint32 +attribSize(uint32 unpack) +{ + static uint32 size[] = { 32, 16, 8, 16 }; + return ((unpack>>26 & 3)+1)*size[unpack>>24 & 3]/8; +} + +#define QWC(x) (((x)+0xF)>>4) + +static uint32 +getBatchSize(MatPipeline *pipe, uint32 vertCount) +{ + PipeAttribute *a; + if(vertCount == 0) + return 0; + uint32 size = 1; // ITOP &c. at the end + for(uint i = 0; i < nelem(pipe->attribs); i++) + if((a = pipe->attribs[i]) && (a->attrib & AT_RW) == 0){ + size++; // UNPACK &c. + size += QWC(vertCount*attribSize(a->attrib)); + } + return size; +} + +uint32* +instanceXYZ(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d = (uint32*)g->morphTargets[0].vertices; + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j*3+0]; + *p++ = d[j*3+1]; + *p++ = d[j*3+2]; + } + while((uintptr)p % 0x10) + *p++ = 0; + return p; +} + +uint32* +instanceXYZW(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d = (uint32*)g->morphTargets[0].vertices; + int8 *adcbits = getADCbitsForMesh(g, m); + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j*3+0]; + *p++ = d[j*3+1]; + *p++ = d[j*3+2]; + *p++ = adcbits && adcbits[i] ? 0x8000 : 0; + } + // don't need to pad + return p; +} + +uint32* +instanceUV(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d = (uint32*)g->texCoords[0]; + if((g->flags & Geometry::TEXTURED) || + (g->flags & Geometry::TEXTURED2)) + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j*2+0]; + *p++ = d[j*2+1]; + } + else + for(uint32 i = idx; i < idx+n; i++){ + *p++ = 0; + *p++ = 0; + } + while((uintptr)p % 0x10) + *p++ = 0; + return p; +} + +uint32* +instanceUV2(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d0 = (uint32*)g->texCoords[0]; + uint32 *d1 = (uint32*)g->texCoords[1]; + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + if(g->numTexCoordSets > 0){ + *p++ = d0[j*2+0]; + *p++ = d0[j*2+1]; + }else{ + *p++ = 0; + *p++ = 0; + } + if(g->numTexCoordSets > 1){ + *p++ = d1[j*2+0]; + *p++ = d1[j*2+1]; + }else{ + *p++ = 0; + *p++ = 0; + } + } + while((uintptr)p % 0x10) + *p++ = 0; + return p; +} + +uint32* +instanceRGBA(uint32 *p, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + uint32 *d = (uint32*)g->colors; + if((g->flags & Geometry::PRELIT)) + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j]; + } + else + for(uint32 i = idx; i < idx+n; i++) + *p++ = 0xFF000000; + while((uintptr)p % 0x10) + *p++ = 0; + return p; +} + +uint32* +instanceNormal(uint32 *wp, Geometry *g, Mesh *m, uint32 idx, uint32 n) +{ + uint16 j; + V3d *d = g->morphTargets[0].normals; + uint8 *p = (uint8*)wp; + if((g->flags & Geometry::NORMALS)) + for(uint32 i = idx; i < idx+n; i++){ + j = m->indices[i]; + *p++ = d[j].x*127.0f; + *p++ = d[j].y*127.0f; + *p++ = d[j].z*127.0f; + } + else + for(uint32 i = idx; i < idx+n; i++){ + *p++ = 0; + *p++ = 0; + *p++ = 0; + } + while((uintptr)p % 0x10) + *p++ = 0; + return (uint32*)p; +} + +void +MatPipeline::init(void) +{ + this->rw::Pipeline::init(PLATFORM_PS2); + for(int i = 0; i < 10; i++) + this->attribs[i] = nil; + this->instanceCB = nil; + this->uninstanceCB = nil; + this->preUninstCB = nil; + this->postUninstCB = nil; +} + +MatPipeline* +MatPipeline::create(void) +{ + MatPipeline *pipe = rwNewT(MatPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +MatPipeline::destroy(void) +{ + rwFree(this); +} + +void +MatPipeline::dump(void) +{ + if(this->platform != PLATFORM_PS2) + return; + PipeAttribute *a; + printf("%x %x\n", this->pluginID, this->pluginData); + for(uint i = 0; i < nelem(this->attribs); i++){ + a = this->attribs[i]; + if(a) + printf("%d %s: %x\n", i, a->name, a->attrib); + } + printf("stride: %x\n", this->inputStride); + printf("vertcount: %x\n", this->vifOffset/this->inputStride); + printf("triSCount: %x\n", this->triStripCount); + printf("triLCount: %x\n", this->triListCount); + printf("vifOffset: %x\n", this->vifOffset); + printf("\n"); +} + +void +MatPipeline::setTriBufferSizes(uint32 inputStride, uint32 bufferSize) +{ + PipeAttribute *a; + + this->inputStride = inputStride; + uint32 numTLtris = bufferSize/3; + this->triListCount = (numTLtris & ~3) * 3; + this->triStripCount = bufferSize & ~3; + for(uint i = 0; i < nelem(this->attribs); i++){ + a = this->attribs[i]; + if(a && a->attrib & AT_RW){ + // broken out attribs have different requirement + // because we have to be able to restart a strip + // at an aligned offset + this->triStripCount = ((bufferSize-2) & ~3)+2; + return; + } + } +} + +// Instance format: +// no broken out clusters +// ====================== +// DMAret [FLUSH; MSKPATH3 || FLUSH; FLUSH] { +// foreach batch { +// foreach cluster { +// MARK/0; STMOD; STCYCL; UNPACK +// unpack-data +// } +// ITOP; MSCALF/MSCNT; // if first/not-first +// 0/FLUSH; 0/MSKPATH3 || 0/FLUSH; 0/FLUSH // if not-last/last +// } +// } +// +// broken out clusters +// =================== +// foreach batch { +// foreach broken out cluster { +// DMAref [STCYCL; UNPACK] -> pointer into unpack-data +// DMAcnt (empty) +// } +// DMAcnt/ret { +// foreach cluster { +// MARK/0; STMOD; STCYCL; UNPACK +// unpack-data +// } +// ITOP; MSCALF/MSCNT; // if first/not-first +// 0/FLUSH; 0/MSKPATH3 || 0/FLUSH; 0/FLUSH // if not-last/last +// } +// } +// unpack-data for broken out clusters + +uint32 markcnt = 0; + +enum { + DMAcnt = 0x10000000, + DMAref = 0x30000000, + DMAret = 0x60000000, + + VIF_NOP = 0, + VIF_STCYCL = 0x01000000, + VIF_STCYCL1 = 0x01000100, // WL = 1 + VIF_OFFSET = 0x02000000, + VIF_BASE = 0x03000000, + VIF_ITOP = 0x04000000, + VIF_STMOD = 0x05000000, + VIF_MSKPATH3 = 0x06000000, + VIF_MARK = 0x07000000, + VIF_FLUSHE = 0x10000000, + VIF_FLUSH = 0x11000000, + VIF_FLUSHA = 0x13000000, + VIF_MSCAL = 0x14000000, + VIF_MSCALF = 0x15000000, + VIF_MSCNT = 0x17000000, + VIF_STMASK = 0x20000000, + VIF_STROW = 0x30000000, + VIF_STCOL = 0x31000000, + VIF_MPG = 0x4A000000, + VIF_DIRECT = 0x50000000, + VIF_DIRECTHL = 0x51000000, + VIF_UNPACK = 0x60000000 // no mode encoded +}; + +struct InstMeshInfo +{ + uint32 numAttribs, numBrokenAttribs; + uint32 batchVertCount, lastBatchVertCount; + uint32 numBatches; + uint32 batchSize, lastBatchSize; + uint32 size; // size of DMA chain without broken out data + uint32 size2; // size of broken out data + uint32 vertexSize; + uint32 attribPos[10]; +}; + +InstMeshInfo +getInstMeshInfo(MatPipeline *pipe, Geometry *g, Mesh *m) +{ + PipeAttribute *a; + InstMeshInfo im; + im.numAttribs = 0; + im.numBrokenAttribs = 0; + im.vertexSize = 0; + for(uint i = 0; i < nelem(pipe->attribs); i++) + if(a = pipe->attribs[i]) + if(a->attrib & AT_RW) + im.numBrokenAttribs++; + else{ + im.vertexSize += attribSize(a->attrib); + im.numAttribs++; + } + if(g->meshHeader->flags == MeshHeader::TRISTRIP){ + im.numBatches = (m->numIndices-2) / (pipe->triStripCount-2); + im.batchVertCount = pipe->triStripCount; + im.lastBatchVertCount = (m->numIndices-2) % (pipe->triStripCount-2); + if(im.lastBatchVertCount){ + im.numBatches++; + im.lastBatchVertCount += 2; + } + }else{ // TRILIST; nothing else supported yet + im.numBatches = (m->numIndices+pipe->triListCount-1) / + pipe->triListCount; + im.batchVertCount = pipe->triListCount; + im.lastBatchVertCount = m->numIndices % pipe->triListCount; + } + if(im.lastBatchVertCount == 0) + im.lastBatchVertCount = im.batchVertCount; + + im.batchSize = getBatchSize(pipe, im.batchVertCount); + im.lastBatchSize = getBatchSize(pipe, im.lastBatchVertCount); + if(im.numBrokenAttribs == 0) + im.size = 1 + im.batchSize*(im.numBatches-1) + im.lastBatchSize; + else + im.size = 2*im.numBrokenAttribs*im.numBatches + + (1+im.batchSize)*(im.numBatches-1) + 1+im.lastBatchSize; + + /* figure out size and addresses of broken out sections */ + im.size2 = 0; + for(uint i = 0; i < nelem(im.attribPos); i++) + if((a = pipe->attribs[i]) && a->attrib & AT_RW){ + im.attribPos[i] = im.size2 + im.size; + im.size2 += QWC(m->numIndices*attribSize(a->attrib)); + } + + return im; +} + +void +MatPipeline::instance(Geometry *g, InstanceData *inst, Mesh *m) +{ + PipeAttribute *a; + InstMeshInfo im = getInstMeshInfo(this, g, m); + + inst->dataSize = (im.size+im.size2)<<4; + // TODO: do this properly, just a test right now + inst->dataSize += 0x7F; + inst->dataRaw = rwNewT(uint8, inst->dataSize, MEMDUR_EVENT | ID_GEOMETRY); + inst->data = ALIGNPTR(inst->dataRaw, 0x80); + + /* make array of addresses of broken out sections */ + uint8 *datap[nelem(this->attribs)]; + uint8 **dp = datap; + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && a->attrib & AT_RW) + dp[i] = inst->data + im.attribPos[i]*0x10; + + // TODO: not sure if this is correct + uint32 msk_flush = rw::version >= 0x35000 ? VIF_FLUSH : VIF_MSKPATH3; + + uint32 idx = 0; + uint32 *p = (uint32*)inst->data; + if(im.numBrokenAttribs == 0){ + *p++ = DMAret | im.size-1; + *p++ = 0; + *p++ = VIF_FLUSH; + *p++ = msk_flush; + } + for(uint32 j = 0; j < im.numBatches; j++){ + uint32 nverts, bsize; + if(j < im.numBatches-1){ + bsize = im.batchSize; + nverts = im.batchVertCount; + }else{ + bsize = im.lastBatchSize; + nverts = im.lastBatchVertCount; + } + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && a->attrib & AT_RW){ + uint32 atsz = attribSize(a->attrib); + *p++ = DMAref | QWC(nverts*atsz); + *p++ = im.attribPos[i]; + *p++ = VIF_STCYCL1 | this->inputStride; + // Round up nverts so UNPACK will fit exactly into the DMA packet + // (can't pad with zeroes in broken out sections). + int num = (QWC(nverts*atsz)<<4)/atsz; + *p++ = (a->attrib&0xFF004000) + | 0x8000 | num << 16 | i; // UNPACK + // This probably shouldn't happen. + if(num*this->inputStride > this->vifOffset) + fprintf(stderr, "WARNING: PS2 instance data over vifOffset %08X, %X-> %X %X\n", + p[-1], num, + num*this->inputStride, this->vifOffset); + + *p++ = DMAcnt; + *p++ = 0x0; + *p++ = VIF_NOP; + *p++ = VIF_NOP; + + im.attribPos[i] += g->meshHeader->flags == 1 ? + QWC((im.batchVertCount-2)*atsz) : + QWC(im.batchVertCount*atsz); + } + if(im.numBrokenAttribs){ + *p++ = (j < im.numBatches-1 ? DMAcnt : DMAret) | bsize; + *p++ = 0x0; + *p++ = VIF_NOP; + *p++ = VIF_NOP; + } + + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && (a->attrib & AT_RW) == 0){ + if(rw::version >= 0x35000) + *p++ = VIF_NOP; + else + *p++ = VIF_MARK | markcnt++; + *p++ = VIF_STMOD; + *p++ = VIF_STCYCL1 | this->inputStride; + *p++ = (a->attrib&0xFF004000) + | 0x8000 | nverts << 16 | i; // UNPACK + + if(a == &attribXYZ) + p = instanceXYZ(p, g, m, idx, nverts); + else if(a == &attribXYZW) + p = instanceXYZW(p, g, m, idx, nverts); + else if(a == &attribUV) + p = instanceUV(p, g, m, idx, nverts); + else if(a == &attribUV2) + p = instanceUV2(p, g, m, idx, nverts); + else if(a == &attribRGBA) + p = instanceRGBA(p, g, m, idx, nverts); + else if(a == &attribNormal) + p = instanceNormal(p, g, m, idx, nverts); + } + idx += g->meshHeader->flags == 1 + ? im.batchVertCount-2 : im.batchVertCount; + + *p++ = VIF_ITOP | nverts; + *p++ = j == 0 ? VIF_MSCALF : VIF_MSCNT; + if(j < im.numBatches-1){ + *p++ = VIF_NOP; + *p++ = VIF_NOP; + }else{ + *p++ = VIF_FLUSH; + *p++ = msk_flush; + } + } + + if(this->instanceCB) + this->instanceCB(this, g, m, datap); +#ifdef RW_PS2 + if(im.numBrokenAttribs) + fixDmaOffsets(inst); +#endif +} + +uint8* +MatPipeline::collectData(Geometry *g, InstanceData *inst, Mesh *m, uint8 *data[]) +{ + PipeAttribute *a; + InstMeshInfo im = getInstMeshInfo(this, g, m); + + uint8 *raw = rwNewT(uint8, im.vertexSize*m->numIndices, MEMDUR_EVENT | ID_GEOMETRY); + uint8 *dp = raw; + for(uint i = 0; i < nelem(this->attribs); i++) + if(a = this->attribs[i]) + if(a->attrib & AT_RW){ + data[i] = inst->data + im.attribPos[i]*0x10; + }else{ + data[i] = dp; + dp += m->numIndices*attribSize(a->attrib); + } + + uint8 *datap[nelem(this->attribs)]; + memcpy(datap, data, sizeof(datap)); + + uint32 overlap = g->meshHeader->flags == 1 ? 2 : 0; + uint32 *p = (uint32*)inst->data; + if(im.numBrokenAttribs == 0) + p += 4; + for(uint32 j = 0; j < im.numBatches; j++){ + uint32 nverts = j < im.numBatches-1 ? im.batchVertCount : + im.lastBatchVertCount; + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && a->attrib & AT_RW) + p += 8; + if(im.numBrokenAttribs) + p += 4; + for(uint i = 0; i < nelem(this->attribs); i++) + if((a = this->attribs[i]) && (a->attrib & AT_RW) == 0){ + uint32 asz = attribSize(a->attrib); + p += 4; + if((p[-1] & 0xff004000) != a->attrib){ + fprintf(stderr, "unexpected unpack: %08x %08x\n", p[-1], a->attrib); + assert(0 && "unexpected unpack\n"); + } + memcpy(datap[i], p, asz*nverts); + datap[i] += asz*(nverts-overlap); + p += QWC(asz*nverts)*4; + } + p += 4; + } + return raw; +} + +static void +objInstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + // TODO: allow for REINSTANCE + if(geo->instData) + return; + InstanceDataHeader *header = rwNewT(InstanceDataHeader, 1, MEMDUR_EVENT | ID_GEOMETRY); + geo->instData = header; + header->platform = PLATFORM_PS2; + assert(geo->meshHeader != nil); + header->numMeshes = geo->meshHeader->numMeshes; + header->instanceMeshes = rwNewT(InstanceData, header->numMeshes, MEMDUR_EVENT | ID_GEOMETRY); + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->getMeshes()[i]; + InstanceData *instance = &header->instanceMeshes[i]; + + MatPipeline *m; + m = pipe->groupPipeline ? + pipe->groupPipeline : + (MatPipeline*)mesh->material->pipeline; + if(m == nil) + m = defaultMatPipe; + m->instance(geo, instance, mesh); + instance->material = mesh->material; + } +} + +/* +static void +printVertCounts(InstanceData *inst, int flag) +{ + uint32 *d = (uint32*)inst->data; + uint32 id = 0; + if(inst->material->pipeline) + id = inst->material->pipeline->pluginData; + int stride; + if(inst->arePointersFixed){ + d += 4; + while(d[3]&0x60000000){ // skip UNPACKs + stride = d[2]&0xFF; + d += 4 + 4*QWC(attribSize(d[3])*((d[3]>>16)&0xFF)); + } + if(d[2] == 0) + printf("ITOP %x %d (%d) %x\n", *d, stride, flag, id); + }else{ + while((*d&0x70000000) == 0x30000000){ + stride = d[2]&0xFF; + printf("UNPACK %x %d (%d) %x\n", d[3], stride, flag, id); + d += 8; + } + if((*d&0x70000000) == 0x10000000){ + d += (*d&0xFFFF)*4; + printf("ITOP %x %d (%d) %x\n", *d, stride, flag, id); + } + } +} +*/ + +static void +objUninstance(rw::ObjPipeline *rwpipe, Atomic *atomic) +{ + ObjPipeline *pipe = (ObjPipeline*)rwpipe; + Geometry *geo = atomic->geometry; + if((geo->flags & Geometry::NATIVE) == 0) + return; + assert(geo->instData != nil); + assert(geo->instData->platform == PLATFORM_PS2); + InstanceDataHeader *header = (InstanceDataHeader*)geo->instData; + // highest possible number of vertices + geo->numVertices = geo->meshHeader->totalIndices; + geo->numTriangles = geo->meshHeader->guessNumTriangles(); + geo->allocateData(); + geo->allocateMeshes(geo->meshHeader->numMeshes, geo->meshHeader->totalIndices, 0); + uint32 *flags = rwNewT(uint32, geo->numVertices, + MEMDUR_FUNCTION | ID_GEOMETRY); + memset(flags, 0, 4*geo->numVertices); + memset(geo->meshHeader->getMeshes()->indices, 0, 2*geo->meshHeader->totalIndices); + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->getMeshes()[i]; + MatPipeline *m; + m = pipe->groupPipeline ? + pipe->groupPipeline : + (MatPipeline*)mesh->material->pipeline; + if(m == nil) m = defaultMatPipe; + if(m->preUninstCB) m->preUninstCB(m, geo); + } + geo->numVertices = 0; + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->getMeshes()[i]; + InstanceData *instance = &header->instanceMeshes[i]; + MatPipeline *m; + m = pipe->groupPipeline ? + pipe->groupPipeline : + (MatPipeline*)mesh->material->pipeline; + if(m == nil) m = defaultMatPipe; + + //printDMAVIF(instance); + uint8 *data[nelem(m->attribs)] = { nil }; + uint8 *raw = m->collectData(geo, instance, mesh, data); + assert(m->uninstanceCB); + m->uninstanceCB(m, geo, flags, mesh, data); + rwFree(raw); + } + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->getMeshes()[i]; + MatPipeline *m; + m = pipe->groupPipeline ? + pipe->groupPipeline : + (MatPipeline*)mesh->material->pipeline; + if(m == nil) m = defaultMatPipe; + if(m->postUninstCB) m->postUninstCB(m, geo); + } + + int8 *bits = getADCbits(geo); + geo->generateTriangles(bits); + rwFree(flags); + geo->flags &= ~Geometry::NATIVE; + destroyNativeData(geo, 0, 0); +/* + for(uint32 i = 0; i < header->numMeshes; i++){ + Mesh *mesh = &geo->meshHeader->mesh[i]; + InstanceData *instance = &header->instanceMeshes[i]; +// printf("numIndices: %d\n", mesh->numIndices); +// printDMA(instance); + printVertCounts(instance, geo->meshHeader->flags); + } +*/ +} + +void +ObjPipeline::init(void) +{ + this->rw::ObjPipeline::init(PLATFORM_PS2); + this->groupPipeline = nil; + this->impl.instance = objInstance; + this->impl.uninstance = objUninstance; +} + +ObjPipeline* +ObjPipeline::create(void) +{ + ObjPipeline *pipe = rwNewT(ObjPipeline, 1, MEMDUR_GLOBAL); + pipe->init(); + return pipe; +} + +void +insertVertex(Geometry *geo, int32 i, uint32 mask, Vertex *v) +{ + if(mask & 0x1) + geo->morphTargets[0].vertices[i] = v->p; + if(mask & 0x10) + geo->morphTargets[0].normals[i] = v->n; + if(mask & 0x100) + geo->colors[i] = v->c; + if(mask & 0x1000) + geo->texCoords[0][i] = v->t; + if(mask & 0x2000) + geo->texCoords[1][i] = v->t1; +} + +void +genericPreCB(MatPipeline *pipe, Geometry *geo) +{ + PipeAttribute *a; + for(int32 i = 0; i < nelem(pipe->attribs); i++) + if(a = pipe->attribs[i]) + if(a == &attribXYZW){ + allocateADC(geo); + break; + } + skinPreCB(pipe, geo); +} + +void +genericUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]) +{ + float32 *xyz = nil, *xyzw = nil; + float32 *uv = nil, *uv2 = nil; + uint8 *rgba = nil; + int8 *normals = nil; + uint32 *weights = nil; + int8 *adc = nil; + Skin *skin = nil; + if(skinGlobals.geoOffset) + skin = Skin::get(geo); + + PipeAttribute *a; + for(int32 i = 0; i < nelem(pipe->attribs); i++) + if(a = pipe->attribs[i]){ + if(a == &attribXYZ) xyz = (float32*)data[i]; + else if(a == &attribXYZW) xyzw = (float32*)data[i]; + else if(a == &attribUV) uv = (float32*)data[i]; + else if(a == &attribUV2) uv2 = (float32*)data[i]; + else if(a == &attribRGBA) rgba = data[i]; + else if(a == &attribNormal) normals = (int8*)data[i]; + else if(a == &attribWeights) weights = (uint32*)data[i]; + } + + uint32 mask = 0x1; // vertices + if(normals && geo->flags & Geometry::NORMALS) + mask |= 0x10; + if(rgba && geo->flags & Geometry::PRELIT) + mask |= 0x100; + if((uv || uv2) && geo->numTexCoordSets > 0) + mask |= 0x1000; + if(uv2 && geo->numTexCoordSets > 1) + mask |= 0x2000; + if(weights && skin) + mask |= 0x10000; + if(xyzw) + adc = getADCbitsForMesh(geo, mesh); + + Vertex v; + for(uint32 i = 0; i < mesh->numIndices; i++){ + if(mask & 0x1) + memcpy(&v.p, xyz ? xyz : xyzw, 12); + if(mask & 0x10){ + // TODO: figure out scaling :/ + v.n.x = normals[0]/128.0f; + v.n.y = normals[1]/128.0f; + v.n.z = normals[2]/128.0f; + } + if(mask & 0x100) + memcpy(&v.c, rgba, 4); + if(mask & 0x1000) + memcpy(&v.t, uv ? uv : uv2, 8); + if(mask & 0x2000) + memcpy(&v.t1, uv2 + 2, 8); + if(mask & 0x10000) + for(int j = 0; j < 4; j++){ + ((uint32*)v.w)[j] = weights[j] & ~0x3FF; + v.i[j] = (weights[j] & 0x3FF) >> 2; + if(v.i[j]) v.i[j]--; + if(v.w[j] == 0.0f) v.i[j] = 0; + } + int32 idx = findVertexSkin(geo, flags, mask, &v); + if(idx < 0) + idx = geo->numVertices++; + mesh->indices[i] = idx; + if(adc) + adc[i] = xyzw[3] != 0.0f; + flags[idx] = mask; + insertVertexSkin(geo, idx, mask, &v); + if(xyz) xyz += 3; + if(xyzw) xyzw += 4; + if(uv) uv += 2; + if(uv2) uv2 += 4; + rgba += 4; + normals += 3; + weights += 4; + } +} + +/* +void +defaultUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]) +{ + float32 *verts = (float32*)data[AT_XYZ]; + float32 *texcoords = (float32*)data[AT_UV]; + uint8 *colors = (uint8*)data[AT_RGBA]; + int8 *norms = (int8*)data[AT_NORMAL]; + uint32 mask = 0x1; // vertices + if(geo->flags & Geometry::NORMALS) + mask |= 0x10; + if(geo->flags & Geometry::PRELIT) + mask |= 0x100; + for(int32 i = 0; i < geo->numTexCoordSets; i++) + mask |= 0x1000 << i; + int numUV = pipe->attribs[AT_UV] == &attribUV2 ? 2 : 1; + + Vertex v; + for(uint32 i = 0; i < mesh->numIndices; i++){ + if(mask & 0x1) + memcpy(&v.p, verts, 12); + if(mask & 0x10){ + v.n[0] = norms[0]/127.0f; + v.n[1] = norms[1]/127.0f; + v.n[2] = norms[2]/127.0f; + } + if(mask & 0x100){ + memcpy(&v.c, colors, 4); + //v.c[3] = 0xFF; + } + if(mask & 0x1000) + memcpy(&v.t, texcoords, 8); + if(mask & 0x2000) + memcpy(&v.t1, texcoords+2, 8); + + int32 idx = findVertex(geo, flags, mask, &v); + if(idx < 0) + idx = geo->numVertices++; + mesh->indices[i] = idx; + flags[idx] = mask; + insertVertex(geo, idx, mask, &v); + verts += 3; + texcoords += 2*numUV; + colors += 4; + norms += 3; + } +} +*/ + +#undef QWC + +ObjPipeline* +makeDefaultPipeline(void) +{ + if(defaultMatPipe == nil){ + MatPipeline *pipe = MatPipeline::create(); + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + uint32 vertCount = MatPipeline::getVertCount(VU_Lights,4,3,2); + pipe->setTriBufferSizes(4, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->uninstanceCB = genericUninstanceCB; + defaultMatPipe = pipe; + } + + if(defaultObjPipe == nil){ + ObjPipeline *opipe = ObjPipeline::create(); + defaultObjPipe = opipe; + } + return defaultObjPipe; +} + +// ADC + +int32 adcOffset; + +int8* +getADCbits(Geometry *geo) +{ + int8 *bits = nil; + if(adcOffset){ + ADCData *adc = PLUGINOFFSET(ADCData, geo, adcOffset); + if(adc->adcFormatted) + bits = adc->adcBits; + } + return bits; +} + +int8* +getADCbitsForMesh(Geometry *geo, Mesh *mesh) +{ + int8 *bits = getADCbits(geo); + if(bits == nil) + return nil; + int32 n = mesh - geo->meshHeader->getMeshes(); + for(int32 i = 0; i < n; i++) + bits += geo->meshHeader->getMeshes()[i].numIndices; + return bits; +} + +// TODO +void +convertADC(Geometry*) +{ +} + +// Not optimal but works +void +unconvertADC(Geometry *g) +{ + ADCData *adc = PLUGINOFFSET(ADCData, g, adcOffset); + if(!adc->adcFormatted) + return; + int8 *b = adc->adcBits; + + MeshHeader *oldmh = g->meshHeader; + g->meshHeader = nil; + // Don't allocate indices for now + MeshHeader *newmh = g->allocateMeshes(oldmh->numMeshes, 0, 1); + newmh->flags = oldmh->flags; // should be tristrip + Mesh *oldm = oldmh->getMeshes(); + Mesh *newm = newmh->getMeshes(); + for(int32 i = 0; i < newmh->numMeshes; i++){ + newm->material = oldm->material; + newm->numIndices = oldm->numIndices; + for(uint32 j = 0; j < oldm->numIndices; j++) + if(*b++) + newm->numIndices += 2; + newmh->totalIndices += newm->numIndices; + newm++; + oldm++; + } + // Now re-allocate with indices + newmh = g->allocateMeshes(newmh->numMeshes, newmh->totalIndices, 0); + b = adc->adcBits; + oldm = oldmh->getMeshes(); + newm = newmh->getMeshes(); + for(int32 i = 0; i < newmh->numMeshes; i++){ + int32 n = 0; + for(uint32 j = 0; j < oldm->numIndices; j++){ + if(*b++){ + newm->indices[n++] = oldm->indices[j-1]; + newm->indices[n++] = oldm->indices[j-1]; + } + newm->indices[n++] = oldm->indices[j]; + } + newm++; + oldm++; + } + rwFree(oldmh); + adc->adcFormatted = 0; + rwFree(adc->adcBits); + adc->adcBits = nil; + adc->numBits = 0; +} + +void +allocateADC(Geometry *geo) +{ + ADCData *adc = PLUGINOFFSET(ADCData, geo, adcOffset); + adc->adcFormatted = 1; + adc->numBits = geo->meshHeader->totalIndices; + int32 size = adc->numBits+3 & ~3; + adc->adcBits = rwNewT(int8, size, MEMDUR_EVENT | ID_ADC); + memset(adc->adcBits, 0, size); +} + +static void* +createADC(void *object, int32 offset, int32) +{ + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + adc->adcFormatted = 0; + return object; +} + +static void* +copyADC(void *dst, void *src, int32 offset, int32) +{ + ADCData *dstadc = PLUGINOFFSET(ADCData, dst, offset); + ADCData *srcadc = PLUGINOFFSET(ADCData, src, offset); + dstadc->adcFormatted = srcadc->adcFormatted; + if(!dstadc->adcFormatted) + return dst; + dstadc->numBits = srcadc->numBits; + int32 size = dstadc->numBits+3 & ~3; + dstadc->adcBits = rwNewT(int8, size, MEMDUR_EVENT | ID_ADC); + memcpy(dstadc->adcBits, srcadc->adcBits, size); + return dst; +} + +static void* +destroyADC(void *object, int32 offset, int32) +{ + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + if(adc->adcFormatted) + rwFree(adc->adcBits); + return object; +} + +static Stream* +readADC(Stream *stream, int32, void *object, int32 offset, int32) +{ + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + if(!findChunk(stream, ID_ADC, nil, nil)){ + RWERROR((ERR_CHUNK, "ADC")); + return nil; + } + adc->numBits = stream->readI32(); + adc->adcFormatted = 1; + if(adc->numBits == 0){ + adc->adcBits = nil; + adc->numBits = 0; + return stream; + } + int32 size = adc->numBits+3 & ~3; + adc->adcBits = rwNewT(int8, size, MEMDUR_EVENT | ID_ADC); + stream->read8(adc->adcBits, size); + return stream; +} + +static Stream* +writeADC(Stream *stream, int32 len, void *object, int32 offset, int32) +{ + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + Geometry *geometry = (Geometry*)object; + writeChunkHeader(stream, ID_ADC, len-12); + if(geometry->flags & Geometry::NATIVE){ + stream->writeI32(0); + return stream; + } + stream->writeI32(adc->numBits); + int32 size = adc->numBits+3 & ~3; + stream->write8(adc->adcBits, size); + return stream; +} + +static int32 +getSizeADC(void *object, int32 offset, int32) +{ + Geometry *geometry = (Geometry*)object; + ADCData *adc = PLUGINOFFSET(ADCData, object, offset); + if(!adc->adcFormatted) + return 0; + if(geometry->flags & Geometry::NATIVE) + return 16; + return 16 + (adc->numBits+3 & ~3); +} + +void +registerADCPlugin(void) +{ + adcOffset = Geometry::registerPlugin(sizeof(ADCData), ID_ADC, + createADC, destroyADC, copyADC); + Geometry::registerPluginStream(ID_ADC, + readADC, + writeADC, + getSizeADC); +} + +// misc stuff + +static uint32 +unpackSize(uint32 unpack) +{ + static uint32 size[] = { 32, 16, 8, 4 }; + return ((unpack>>26 & 3)+1)*size[unpack>>24 & 3]/8; +} + +/* A little dumb VIF interpreter */ +static void +sendVIF(uint32 w) +{ + enum VIFstate { + VST_cmd, + VST_stmask, + VST_strow, + VST_stcol, + VST_mpg, + VST_direct, + VST_unpack + }; +// static uint32 buf[256 * 16]; // maximum unpack size + static VIFstate state = VST_cmd; + static uint32 n; + static uint32 code; + uint32 imm, num; + + imm = w & 0xFFFF; + num = (w>>16) & 0xFF; + switch(state){ + case VST_cmd: + code = w; + if((code & 0x60000000) == VIF_UNPACK){ + printf("\t%08X VIF_UNPACK\n", code); + printf("\t...skipping...\n"); + state = VST_unpack; + n = (unpackSize(code)*num + 3) >> 2; + }else switch(code & 0x7F000000){ + case VIF_NOP: + printf("\t%08X VIF_NOP\n", code); + break; + case VIF_STCYCL: + printf("\t%08X VIF_STCYCL\n", code); + break; + case VIF_OFFSET: + printf("\t%08X VIF_OFFSET\n", code); + break; + case VIF_BASE: + printf("\t%08X VIF_BASE\n", code); + break; + case VIF_ITOP: + printf("\t%08X VIF_ITOP\n", code); + break; + case VIF_STMOD: + printf("\t%08X VIF_STMOD\n", code); + break; + case VIF_MSKPATH3: + printf("\t%08X VIF_MSKPATH3\n", code); + break; + case VIF_MARK: + printf("\t%08X VIF_MARK\n", code); + break; + case VIF_FLUSHE: + printf("\t%08X VIF_FLUSHE\n", code); + break; + case VIF_FLUSH: + printf("\t%08X VIF_FLUSH\n", code); + break; + case VIF_FLUSHA: + printf("\t%08X VIF_FLUSHA\n", code); + break; + case VIF_MSCAL: + printf("\t%08X VIF_MSCAL\n", code); + break; + case VIF_MSCALF: + printf("\t%08X VIF_MSCALF\n", code); + break; + case VIF_MSCNT: + printf("\t%08X VIF_MSCNT\n", code); + break; + case VIF_STMASK: + printf("\t%08X VIF_STMASK\n", code); + printf("\t...skipping...\n"); + state = VST_stmask; + n = 1; + break; + case VIF_STROW: + printf("\t%08X VIF_STROW\n", code); + printf("\t...skipping...\n"); + state = VST_strow; + n = 4; + break; + case VIF_STCOL: + printf("\t%08X VIF_STCOL\n", code); + printf("\t...skipping...\n"); + state = VST_stcol; + n = 4; + break; + case VIF_MPG: + printf("\t%08X VIF_MPG\n", code); + state = VST_mpg; + n = num*2; + break; + case VIF_DIRECT: + printf("\t%08X VIF_DIRECT\n", code); + printf("\t...skipping...\n"); + state = VST_direct; + n = imm*4; + break; + case VIF_DIRECTHL: + printf("\t%08X VIF_DIRECTHL\n", code); + printf("\t...skipping...\n"); + state = VST_direct; + n = imm*4; + break; + default: + printf("\tUnknown VIFcode %08X\n", code); + } + break; + /* TODO: actually do something here */ + case VST_stmask: + n--; + break; + case VST_strow: + n--; + break; + case VST_stcol: + n--; + break; + case VST_mpg: + n--; + break; + case VST_direct: + n--; + break; + case VST_unpack: + n--; + break; + } + if(n == 0) + state = VST_cmd; +} + +static void +dmaVIF(int32 qwc, uint32 *data) +{ + qwc *= 4; + while(qwc--) + sendVIF(*data++); +} + +void +printDMAVIF(InstanceData *inst) +{ + uint32 *tag = (uint32*)inst->data; + uint32 *base = (uint32*)inst->data; + uint32 qwc; + + for(;;){ + qwc = tag[0]&0xFFFF; + switch(tag[0]&0x70000000){ + case DMAcnt: + printf("DMAcnt %04x %08x\n", qwc, tag[1]); + sendVIF(tag[2]); + sendVIF(tag[3]); + dmaVIF(qwc, tag+4); + tag += (1+qwc)*4; + break; + + case DMAref: + printf("DMAref %04x %08x\n", qwc, tag[1]); + sendVIF(tag[2]); + sendVIF(tag[3]); + dmaVIF(qwc, base + tag[1]*4); + tag += 4; + break; + + case DMAret: + printf("DMAret %04x %08x\n", qwc, tag[1]); + sendVIF(tag[2]); + sendVIF(tag[3]); + dmaVIF(qwc, tag+4); + printf("\n"); + return; + } + } +} + +void +printDMA(InstanceData *inst) +{ + uint32 *tag = (uint32*)inst->data; + uint32 qwc; + for(;;){ + qwc = tag[0]&0xFFFF; + switch(tag[0]&0x70000000){ + case DMAcnt: + printf("CNT %04x %08x\n", qwc, tag[1]); + tag += (1+qwc)*4; + break; + + case DMAref: + printf("REF %04x %08x\n", qwc, tag[1]); + tag += 4; + break; + + case DMAret: + printf("RET %04x %08x\n\n", qwc, tag[1]); + return; + } + } +} + +/* +void +sizedebug(InstanceData *inst) +{ + if(inst->arePointersFixed == 2) + return; + uint32 *base = (uint32*)inst->data; + uint32 *tag = (uint32*)inst->data; + uint32 *last = nil; + for(;;){ + switch(tag[0]&0x70000000){ + case DMAcnt: + tag += (1+(tag[0]&0xFFFF))*4; + break; + + case DMAref: + last = base + tag[1]*4 + (tag[0]&0xFFFF)*4; + tag += 4; + break; + + case DMAret: + tag += (1+(tag[0]&0xFFFF))*4; + uint32 diff; + if(!last) + diff = (uint8*)tag - (uint8*)base; + else + diff = (uint8*)last - (uint8*)base; + printf("%x %x %x\n", inst->dataSize-diff, diff, inst->dataSize); + return; + + default: + printf("unkown DMAtag: %X %X\n", tag[0], tag[1]); + break; + } + } +} +*/ + +} +} diff --git a/src/ps2/ps2device.cpp b/src/ps2/ps2device.cpp new file mode 100644 index 0000000..98f9914 --- /dev/null +++ b/src/ps2/ps2device.cpp @@ -0,0 +1,49 @@ +#ifdef RW_PS2 + +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "../rwanim.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +#include "rwps2impl.h" + +#define PLUGIN_ID 2 + +namespace rw { +namespace ps2 { + +Device renderdevice = { + 16777215.0f, 0.0f, + null::beginUpdate, + null::endUpdate, + null::clearCamera, + null::showRaster, + null::rasterRenderFast, + null::setRenderState, + null::getRenderState, + null::im2DRenderLine, + null::im2DRenderTriangle, + null::im2DRenderPrimitive, + null::im2DRenderIndexedPrimitive, + null::im3DTransform, + null::im3DRenderPrimitive, + null::im3DRenderIndexedPrimitive, + null::im3DEnd, + null::deviceSystem +}; + +} +} + +#endif diff --git a/src/ps2/ps2matfx.cpp b/src/ps2/ps2matfx.cpp new file mode 100644 index 0000000..6972a05 --- /dev/null +++ b/src/ps2/ps2matfx.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +#define PLUGIN_ID ID_MATFX + +namespace rw { +namespace ps2 { + +static void* +matfxOpen(void *o, int32, int32) +{ + matFXGlobals.pipelines[PLATFORM_PS2] = makeMatFXPipeline(); + return o; +} + +static void* +matfxClose(void *o, int32, int32) +{ + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_PS2])->groupPipeline->destroy(); + ((ObjPipeline*)matFXGlobals.pipelines[PLATFORM_PS2])->destroy(); + matFXGlobals.pipelines[PLATFORM_PS2] = nil; + return o; +} + +void +initMatFX(void) +{ + Driver::registerPlugin(PLATFORM_PS2, 0, ID_MATFX, + matfxOpen, matfxClose); +} + +ObjPipeline* +makeMatFXPipeline(void) +{ + MatPipeline *pipe = MatPipeline::create(); + pipe->pluginID = ID_MATFX; + pipe->pluginData = 0; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + uint32 vertCount = MatPipeline::getVertCount(0x3C5, 4, 3, 3); + pipe->setTriBufferSizes(4, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->uninstanceCB = genericUninstanceCB; + + ObjPipeline *opipe = ObjPipeline::create(); + opipe->pluginID = ID_MATFX; + opipe->pluginData = 0; + opipe->groupPipeline = pipe; + return opipe; +} + +} +} diff --git a/src/ps2/ps2raster.cpp b/src/ps2/ps2raster.cpp new file mode 100644 index 0000000..68e8165 --- /dev/null +++ b/src/ps2/ps2raster.cpp @@ -0,0 +1,2238 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwengine.h" +#include "rwps2.h" + +#define PLUGIN_ID ID_DRIVER + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) + +namespace rw { +namespace ps2 { + +int32 nativeRasterOffset; + +#define MAXLEVEL(r) ((r)->tex1low >> 2) +static bool32 noNewStyleRasters; + +enum Psm { + PSMCT32 = 0x0, + PSMCT24 = 0x1, + PSMCT16 = 0x2, + PSMCT16S = 0xA, + PSMT8 = 0x13, + PSMT4 = 0x14, + PSMT8H = 0x1B, + PSMT4HL = 0x24, + PSMT4HH = 0x2C, + PSMZ32 = 0x30, + PSMZ24 = 0x31, + PSMZ16 = 0x32, + PSMZ16S = 0x3A +}; + +// i don't really understand this, stolen from RW +static void +transferMinSize(int32 psm, int32 flags, int32 *minw, int32 *minh) +{ + *minh = 1; + switch(psm){ + case PSMCT32: + case PSMZ32: + *minw = 2; // 32 bit + break; + case PSMCT16: + case PSMCT16S: + case PSMZ16: + case PSMZ16S: + *minw = 4; // 16 bit + break; + case PSMCT24: + case PSMT8: + case PSMT4: + case PSMT8H: + case PSMT4HL: + case PSMT4HH: + case PSMZ24: + *minw = 8; // everything else + break; + } + if(flags & 0x2 && psm == PSMT8){ + *minw = 16; + *minh = 4; + } + if(flags & 0x4 && psm == PSMT4){ + *minw = 32; + *minh = 4; + } +} + +#define ALIGN(x,a) ((x) + (a)-1 & ~((a)-1)) +#define ALIGN16(x) ((x) + 0xF & ~0xF) +#define ALIGN64(x) ((x) + 0x3F & ~0x3F) +#define NSIZE(dim,pagedim) (((dim) + (pagedim)-1)/(pagedim)) + +void* +mallocalign(size_t size, int32 alignment) +{ + void *p; + void **pp; + p = rwMalloc(size + alignment + sizeof(void*), MEMDUR_EVENT | ID_RASTERPS2); + if(p == nil) return nil; + pp = (void**)(((uintptr)p + sizeof(void*) + alignment)&~(alignment-1)); + pp[-1] = p; + return (void*)pp; +} + +void +freealign(void *p) +{ + void *pp; + if(p == nil) return; + pp = ((void**)p)[-1]; + rwFree(pp); +} + +// TODO: these depend on video mode, set in deviceSystem! +int32 cameraFormat = Raster::C8888; +int32 cameraDepth = 32; +int32 cameraZDepth = 16; + +int32 defaultMipMapKL = 0xFC0; +int32 maxMipLevels = 7; + +int32 +getRasterFormat(Raster *raster) +{ + int32 palformat, pixelformat, mipmapflags; + pixelformat = raster->format & 0xF00; + palformat = raster->format & 0x6000; + mipmapflags = raster->format & 0x9000; + switch(raster->type){ + case Raster::ZBUFFER: + if(palformat || mipmapflags){ + RWERROR((ERR_INVRASTER)); + return 0; + } + if(raster->depth && raster->depth != cameraZDepth){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = cameraZDepth; + if(pixelformat){ + if(raster->depth == 16 && pixelformat != Raster::D16 || + raster->depth == 32 && pixelformat != Raster::D32){ + RWERROR((ERR_INVRASTER)); + return 0; + } + } + pixelformat = raster->depth == 16 ? Raster::D16 : Raster::D32; + raster->format = pixelformat; + break; + case Raster::CAMERA: + if(palformat || mipmapflags){ + RWERROR((ERR_INVRASTER)); + return 0; + } + if(raster->depth && raster->depth != cameraDepth){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = cameraDepth; + if(pixelformat && pixelformat != cameraFormat){ + RWERROR((ERR_INVRASTER)); + return 0; + } + pixelformat = cameraFormat; + raster->format = pixelformat; + break; + case Raster::NORMAL: + case Raster::CAMERATEXTURE: + if(palformat || mipmapflags){ + RWERROR((ERR_INVRASTER)); + return 0; + } + /* fallthrough */ + case Raster::TEXTURE: + // Find raster format by depth if none was given + if(pixelformat == 0) + switch(raster->depth){ + case 4: + pixelformat = Raster::C1555; + palformat = Raster::PAL4; + break; + case 8: + pixelformat = Raster::C1555; + palformat = Raster::PAL8; + break; + case 24: + // unsafe + // pixelformat = Raster::C888; + // palformat = 0; + // break; + case 32: + pixelformat = Raster::C8888; + palformat = 0; + break; + default: + pixelformat = Raster::C1555; + palformat = 0; + break; + } + raster->format = pixelformat | palformat | mipmapflags; + // Sanity check raster format and depth; set depth if none given + if(palformat){ + if(palformat == Raster::PAL8){ + if(raster->depth && raster->depth != 8){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 8; + if(pixelformat != Raster::C1555 && pixelformat != Raster::C8888){ + RWERROR((ERR_INVRASTER)); + return 0; + } + }else if(palformat == Raster::PAL4){ + if(raster->depth && raster->depth != 4){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 4; + if(pixelformat != Raster::C1555 && pixelformat != Raster::C8888){ + RWERROR((ERR_INVRASTER)); + return 0; + } + }else{ + RWERROR((ERR_INVRASTER)); + return 0; + } + }else if(pixelformat == Raster::C1555){ + if(raster->depth && raster->depth != 16){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 16; + }else if(pixelformat == Raster::C8888){ + if(raster->depth && raster->depth != 32){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 32; + }else if(pixelformat == Raster::C888){ + assert(0 && "24 bit rasters not supported"); + if(raster->depth && raster->depth != 24){ + RWERROR((ERR_INVRASTER)); + return 0; + } + raster->depth = 24; + }else{ + RWERROR((ERR_INVRASTER)); + return 0; + } + break; + default: + RWERROR((ERR_INVRASTER)); + return 0; + } + return 1; +} + +/* + * Memory units: + * Column: 64 bytes (single cycle access) + * Block: 256 bytes, 64 words, 4 columns. texture base pointers + * Page: 8 kbytes, 2 kwords, 128 columns, 32 blocks. frame buffer base pointers + * entire memory: 4 mbytes, 64k columns, 16k blocks, 512 pages + * + * PSMT4: 128x128 pixels, 4x8 blocks per page, 32x16 pixels per block + * PSMT8: 128x64 pixels, 8x4 blocks per page, 16x16 pixels per block + * PSMCT16(S): 64x64 pixels, 4x8 blocks per page, 16x8 pixels per block + * PSMCT24: 64x32 pixels, 8x4 blocks per page, 8x8 pixels per block + * PSMCT32: 64x32 pixels, 8x4 blocks per page, 8x8 pixels per block + * + * Layout of blocks in page: + * + * PSMCT24, PSMCT32, PSMT8 + * +----+----+----+----+----+----+----+----+ + * | 0 | 1 | 4 | 5 | 16 | 17 | 20 | 21 | + * +----+----+----+----+----+----+----+----+ + * | 2 | 3 | 6 | 7 | 18 | 19 | 22 | 23 | + * +----+----+----+----+----+----+----+----+ + * | 8 | 9 | 12 | 13 | 24 | 25 | 28 | 29 | + * +----+----+----+----+----+----+----+----+ + * | 10 | 11 | 14 | 15 | 26 | 27 | 30 | 31 | + * +----+----+----+----+----+----+----+----+ + * + * PSMCT16, PSMT4 + * +----+----+----+----+ + * | 0 | 2 | 8 | 10 | + * +----+----+----+----+ + * | 1 | 3 | 9 | 11 | + * +----+----+----+----+ + * | 4 | 6 | 12 | 14 | + * +----+----+----+----+ + * | 5 | 7 | 13 | 15 | + * +----+----+----+----+ + * | 16 | 18 | 24 | 26 | + * +----+----+----+----+ + * | 17 | 19 | 25 | 27 | + * +----+----+----+----+ + * | 20 | 22 | 28 | 30 | + * +----+----+----+----+ + * | 21 | 23 | 29 | 31 | + * +----+----+----+----+ + * + * PSMCT16S + * +----+----+----+----+ + * | 0 | 2 | 16 | 18 | + * +----+----+----+----+ + * | 1 | 3 | 17 | 19 | + * +----+----+----+----+ + * | 8 | 10 | 24 | 26 | + * +----+----+----+----+ + * | 9 | 11 | 25 | 27 | + * +----+----+----+----+ + * | 4 | 6 | 20 | 22 | + * +----+----+----+----+ + * | 5 | 7 | 21 | 23 | + * +----+----+----+----+ + * | 12 | 14 | 28 | 30 | + * +----+----+----+----+ + * | 13 | 15 | 29 | 31 | + * +----+----+----+----+ + * + */ + +static uint8 blockmap_PSMCT32[32] = { + 0, 1, 4, 5, 16, 17, 20, 21, + 2, 3, 6, 7, 18, 19, 22, 23, + 8, 9, 12, 13, 24, 25, 28, 29, + 10, 11, 14, 15, 26, 27, 30, 31, +}; +static uint8 blockmap_PSMCT16[32] = { + 0, 2, 8, 10, + 1, 3, 9, 11, + 4, 6, 12, 14, + 5, 7, 13, 15, + 16, 18, 24, 26, + 17, 19, 25, 27, + 20, 22, 28, 30, + 21, 23, 29, 31, +}; +static uint8 blockmap_PSMCT16S[32] = { + 0, 2, 16, 18, + 1, 3, 17, 19, + 8, 10, 24, 26, + 9, 11, 25, 27, + 4, 6, 20, 22, + 5, 7, 21, 23, + 12, 14, 28, 30, + 13, 15, 29, 31, +}; +static uint8 blockmap_PSMZ32[32] = { + 24, 25, 28, 29, 8, 9, 12, 13, + 26, 27, 30, 31, 10, 11, 14, 15, + 16, 17, 20, 21, 0, 1, 4, 5, + 18, 19, 22, 23, 2, 3, 6, 7, +}; +static uint8 blockmap_PSMZ16[32] = { + 24, 26, 16, 18, + 25, 27, 17, 19, + 28, 30, 20, 22, + 29, 31, 21, 23, + 8, 10, 0, 2, + 9, 11, 1, 3, + 12, 14, 4, 6, + 13, 15, 5, 7, +}; +static uint8 blockmap_PSMZ16S[32] = { + 24, 26, 8, 10, + 25, 27, 9, 11, + 16, 18, 0, 2, + 17, 19, 1, 3, + 28, 30, 12, 14, + 29, 31, 13, 15, + 20, 22, 4, 6, + 21, 23, 5, 7, +}; + +static uint8 blockmaprev_PSMCT32[32] = { + 0, 1, 8, 9, 2, 3, 10, 11, + 16, 17, 24, 25, 18, 19, 26, 27, + 4, 5, 12, 13, 6, 7, 14, 15, + 20, 21, 28, 29, 22, 23, 30, 31, +}; +static uint8 blockmaprev_PSMCT16[32] = { + 0, 4, 1, 5, + 8, 12, 9, 13, + 2, 6, 3, 7, + 10, 14, 11, 15, + 16, 20, 17, 21, + 24, 28, 25, 29, + 18, 22, 19, 23, + 26, 30, 27, 31, +}; + +/* Suffixes used: + * _Px - pixels + * _W - width units (pixels/64) + * _B - blocks + * _P - pages + */ + +/* Layout mipmaps and palette in GS memory */ +static void +calcOffsets(int32 width_Px, int32 height_Px, int32 psm, uint64 *bufferBase_B, uint64 *bufferWidth_W, uint32 *trxpos, uint32 *totalSize, uint32 *paletteBase) +{ + uint32 pageWidth_Px, pageHeight_Px; + uint32 blockWidth_Px, blockHeight_Px; + uint32 mindim_Px; + int32 nlevels; + int32 n; + uint32 mipw_Px, miph_Px; + uint32 lastmipw_Px, lastmiph_Px; + uint32 bufferHeight_P[8]; + uint32 bufferPage_B[8]; // address of page in which the level is allocated + uint32 xoff_Px, yoff_Px; // x/y offset the last level starts at + // Whenever we allocate horizontally inside a page, + // keep track of the region below it on this stack. + uint32 sp; + uint32 xoffstack_Px[8]; // actually unused... + uint32 widthstack_Px[8]; + uint32 heightstack_Px[8]; + uint32 basestack_B[8]; + uint32 flag; + + switch(psm){ + case PSMCT32: + case PSMCT24: + case PSMT8H: + case PSMT4HL: + case PSMT4HH: + case PSMZ32: + case PSMZ24: + pageWidth_Px = 64; + pageHeight_Px = 32; + blockWidth_Px = 8; + blockHeight_Px = 8; + break; + case PSMT8: + pageWidth_Px = 128; + pageHeight_Px = 64; + blockWidth_Px = 16; + blockHeight_Px = 16; + break; + case PSMT4: + pageWidth_Px = 128; + pageHeight_Px = 128; + blockWidth_Px = 32; + blockHeight_Px = 16; + break; + case PSMCT16: + case PSMCT16S: + case PSMZ16: + case PSMZ16S: + default: + pageWidth_Px = 64; + pageHeight_Px = 64; + blockWidth_Px = 16; + blockHeight_Px = 8; + break; + } + + mindim_Px = min(width_Px, height_Px); + for(nlevels = 1; mindim_Px > 8; nlevels++){ + if(nlevels >= maxMipLevels) + break; + mindim_Px /= 2; + } + +#define PAGEWIDTH_B (pageWidth_Px/blockWidth_Px) // number of horizontal blocks per page +#define NBLKX(dim) (NSIZE((dim), blockWidth_Px)) +#define NBLKY(dim) (NSIZE((dim), blockHeight_Px)) +#define NPGX(dim) (NSIZE((dim), pageWidth_Px)) +#define NPGY(dim) (NSIZE((dim), pageHeight_Px)) +#define REALWIDTH(w) (max((w), blockWidth_Px)) +#define REALHEIGHT(w) (max((w), blockHeight_Px)) + + bufferBase_B[0] = 0; + bufferWidth_W[0] = NPGX(width_Px)*pageWidth_Px/64; + bufferHeight_P[0] = NPGY(height_Px); + bufferPage_B[0] = 0; + lastmipw_Px = width_Px; + lastmiph_Px = height_Px; + sp = 0; + xoff_Px = 0; + yoff_Px = 0; + flag = 0; + // Calculate info for all mipmap levels. + // mipwidth/height are actually the dimensions of level n-1! + // This code was reversed from RW and is rather complicated... + // partially because it's not clear what the assumptions are, + // can width/height be non-powers of 2? + for(n = 1; n < nlevels; n++){ + mipw_Px = lastmipw_Px/2; + miph_Px = lastmiph_Px/2; + if(lastmipw_Px >= pageWidth_Px){ + if(lastmiph_Px >= pageHeight_Px){ + // CASE 0 + // We allocate full pages + // This is the only place bufferWidth can change. Similarly bufferBase_2, which is related + bufferBase_B[n] = bufferBase_B[n-1] + (lastmipw_Px/blockWidth_Px)*(lastmiph_Px/blockHeight_Px); + bufferPage_B[n] = bufferBase_B[n]; + bufferWidth_W[n] = NPGX(mipw_Px)*pageWidth_Px/64; + bufferHeight_P[n] = NPGY(miph_Px); + xoff_Px = 0; + yoff_Px = 0; + }else{ + // CASE 1 + // Allocate vertically in the current page + bufferPage_B[n] = bufferPage_B[n-1]; + bufferHeight_P[n] = bufferHeight_P[n-1]; + bufferWidth_W[n] = bufferWidth_W[n-1]; + // How do we know pageHeight - yoff - REALHEIGHT(lastmiph) >= miph? + // And how is this condition ever false? + // Assuming lastmipw >= pageWidth for any number of levels, lastmiph must be pageHeight/2 + // or lower to reach this code. No dimension is lower than 8. Then consequent mipmaps + // will have heights halved but even with PSMT4 we will only (vertically) fill the + // page with the last mipmap and not go beyond... + if(REALHEIGHT(lastmiph_Px) + yoff_Px < pageHeight_Px){ + // CASE 2 + yoff_Px += REALHEIGHT(lastmiph_Px); + bufferBase_B[n] = bufferBase_B[n-1] + + PAGEWIDTH_B * NBLKY(lastmiph_Px) * + bufferWidth_W[n]*64/pageWidth_Px; // number of horizontal pages for level + }else{ + // CASE 3 + // Can this happen? + xoff_Px += REALWIDTH(lastmipw_Px); + bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px); + } + } + }else if(lastmiph_Px >= pageHeight_Px){ + // CASE 4 + // Allocate horizontally + bufferPage_B[n] = bufferPage_B[n-1]; + bufferHeight_P[n] = bufferHeight_P[n-1]; + bufferWidth_W[n] = bufferWidth_W[n-1]; + if(REALWIDTH(lastmipw_Px) + xoff_Px < pageWidth_Px){ + // CASE 5 + xoffstack_Px[sp] = xoff_Px; // unused... + heightstack_Px[sp] = REALHEIGHT(lastmiph_Px); + widthstack_Px[sp] = REALWIDTH(lastmipw_Px); + basestack_B[sp] = bufferBase_B[n-1] + NBLKY(lastmiph_Px) * PAGEWIDTH_B; + sp++; + xoff_Px += REALWIDTH(lastmipw_Px); + bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px); + }else if(sp){ + // CASE 7 + bufferBase_B[n] = basestack_B[sp-1]; + if(REALWIDTH(mipw_Px) < widthstack_Px[sp-1]){ + // CASE 9 + basestack_B[sp-1] += NBLKX(mipw_Px); + widthstack_Px[sp-1] -= REALWIDTH(mipw_Px); + }else if(REALHEIGHT(miph_Px) < heightstack_Px[sp-1]){ + // CASE 8 + basestack_B[sp-1] += NBLKY(miph_Px) * PAGEWIDTH_B; + heightstack_Px[sp-1] -= REALHEIGHT(miph_Px); + }else{ + // CASE 10 + sp--; + } + flag = 1; + }else{ + // CASE 6 + yoff_Px += REALHEIGHT(lastmiph_Px); + bufferBase_B[n] = bufferBase_B[n-1] + PAGEWIDTH_B*NBLKY(lastmiph_Px); + } + }else{ + // CASE 11 + bufferHeight_P[n] = bufferHeight_P[n-1]; + bufferPage_B[n] = bufferPage_B[n-1]; + bufferWidth_W[n] = bufferWidth_W[n-1]; + if(REALWIDTH(lastmipw_Px) + xoff_Px < bufferWidth_W[n-1]*64){ + // CASE 12 + xoffstack_Px[sp] = xoff_Px; // unused... + widthstack_Px[sp] = REALWIDTH(lastmipw_Px); + heightstack_Px[sp] = REALHEIGHT(lastmiph_Px); + basestack_B[sp] = bufferBase_B[n-1] + PAGEWIDTH_B * NBLKY(lastmiph_Px); + sp++; + xoff_Px += REALWIDTH(lastmipw_Px); + bufferBase_B[n] = bufferBase_B[n-1] + NBLKX(lastmipw_Px); + }else if(REALHEIGHT(lastmiph_Px) + yoff_Px < pageHeight_Px*bufferHeight_P[n] && flag == 0){ + // CASE 13 + bufferBase_B[n] = bufferBase_B[n-1] + PAGEWIDTH_B * NBLKY(lastmiph_Px); + yoff_Px += blockHeight_Px ? lastmiph_Px : 0; // how exactly can blockHeight be 0?? This looks wrong... + flag = n; + }else{ + // CASE 14 + bufferBase_B[n] = basestack_B[sp-1]; + if(REALWIDTH(mipw_Px) < widthstack_Px[sp-1]){ + // CASE 15 + basestack_B[sp-1] += NBLKX(mipw_Px); + widthstack_Px[sp-1] -= REALWIDTH(mipw_Px); + }else if(REALHEIGHT(miph_Px) < heightstack_Px[sp-1]){ + // CASE 16 + basestack_B[sp-1] += PAGEWIDTH_B * NBLKY(miph_Px); + heightstack_Px[sp-1] -= REALHEIGHT(miph_Px); + }else{ + // CASE 17 + sp--; + } + } + } + lastmipw_Px = mipw_Px; + lastmiph_Px = miph_Px; + } + + // Calculate position of palette. + uint32 paletteBase_B = 0; + uint64 bufwidth_Px = bufferWidth_W[nlevels-1]*64; + uint64 bufheight_Px = bufferHeight_P[nlevels-1]*pageHeight_Px; + // != means > really + if(bufwidth_Px != lastmipw_Px || bufheight_Px != lastmiph_Px){ + if(psm == PSMT8){ + // 2x2 blocks at the end of the page (even for PSMCT16S) + paletteBase_B = bufferPage_B[nlevels-1] + + ((bufwidth_Px/pageWidth_Px)*bufferHeight_P[nlevels-1] << 5) // total number of blocks + - (bufheight_Px/pageWidth_Px) * PAGEWIDTH_B // one block up + - 2; // two blocks left + }else if(psm == PSMT4){ + // One block at the end of the page + paletteBase_B = bufferPage_B[nlevels-1] + + ((bufwidth_Px/pageWidth_Px) * bufferHeight_P[nlevels-1] << 5) + - 1; + } + }else{ + if(psm == PSMT8 || psm == PSMT4){ + paletteBase_B = bufferPage_B[nlevels-1] + + (bufwidth_Px/blockWidth_Px) * (bufheight_Px/blockHeight_Px); + } + } + + uint32 bufwidth_W = bufferWidth_W[0]; + uint32 bufpage_B = bufferPage_B[0]; + uint32 pixeloff; + for(n = 0; n < nlevels; n++){ + // Calculate TRXPOS register (DSAX and DSAY, shifted up later) + // Start of buffer on current page (x in pixels, y in blocks) + pixeloff = (bufferBase_B[n] - bufpage_B) * blockWidth_Px; + // y coordinate of first pixel + yoff_Px = (pixeloff / (bufwidth_W*64)) * blockHeight_Px; + // x coordinate of first pixel + xoff_Px = pixeloff % (bufwidth_W*64); + if(bufferWidth_W[n] == bufwidth_W && + // Not quite sure what's the meaning of this. + // DSAY is 11 bits, but so is DSAX and it is not checked? + yoff_Px < 0x800){ + trxpos[n] = yoff_Px<<16 | xoff_Px; + }else{ + bufwidth_W = bufferWidth_W[n]; + bufpage_B = bufferPage_B[n]; + trxpos[n] = 0; + } + + // If using more than one page we have to swizzle rows inside page rows + if(bufwidth_W*64 / pageWidth_Px > 1){ + uint32 bufpagestride_B = bufwidth_W*64 * 32 / pageWidth_Px; // one row of pages + uint32 bufwidth_B = bufwidth_W*64 / blockWidth_Px; // one row of blocks + // To illustrate assume: + // - 8x4 block pages + // - texture is 4 pages wide + // Then the lower bits of an input block address look like: RRRPPCC + // where the C bits are the block's column inside a page + // the P bits are the block's page horizontally + // the R bits are the block's row in a row of pages + // We want to swap P and R: PPRRRCC + bufferBase_B[n] = + (bufferBase_B[n] & ~((uint64)bufpagestride_B - PAGEWIDTH_B)) // mask out R and P + | ((bufferBase_B[n] & (bufwidth_B - PAGEWIDTH_B)) * (bufpagestride_B/bufwidth_B)) // extract P and shift left + | ((bufferBase_B[n] & (bufpagestride_B - bufwidth_B)) / (bufwidth_B/PAGEWIDTH_B)); // extract R and shift right + } + + // Always have to swizzle blocks inside pages. We use a lookup, RW does bit operations + switch(psm){ + case PSMCT32: + case PSMCT24: + case PSMT8: + case PSMT8H: + case PSMT4HL: + case PSMT4HH: + // ABCDE -> CADBE + bufferBase_B[n] = bufferBase_B[n]&~0x1F | (uint64)blockmap_PSMCT32[bufferBase_B[n]&0x1F]; + break; + case PSMT4: + case PSMCT16: + // ABCDE -> ADBEC + bufferBase_B[n] = bufferBase_B[n]&~0x1F | (uint64)blockmap_PSMCT16[bufferBase_B[n]&0x1F]; + break; + case PSMCT16S: + // ABCDE -> DBAEC + bufferBase_B[n] = bufferBase_B[n]&~0x1F | (uint64)blockmap_PSMCT16S[bufferBase_B[n]&0x1F]; + break; + case PSMZ32: + case PSMZ24: + // ABCDE -> ~C~ADBE + bufferBase_B[n] = bufferBase_B[n]&~0x1F | (uint64)blockmap_PSMZ32[bufferBase_B[n]&0x1F]; + break; + case PSMZ16: + // ABCDE -> ~A~DBEC + bufferBase_B[n] = bufferBase_B[n]&~0x1F | (uint64)blockmap_PSMZ16[bufferBase_B[n]&0x1F]; + break; + case PSMZ16S: + // ABCDE -> ~D~BAEC + bufferBase_B[n] = bufferBase_B[n]&~0x1F | (uint64)blockmap_PSMZ16S[bufferBase_B[n]&0x1F]; + break; + default: break; + } + } + + // Same dance as above, with the palette + if(bufwidth_W*64 / pageWidth_Px > 1){ + uint32 bufpagestride_B = bufwidth_W*64 * 32 / pageWidth_Px; // one row of pages + uint32 bufwidth_B = bufwidth_W*64 / blockWidth_Px; // one row of blocks + paletteBase_B = + (paletteBase_B & ~(bufpagestride_B - PAGEWIDTH_B)) // mask out R and P + | ((paletteBase_B & (bufwidth_B - PAGEWIDTH_B)) * (bufpagestride_B/bufwidth_B)) // extract P and shift left + | ((paletteBase_B & (bufpagestride_B - bufwidth_B)) / (bufwidth_B/PAGEWIDTH_B)); // extract R and shift right + } + switch(psm){ + case PSMCT32: + case PSMCT24: + case PSMT8: + case PSMT8H: + case PSMT4HL: + case PSMT4HH: + paletteBase_B = paletteBase_B&~0x1F | (uint64)blockmap_PSMCT32[paletteBase_B&0x1F]; + break; + case PSMT4: + case PSMCT16: + paletteBase_B = paletteBase_B&~0x1F | (uint64)blockmap_PSMCT16[paletteBase_B&0x1F]; + break; + case PSMCT16S: + paletteBase_B = paletteBase_B&~0x1F | (uint64)blockmap_PSMCT16S[paletteBase_B&0x1F]; + break; + case PSMZ32: + case PSMZ24: + paletteBase_B = paletteBase_B&~0x1F | (uint64)blockmap_PSMZ32[paletteBase_B&0x1F]; + break; + case PSMZ16: + paletteBase_B = paletteBase_B&~0x1F | (uint64)blockmap_PSMZ16[paletteBase_B&0x1F]; + break; + case PSMZ16S: + paletteBase_B = paletteBase_B&~0x1F | (uint64)blockmap_PSMZ16S[paletteBase_B&0x1F]; + break; + default: break; + } + *paletteBase = paletteBase_B; + *totalSize = bufferPage_B[nlevels-1] + // start of last buffer` + bufferWidth_W[nlevels-1]*64/blockWidth_Px * // number of horizontal blocks in last level + pageHeight_Px*bufferHeight_P[nlevels-1]/blockHeight_Px; // number of vertical blocks in last level + *totalSize *= 64; // to words + +#undef BLKSTRIDE +#undef NBLKX +#undef NBLKY +#undef NPGX +#undef NPGY +#undef REALWIDTH +#undef REALHEIGHT +} + +static Raster* +rasterCreateTexture(Raster *raster) +{ + // We use a map for fast lookup, even for impossible depths + static int32 pageWidths[32] = { + 128, 128, 128, 128, + 128, 128, 128, 128, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + }; + static int32 pageHeights[32] = { + 128, 128, 128, 128, + 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, + 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, + }; + enum { + TCC_RGBA = 1 << 2, + CLD_1 = 1 << 29, + + WD2BLK = 64, // words per block + WD2PG = 2048, // words per page + }; + int32 pixelformat, palformat; + // TEX0 fields (not all) + int64 tbw = 0; // texture buffer width, texels/64 + int64 psm = 0; // pixel storage mode + int64 tw = 0; // texture width exponent, width = 2^tw + int64 th = 0; // texture height exponent, height = 2^th + int64 tcc = 0; // texture color component, 0 = rgb, 1 = rgba + int64 cpsm = 0; // CLUT pixel storage mode + int64 cld = 0; // CLUT buffer load control + + uint64 bufferWidth[7]; // in number of pixels / 64 + uint64 bufferBase[7]; // block address + uint32 trxpos_hi[8]; + int32 width, height, depth; + int32 pageWidth, pageHeight; + int32 paletteWidth, paletteHeight, paletteDepth; + int32 palettePagewidth, palettePageheight; + + + Ps2Raster *ras = PLUGINOFFSET(Ps2Raster, raster, nativeRasterOffset); + pixelformat = raster->format & 0xF00; + palformat = raster->format & 0x6000; + width = raster->width; + height = raster->height; + depth = raster->depth; + // RW's code does not seem to quite work with 24 bit rasters + // so make sure we're not generating them for safety + assert(depth != 24); + + ras->flags = 0; + ras->data = nil; + ras->dataSize = 0; + + // RW doesn't seem to check this, hm... + if(raster->flags & Raster::DONTALLOCATE) + return raster; + + //printf("%x %x %x %x\n", raster->format, raster->flags, raster->type, noNewStyleRasters); + pageWidth = pageWidths[depth-1]; + pageHeight = pageHeights[depth-1]; + + int32 s; + tw = 0; + for(s = 1; s < width; s *= 2) + tw++; + th = 0; + for(s = 1; s < height; s *= 2) + th++; + ras->kl = defaultMipMapKL; + // unk2[0] = 1 + //printf("%d %d %d %d\n", raster->width, logw, raster->height, logh); + + // round up to page width, set TBW, TW, TH + tbw = max(width,pageWidth)/64; + + // set PSM, TCC, CLD, CPSM and figure out palette format + if(palformat){ + if(palformat == Raster::PAL8){ + psm = PSMT8; + paletteWidth = 16; + paletteHeight = 16; + }else if(palformat == Raster::PAL4){ + psm = PSMT4; + paletteWidth = 8; + paletteHeight = 2; + }else{ + // can't happen, sanity check in getRasterFormat + return nil;; + } + tcc = 1; // RGBA + cld = 1; + if(pixelformat == Raster::C1555){ + paletteDepth = 2; + cpsm = PSMCT16S; + palettePagewidth = 64; + palettePageheight = 64; + }else if(pixelformat == Raster::C8888){ + paletteDepth = 4; + cpsm = PSMCT32; + palettePagewidth = 64; + palettePageheight = 32; + }else + // can't happen, sanity check in getRasterFormat + return nil;; + }else{ + paletteWidth = 0; + paletteHeight = 0; + paletteDepth = 0; + palettePagewidth = 0; + palettePageheight = 0; + if(pixelformat == Raster::C8888){ + psm = PSMCT32; + tcc = 1; // RGBA + }else if(pixelformat == Raster::C888){ + psm = PSMCT24; + tcc = 0; // RGB + }else if(pixelformat == Raster::C1555){ + psm = PSMCT16S; + tcc = 1; // RGBA + }else + // can't happen, sanity check in getRasterFormat + return nil;; + } + + for(int i = 0; i < 7; i++){ + bufferWidth[i] = 1; + bufferBase[i] = 0; + } + + int32 mipw, miph; + int32 w, h; + int32 n; + int32 nPagW, nPagH; + raster->stride = width*depth/8; + + if(raster->format & Raster::MIPMAP){ + // NOTE: much of this code seems to be totally useless. + // calcOffsets overwrites what we calculate here. I wonder + // why this code even is in RW. Maybe it's older code that used + // the GS' automatic base pointer calculation? + + // see the left columns in the maps above + static uint32 blockOffset32_24_8[8] = { 0, 2, 2, 8, 8, 10, 10, 32 }; + static uint32 blockOffset16_4[8] = { 0, 1, 4, 5, 16, 17, 20, 21 }; + static uint32 blockOffset16S[8] = { 0, 1, 8, 9, 4, 5, 12, 13 }; + uint64 lastBufferWidth; + mipw = width; + miph = height; + lastBufferWidth = max(pageWidth, width)/64; + ras->pixelSize = 0; + int32 lastaddress = 0; // word address + int32 nextaddress = 0; // word address + int32 stride; // in bytes + for(n = 0; mipw != 0 && miph != 0 && n < maxMipLevels; n++){ + if(width >= 8 && height >= 8 && (mipw < 8 || miph < 8)) + break; + ras->pixelSize += ALIGN64(mipw*miph*depth/8); + bufferWidth[n] = max(pageWidth, mipw)/64; + stride = bufferWidth[n]*64*depth/8; + + // If buffer width changes, align next address to page + if(bufferWidth[n] != lastBufferWidth){ + nPagW = ((width >> n-1) + pageWidth-1)/pageWidth; + nPagH = ((height >> n-1) + pageHeight-1)/pageHeight; + nextaddress = (lastaddress + nPagW*nPagH*WD2PG) & ~(WD2PG-1); + } + lastBufferWidth = bufferWidth[n]; + nextaddress = ALIGN64(nextaddress); // this should already be the case... + uint32 b = nextaddress>>(11-3) & 7; // upper three bits of block-in-page address + switch(psm){ + case PSMCT32: + case PSMCT24: + case PSMT8: + b = blockOffset32_24_8[b]; + break; + case PSMCT16: + case PSMT4: + b = blockOffset16_4[b]; + break; + case PSMCT16S: + b = blockOffset16S[b]; + break; + default: + // can't happen + break; + } + // shift to page address, then to block address and add offset inside page + bufferBase[n] = b + (nextaddress>>11 << 5); + + lastaddress = nextaddress; + nextaddress = ALIGN64(miph*stride/4 + lastaddress); + + mipw /= 2; + miph /= 2; + } + + // Do the real work here + uint32 paletteBase; + uint32 totalSize; + calcOffsets(width, height, psm, bufferBase, bufferWidth, trxpos_hi, &totalSize, &paletteBase); + + ras->paletteSize = paletteWidth*paletteHeight*paletteDepth; + ras->miptbp1 = + bufferWidth[1]<<14 | (bufferBase[1] & 0x3FFF)<<0 + | bufferWidth[2]<<34 | (bufferBase[2] & 0x3FFF)<<20 + | bufferWidth[3]<<54 | (bufferBase[3] & 0x3FFF)<<40; + ras->miptbp2 = + bufferWidth[4]<<14 | (bufferBase[4] & 0x3FFF)<<0 + | bufferWidth[5]<<34 | (bufferBase[5] & 0x3FFF)<<20 + | bufferWidth[6]<<54 | (bufferBase[6] & 0x3FFF)<<40; + ras->tex1low = (n-1)<<2; + ras->totalSize = totalSize; + if(ras->paletteSize){ + ras->paletteBase = paletteBase; + if(ras->paletteBase*64 == ras->totalSize) + ras->totalSize += WD2PG; + }else + ras->paletteBase = 0; + }else{ + // No mipmaps + + ras->pixelSize = ALIGN16(raster->stride*raster->height); + ras->paletteSize = paletteWidth*paletteHeight*paletteDepth; + ras->miptbp1 = 1ULL<<54 | 1ULL<<34 | 1ULL<<14; + ras->miptbp2 = 1ULL<<54 | 1ULL<<34 | 1ULL<<14; + ras->tex1low = 0; // one mipmap level + + // find out number of pages needed + nPagW = (width + pageWidth-1)/pageWidth; + nPagH = (height + pageHeight-1)/pageHeight; + + // calculate buffer width in units of pixels/64 + bufferBase[0] = 0; + trxpos_hi[0] = 0; + bufferWidth[0] = nPagW*pageWidth / 64; + + // calculate whole buffer size in words + ras->totalSize = nPagW*nPagH*WD2PG; + + // calculate palette offset on GS in units of words/64 + if(ras->paletteSize){ + // Maximum palette size is 256 words. + // If there is still room, use it! + // If dimensions don't fill a page, we have at least + // half a page left, enough for any palette +//TODO: this was not always done it seems but even gta3's 3.1 seems to?? + if(pageWidth*nPagW > width || + pageHeight*nPagH > height){ + ras->paletteBase = (ras->totalSize - 256) / WD2BLK; + }else{ + // Otherwise allocate more space... + ras->paletteBase = ras->totalSize / WD2BLK; + // ...using the same calculation as above. + // WHY? we never need more than one page! + nPagW = (paletteWidth + palettePagewidth-1)/palettePagewidth; + nPagH = (paletteHeight + palettePageheight-1)/palettePageheight; + ras->totalSize += nPagW*nPagH*WD2PG; + } + }else + ras->paletteBase = 0; + } + ras->tex0 = tbw << 14 | + psm << 20 | + tw << 26 | + th << 30 | + tcc << 34 | + cpsm << 51 | + 0ULL << 55 | // csm0 + 0ULL << 56 | // entry offset + cld << 61; + + + + // allocate data and fill with GIF packets + ras->pixelSize = ALIGN16(ras->pixelSize); + int32 numLevels = MAXLEVEL(ras)+1; + // No GIF packet because we either don't want it (pre 0x310 rasters) + // or the data wouldn't fit into a DMA packet + if(noNewStyleRasters || + (raster->width*raster->height*raster->depth/8/0x10) >= 0x7FFF){ + ras->dataSize = ras->paletteSize+ras->pixelSize; + uint8 *data = (uint8*)mallocalign(ras->dataSize, 0x40); + assert(data); + ras->data = data; + raster->pixels = data; + if(ras->paletteSize) + raster->palette = data + ras->pixelSize; + if(raster->depth == 8) + ras->flags |= Ps2Raster::SWIZZLED8; + }else{ + ras->flags |= Ps2Raster::NEWSTYLE; + uint64 paltrxpos = 0; + uint32 dsax = trxpos_hi[numLevels-1] & 0x7FF; + uint32 dsay = trxpos_hi[numLevels-1]>>16 & 0x7FF; + // Set swizzle flags and calculate TRXPOS for palette + if(psm == PSMT8){ + ras->flags |= Ps2Raster::SWIZZLED8; + if(cpsm == PSMCT32 && bufferWidth[numLevels-1] == 2){ // one page + // unswizzle the starting block of the last buffer and palette + uint32 bufbase_B = bufferBase[numLevels-1]&~0x1F | (uint64)blockmaprev_PSMCT32[bufferBase[numLevels-1]&0x1F]; + uint32 palbase_B = ras->paletteBase&~0x1F | (uint64)blockmaprev_PSMCT32[ras->paletteBase&0x1F]; + // find start of page of last level (16,16 are PSMT8 block dimensions) + uint32 page_B = bufbase_B - 8*(dsay/16) - dsax/16; + // find palette DSAX/Y (in PSMCT32!) + dsay = (palbase_B - page_B)/8 * 8; // block/blocksPerPageX * blockHeight + dsax = (palbase_B - page_B)*8 % 64; // block*blockWidth % pageWidth + if(dsay < 0x800) + paltrxpos = dsay<<16 | dsax; + } + } + if(psm == PSMT4){ + // swizzle flag depends on version :/ + // but which version? .... + if(rw::version > 0x31000){ + ras->flags |= Ps2Raster::SWIZZLED4; + // Where can this come from? if anything we're using PSMCT16S + // Looks like they wanted to swizzle palettes too... + if(cpsm == PSMCT16){ + // unswizzle the starting block of the last buffer and palette + uint32 bufbase_B = bufferBase[numLevels-1]&~0x1F | (uint64)blockmaprev_PSMCT16[bufferBase[numLevels-1]&0x1F]; + uint32 palbase_B = ras->paletteBase&~0x1F | (uint64)blockmaprev_PSMCT16[ras->paletteBase&0x1F]; + // find start of page of last level (32,16 are PSMT4 block dimensions) + uint32 page_B = bufbase_B - 4*(dsay/32) - dsax/16; + // find palette DSAX/Y (in PSMCT16!) + dsay = (palbase_B - page_B)/4 * 8; // block/blocksPerPageX * blockHeight + dsax = (palbase_B - page_B)*16 % 128; // block*blockWidth % pageWidth + if(dsay < 0x800) + paltrxpos = dsay<<16 | dsax; + } + } + } + ras->pixelSize = 0x50*numLevels; // GIF packets + int32 minW, minH; + transferMinSize(psm, ras->flags, &minW, &minH); + w = raster->width; + h = raster->height; + n = numLevels; + while(n--){ + mipw = max(w, minW); + miph = max(h, minH); + ras->pixelSize += ALIGN16(mipw*miph*raster->depth/8); + w /= 2; + h /= 2; + } + if(ras->paletteSize){ + if(rw::version > 0x31000 && paletteHeight == 2) + paletteHeight = 3; + ras->paletteSize = 0x50 + + paletteDepth*paletteWidth*paletteHeight; + } + // One transfer per buffer width, 4 qwords: + // DMAcnt(2) [NOP, DIRECT] + // GIF tag A+D + // BITBLTBUF + // DMAref(pixel data) [NOP, DIRECT] + uint32 extrasize = 0x10; // PixelPtr + int32 numTransfers = 0; + for(n = 0; n < numLevels; n++) + if(trxpos_hi[n] == 0){ + extrasize += 0x40; + numTransfers++; + } + if(ras->paletteSize){ + extrasize += 0x40; + numTransfers++; + } + // What happens here? + if(ras->paletteSize && paltrxpos == 0) + ras->dataSize = ALIGN(ras->pixelSize,128) + ALIGN(ras->paletteSize,64) + extrasize + 0x70; + else + ras->dataSize = ALIGN(ras->paletteSize+ras->pixelSize,64) + extrasize + 0x70; + uint8 *data = (uint8*)mallocalign(ras->dataSize, 0x40); + uint32 *xferchain = (uint32*)(data + 0x10); + assert(data); + ras->data = data; + Ps2Raster::PixelPtr *pp = (Ps2Raster::PixelPtr*)data; + pp->numTransfers = numTransfers; + pp->numTotalTransfers = numTransfers; + pp->pixels = (uint8*)ALIGN((uintptr)data + extrasize, 128); + raster->pixels = (uint8*)pp; + if(ras->paletteSize) + raster->palette = pp->pixels + ALIGN(ras->pixelSize, 128) + 0x50; + uint32 *p = (uint32*)pp->pixels; + w = raster->width; + h = raster->height; + for(n = 0; n < numLevels; n++){ + mipw = max(w, minW); + miph = max(h, minH); + + // GIF tag + *p++ = 3; // NLOOP = 3 + *p++ = 0x10000000; // NREG = 1 + *p++ = 0xE; // A+D + *p++ = 0; + + // TRXPOS + if(ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8 || + ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4){ + *p++ = 0; // SSAX/Y is always 0 + *p++ = (trxpos_hi[n] & ~0x10001)/2; // divide both DSAX/Y by 2 + }else{ + *p++ = 0; + *p++ = trxpos_hi[n]; + } + *p++ = 0x51; + *p++ = 0; + + // TRXREG + if(ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8 || + ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4){ + *p++ = mipw/2; + *p++ = miph/2; + }else{ + *p++ = mipw; + *p++ = miph; + } + *p++ = 0x52; + *p++ = 0; + + // TRXDIR + *p++ = 0; // host -> local + *p++ = 0; + *p++ = 0x53; + *p++ = 0; + + // GIF tag + uint32 sz = ALIGN16(mipw*miph*raster->depth/8)/16; + *p++ = sz & 0x7FFF; + *p++ = 0x08000000; // IMAGE + *p++ = 0; + *p++ = 0; + + if(trxpos_hi[n] == 0){ + // Add a transfer, see above for layout + + *xferchain++ = 0x10000002; // DMAcnt, 2 qwords + *xferchain++ = 0; + *xferchain++ = 0; // VIF nop + *xferchain++ = 0x50000002; // VIF DIRECT 2 qwords + + // GIF tag + *xferchain++ = 1; // NLOOP = 1 + *xferchain++ = 0x10000000; // NREG = 1 + *xferchain++ = 0xE; // A+D + *xferchain++ = 0; + + // BITBLTBUF + if(ras->flags & Ps2Raster::SWIZZLED8 && psm == PSMT8){ + // PSMT8 is swizzled to PSMCT32 and dimensions are halved + *xferchain++ = PSMCT32<<24 | bufferWidth[n]/2<<16; // src buffer + *xferchain++ = PSMCT32<<24 | bufferWidth[n]/2<<16 | bufferBase[n]; // dst buffer + }else if(ras->flags & Ps2Raster::SWIZZLED4 && psm == PSMT4){ + // PSMT4 is swizzled to PSMCT16 and dimensions are halved + *xferchain++ = PSMCT16<<24 | bufferWidth[n]/2<<16; // src buffer + *xferchain++ = PSMCT16<<24 | bufferWidth[n]/2<<16 | bufferBase[n]; // dst buffer + }else{ + *xferchain++ = psm<<24 | bufferWidth[n]<<16; // src buffer + *xferchain++ = psm<<24 | bufferWidth[n]<<16 | bufferBase[n]; // dst buffer + } + *xferchain++ = 0x50; + *xferchain++ = 0; + + *xferchain++ = 0x30000000 | sz+5; // DMAref + // this obviously only works with 32 bit pointers, but it's only needed on the PS2 anyway + *xferchain++ = (uint32)(uintptr)p - 0x50; + *xferchain++ = 0; // VIF nop + *xferchain++ = 0x50000000 | sz+5; // VIF DIRECT 2 qwords + }else{ + // Add to existing transfer + xferchain[-4] = 0x30000000 | (xferchain[-4]&0xFFFF) + sz+5; // last DMAref + xferchain[-1] = 0x50000000 | (xferchain[-1]&0xFFFF) + sz+5; // last DIRECT + } + + p += sz*4; + w /= 2; + h /= 2; + } + + if(ras->paletteSize){ + // huh? + if(paltrxpos) + raster->palette = (uint8*)p + 0x50; + p = (uint32*)(raster->palette - 0x50); + // GIF tag + *p++ = 3; // NLOOP = 3 + *p++ = 0x10000000; // NREG = 1 + *p++ = 0xE; // A+D + *p++ = 0; + + // TRXPOS + *(uint64*)p = paltrxpos; + p += 2; + *p++ = 0x51; + *p++ = 0; + + // TRXREG + *p++ = paletteWidth; + *p++ = paletteHeight; + *p++ = 0x52; + *p++ = 0; + + // TRXDIR + *p++ = 0; // host -> local + *p++ = 0; + *p++ = 0x53; + *p++ = 0; + + // GIF tag + uint32 sz = ALIGN16(ras->paletteSize - 0x50)/16; + *p++ = sz & 0x7FFF; + *p++ = 0x08000000; // IMAGE + *p++ = 0; + *p++ = 0; + + // Transfer + *xferchain++ = 0x10000002; // DMAcnt, 2 qwords + *xferchain++ = 0; + *xferchain++ = 0; // VIF nop + *xferchain++ = 0x50000002; // VIF DIRECT 2 qwords + + // GIF tag + *xferchain++ = 1; // NLOOP = 1 + *xferchain++ = 0x10000000; // NREG = 1 + *xferchain++ = 0xE; // A+D + *xferchain++ = 0; + + // BITBLTBUF + if(paltrxpos == 0){ + *xferchain++ = cpsm<<24 | 1<<16; // src buffer + *xferchain++ = cpsm<<24 | 1<<16 | ras->paletteBase; // dst buffer + *xferchain++ = 0x50; + *xferchain++ = 0; + }else{ + // copy last pixel bitbltbuf...if uploading palette separately it's still the same buffer + xferchain[0] = xferchain[-16]; + xferchain[1] = xferchain[-15]; + xferchain[2] = xferchain[-14]; + xferchain[3] = xferchain[-13]; + // Add to last transfer + xferchain[-16] = 0x30000000 | (xferchain[-16]&0xFFFF) + sz+5; // last DMAref + xferchain[-13] = 0x50000000 | (xferchain[-13]&0xFFFF) + sz+5; // last DIRECT + xferchain += 4; + pp->numTransfers--; + } + + *xferchain++ = 0x30000000 | sz+5; // DMAref + // this obviously only works with 32 bit pointers, but it's only needed on the PS2 anyway + *xferchain++ = (uint32)(uintptr)p - 0x50; + *xferchain++ = 0; // VIF nop + *xferchain++ = 0x50000000 | sz+5; // VIF DIRECT 2 qwords + } + } + raster->originalPixels = raster->pixels; + raster->originalStride = raster->stride; + if(ras->flags & Ps2Raster::NEWSTYLE) + raster->pixels = ((Ps2Raster::PixelPtr*)raster->pixels)->pixels + 0x50; + return raster; +} + +Raster* +rasterCreate(Raster *raster) +{ + if(!getRasterFormat(raster)) + return nil; + + // init raster + raster->pixels = nil; + raster->palette = nil; + raster->originalWidth = raster->width; + raster->originalHeight = raster->height; + raster->originalPixels = raster->pixels; + if(raster->width == 0 || raster->height == 0){ + raster->flags = Raster::DONTALLOCATE; + raster->stride = 0; + raster->originalStride = 0; + return raster; + } + + switch(raster->type){ + case Raster::NORMAL: + case Raster::TEXTURE: + return rasterCreateTexture(raster); + case Raster::ZBUFFER: + // TODO. only RW_PS2 + // get info from video mode + raster->flags = Raster::DONTALLOCATE; + return raster; + case Raster::CAMERA: + // TODO. only RW_PS2 + // get info from video mode + raster->flags = Raster::DONTALLOCATE; + return raster; + case Raster::CAMERATEXTURE: + // TODO. only RW_PS2 + // check width/height and fall through to texture + return nil; + } + return nil; +} + +static uint32 +swizzle(uint32 x, uint32 y, uint32 logw) +{ +#define X(n) ((x>>(n))&1) +#define Y(n) ((y>>(n))&1) + + uint32 nx, ny, n; + x ^= (Y(1)^Y(2))<<2; + nx = x&7 | (x>>1)&~7; + ny = y&1 | (y>>1)&~1; + n = Y(1) | X(3)<<1; + return n | nx<<2 | ny<format & (Raster::PAL4|Raster::PAL8)) == 0) + return; + + int minw, minh; + transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + w = max(raster->width, minw); + h = max(raster->height, minh); + px = raster->pixels; + logw = 0; + for(i = 1; i < w; i *= 2) logw++; + mask = (1<format & Raster::PAL4 && natras->flags & Ps2Raster::SWIZZLED4){ + for(y = 0; y < h; y += 4){ + memcpy(tmpbuf, &px[y<>1] >> 4 : tmpbuf[s>>1] & 0xF; + px[a>>1] = a & 1 ? px[a>>1]&0xF | c<<4 : px[a>>1]&0xF0 | c; + } + } + }else if(raster->format & Raster::PAL8 && natras->flags & Ps2Raster::SWIZZLED8){ + for(y = 0; y < h; y += 4){ + memcpy(tmpbuf, &px[y<format & (Raster::PAL4|Raster::PAL8)) == 0) + return; + + int minw, minh; + transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + w = max(raster->width, minw); + h = max(raster->height, minh); + px = raster->pixels; + logw = 0; + for(i = 1; i < raster->width; i *= 2) logw++; + mask = (1<format & Raster::PAL4 && natras->flags & Ps2Raster::SWIZZLED4){ + for(y = 0; y < h; y += 4){ + for(i = 0; i < 4; i++) + for(x = 0; x < w; x++){ + uint32 a = (y+i<>1] >> 4 : px[a>>1] & 0xF; + tmpbuf[s>>1] = s & 1 ? tmpbuf[s>>1]&0xF | c<<4 : tmpbuf[s>>1]&0xF0 | c; + } + memcpy(&px[y<format & Raster::PAL8 && natras->flags & Ps2Raster::SWIZZLED8){ + for(y = 0; y < h; y += 4){ + for(i = 0; i < 4; i++) + for(x = 0; x < w; x++){ + uint32 a = (y+i<depth != 24); + + if(level > 0){ + int32 minw, minh; + int32 mipw, miph; + transferMinSize(raster->format & Raster::PAL4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + while(level--){ + mipw = max(raster->width, minw); + miph = max(raster->height, minh); + raster->pixels += ALIGN16(mipw*miph*raster->depth/8) + 0x50; + raster->width /= 2; + raster->height /= 2; + } + } + + if((lockMode & Raster::LOCKNOFETCH) == 0) + unswizzleRaster(raster); + if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ; + if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE; + return raster->pixels; +} + +void +rasterUnlock(Raster *raster, int32 level) +{ + Ps2Raster *natras = PLUGINOFFSET(Ps2Raster, raster, nativeRasterOffset); + if(raster->format & (Raster::PAL4 | Raster::PAL8)) + swizzleRaster(raster); + + raster->width = raster->originalWidth; + raster->height = raster->originalHeight; + raster->pixels = raster->originalPixels; + raster->stride = raster->originalStride; + if(natras->flags & Ps2Raster::NEWSTYLE) + raster->pixels = ((Ps2Raster::PixelPtr*)raster->pixels)->pixels + 0x50; + + raster->privateFlags &= ~(Raster::PRIVATELOCK_READ|Raster::PRIVATELOCK_WRITE); + // TODO: generate mipmaps +} + +static void +convertCSM1_16(uint32 *pal) +{ + int i, j; + uint32 tmp; + for(i = 0; i < 256; i++) + // palette index bits 0x08 and 0x10 are flipped + if((i & 0x18) == 0x10){ + j = i ^ 0x18; + tmp = pal[i]; + pal[i] = pal[j]; + pal[j] = tmp; + } +} + +static void +convertCSM1_32(uint32 *pal) +{ + int i, j; + uint32 tmp; + for(i = 0; i < 256; i++) + // palette index bits 0x08 and 0x10 are flipped + if((i & 0x18) == 0x10){ + j = i ^ 0x18; + tmp = pal[i]; + pal[i] = pal[j]; + pal[j] = tmp; + } +} + +static void +convertPalette(Raster *raster) +{ + if(raster->format & Raster::PAL8){ + if((raster->format & 0xF00) == Raster::C8888) + convertCSM1_32((uint32*)raster->palette); + else if((raster->format & 0xF00) == Raster::C8888) + convertCSM1_16((uint32*)raster->palette); + } +} + +// NB: RW doesn't convert the palette when locking/unlocking +uint8* +rasterLockPalette(Raster *raster, int32 lockMode) +{ + if((raster->format & (Raster::PAL4 | Raster::PAL8)) == 0) + return nil; + if((lockMode & Raster::LOCKNOFETCH) == 0) + convertPalette(raster); + if(lockMode & Raster::LOCKREAD) raster->privateFlags |= Raster::PRIVATELOCK_READ_PALETTE; + if(lockMode & Raster::LOCKWRITE) raster->privateFlags |= Raster::PRIVATELOCK_WRITE_PALETTE; + return raster->palette; +} + +void +rasterUnlockPalette(Raster *raster) +{ + if(raster->format & (Raster::PAL4 | Raster::PAL8)) + convertPalette(raster); + raster->privateFlags &= ~(Raster::PRIVATELOCK_READ_PALETTE|Raster::PRIVATELOCK_WRITE_PALETTE); +} + +// Almost the same as d3d9 and gl3 function +bool32 +imageFindRasterFormat(Image *img, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat) +{ + int32 width, height, depth, format; + + assert((type&0xF) == Raster::TEXTURE); + + for(width = 1; width < img->width; width <<= 1); + for(height = 1; height < img->height; height <<= 1); + + depth = img->depth; + + switch(depth){ + case 32: + case 24: + // C888 24 bit is unsafe + format = Raster::C8888; + depth = 32; + break; + case 16: + format = Raster::C1555; + break; + case 8: + format = Raster::PAL8 | Raster::C8888; + break; + case 4: + format = Raster::PAL4 | Raster::C8888; + break; + default: + RWERROR((ERR_INVRASTER)); + return 0; + } + + format |= type; + + *pWidth = width; + *pHeight = height; + *pDepth = depth; + *pFormat = format; + + return 1; +} + +bool32 +rasterFromImage(Raster *raster, Image *image) +{ + Ps2Raster *natras = PLUGINOFFSET(Ps2Raster, raster, nativeRasterOffset); + + int32 pallength = 0; + switch(image->depth){ + case 24: + case 32: + if(raster->format != Raster::C8888 && + raster->format != Raster::C888) // unsafe already + goto err; + break; + case 16: + if(raster->format != Raster::C1555) goto err; + break; + case 8: + if(raster->format != (Raster::PAL8 | Raster::C8888)) goto err; + pallength = 256; + break; + case 4: + if(raster->format != (Raster::PAL4 | Raster::C8888)) goto err; + pallength = 16; + break; + default: + err: + RWERROR((ERR_INVRASTER)); + return 0; + } + + // unsafe + if((raster->format&0xF00) == Raster::C888){ + RWERROR((ERR_INVRASTER)); + return 0; + } + + uint8 *in, *out; + if(image->depth <= 8){ + in = image->palette; + out = raster->lockPalette(Raster::LOCKWRITE|Raster::LOCKNOFETCH); + memcpy(out, in, 4*pallength); + for(int32 i = 0; i < pallength; i++){ + out[3] = out[3]*128/255; + out += 4; + } + raster->unlockPalette(); + } + + int minw, minh; + int tw; + transferMinSize(image->depth == 4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + tw = max(image->width, minw); + uint8 *src = image->pixels; + out = raster->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + if(image->depth == 4){ + compressPal4(out, tw/2, src, image->stride, image->width, image->height); + }else if(image->depth == 8){ + copyPal8(out, tw, src, image->stride, image->width, image->height); + }else{ + for(int32 y = 0; y < image->height; y++){ + in = src; + for(int32 x = 0; x < image->width; x++){ + switch(image->depth){ + case 16: + conv_ARGB1555_from_ABGR1555(out, in); + out += 2; + break; + case 24: + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = 0x80; + out += 4; + break; + case 32: + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]*128/255; + out += 4; + break; + } + in += image->bpp; + } + src += image->stride; + } + } + raster->unlock(0); + return 1; +} + +Image* +rasterToImage(Raster *raster) +{ + Image *image; + int depth; + Ps2Raster *natras = PLUGINOFFSET(Ps2Raster, raster, nativeRasterOffset); + + int32 rasterFormat = raster->format & 0xF00; + switch(rasterFormat){ + case Raster::C1555: + depth = 16; + break; + case Raster::C8888: + depth = 32; + break; + case Raster::C888: + depth = 24; + break; + case Raster::C555: + depth = 16; + break; + + default: + case Raster::C565: + case Raster::C4444: + case Raster::LUM8: + assert(0 && "unsupported ps2 raster format"); + } + int32 pallength = 0; + if((raster->format & Raster::PAL4) == Raster::PAL4){ + depth = 4; + pallength = 16; + }else if((raster->format & Raster::PAL8) == Raster::PAL8){ + depth = 8; + pallength = 256; + } + + uint8 *in, *out; + image = Image::create(raster->width, raster->height, depth); + image->allocate(); + + if(pallength){ + out = image->palette; + in = raster->lockPalette(Raster::LOCKREAD); + if(rasterFormat == Raster::C8888){ + memcpy(out, in, pallength*4); + for(int32 i = 0; i < pallength; i++){ + out[3] = out[3]*255/128; + out += 4; + } + }else + memcpy(out, in, pallength*2); + raster->unlockPalette(); + } + + int minw, minh; + int tw; + transferMinSize(depth == 4 ? PSMT4 : PSMT8, natras->flags, &minw, &minh); + tw = max(raster->width, minw); + uint8 *dst = image->pixels; + in = raster->lock(0, Raster::LOCKREAD); + if(depth == 4){ + expandPal4(dst, image->stride, in, tw/2, raster->width, raster->height); + }else if(depth == 8){ + copyPal8(dst, image->stride, in, tw, raster->width, raster->height); + }else{ + for(int32 y = 0; y < image->height; y++){ + out = dst; + for(int32 x = 0; x < image->width; x++){ + switch(raster->format & 0xF00){ + case Raster::C8888: + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]*255/128; + in += 4; + break; + case Raster::C888: + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + in += 4; + break; + case Raster::C1555: + conv_ARGB1555_from_ABGR1555(out, in); + in += 2; + break; + case Raster::C555: + conv_ARGB1555_from_ABGR1555(out, in); + out[1] |= 0x80; + in += 2; + break; + default: + assert(0 && "unknown ps2 raster format"); + break; + } + out += image->bpp; + } + dst += image->stride; + } + } + raster->unlock(0); + + return image; +} + +int32 +rasterNumLevels(Raster *raster) +{ + Ps2Raster *ras = PLUGINOFFSET(Ps2Raster, raster, nativeRasterOffset); + if(raster->pixels == nil) return 0; + if(raster->format & Raster::MIPMAP) + return MAXLEVEL(ras)+1; + return 1; +} + +static void* +createNativeRaster(void *object, int32 offset, int32) +{ + Ps2Raster *raster = PLUGINOFFSET(Ps2Raster, object, offset); + raster->tex0 = 0; + raster->paletteBase = 0; + raster->kl = defaultMipMapKL; + raster->tex1low = 0; + raster->unk2 = 0; + raster->miptbp1 = 0; + raster->miptbp2 = 0; + raster->pixelSize = 0; + raster->paletteSize = 0; + raster->totalSize = 0; + raster->flags = 0; + + raster->dataSize = 0; + raster->data = nil; + return object; +} + +static void* +destroyNativeRaster(void *object, int32 offset, int32) +{ + Ps2Raster *raster = PLUGINOFFSET(Ps2Raster, object, offset); + freealign(raster->data); + return object; +} + +static void* +copyNativeRaster(void *dst, void *src, int32 offset, int32) +{ + Ps2Raster *dstraster = PLUGINOFFSET(Ps2Raster, dst, offset); + Ps2Raster *srcraster = PLUGINOFFSET(Ps2Raster, src, offset); + *dstraster = *srcraster; + return dst; +} + +static Stream* +readMipmap(Stream *stream, int32, void *object, int32 offset, int32) +{ + uint16 val = stream->readI32(); + Texture *tex = (Texture*)object; + if(tex->raster == nil) + return stream; + Ps2Raster *raster = PLUGINOFFSET(Ps2Raster, tex->raster, offset); + raster->kl = val; + return stream; +} + +static Stream* +writeMipmap(Stream *stream, int32, void *object, int32 offset, int32) +{ + Texture *tex = (Texture*)object; + if(tex->raster){ + stream->writeI32(defaultMipMapKL); + return stream; + } + Ps2Raster *raster = PLUGINOFFSET(Ps2Raster, tex->raster, offset); + stream->writeI32(raster->kl); + return stream; +} + +static int32 +getSizeMipmap(void*, int32, int32) +{ + return rw::platform == PLATFORM_PS2 ? 4 : 0; +} + +void +registerNativeRaster(void) +{ + nativeRasterOffset = Raster::registerPlugin(sizeof(Ps2Raster), + ID_RASTERPS2, + createNativeRaster, + destroyNativeRaster, + copyNativeRaster); + + Texture::registerPlugin(0, ID_SKYMIPMAP, nil, nil, nil); + Texture::registerPluginStream(ID_SKYMIPMAP, readMipmap, writeMipmap, getSizeMipmap); +} + +void +printTEX0(uint64 tex0) +{ + printf("%016llX ", tex0); + uint32 tbp0 = tex0 & 0x3FFF; tex0 >>= 14; + uint32 tbw = tex0 & 0x3F; tex0 >>= 6; + uint32 psm = tex0 & 0x3F; tex0 >>= 6; + uint32 tw = tex0 & 0xF; tex0 >>= 4; + uint32 th = tex0 & 0xF; tex0 >>= 4; + uint32 tcc = tex0 & 0x1; tex0 >>= 1; + uint32 tfx = tex0 & 0x3; tex0 >>= 2; + uint32 cbp = tex0 & 0x3FFF; tex0 >>= 14; + uint32 cpsm = tex0 & 0xF; tex0 >>= 4; + uint32 csm = tex0 & 0x1; tex0 >>= 1; + uint32 csa = tex0 & 0x1F; tex0 >>= 5; + uint32 cld = tex0 & 0x7; + printf("TBP0:%4X TBW:%2X PSM:%2X TW:%X TH:%X TCC:%X TFX:%X CBP:%4X CPSM:%X CSM:%X CSA:%2X CLD:%X\n", + tbp0, tbw, psm, tw, th, tcc, tfx, cbp, cpsm, csm, csa, cld); +} + +void +printTEX1(uint64 tex1) +{ + printf("%016llX ", tex1); + uint32 lcm = tex1 & 0x1; tex1 >>= 2; + uint32 mxl = tex1 & 0x7; tex1 >>= 3; + uint32 mmag = tex1 & 0x1; tex1 >>= 1; + uint32 mmin = tex1 & 0x7; tex1 >>= 3; + uint32 mtba = tex1 & 0x1; tex1 >>= 10; + uint32 l = tex1 & 0x3; tex1 >>= 13; + uint32 k = tex1 & 0xFFF; + printf("LCM:%X MXL:%X MMAG:%X MMIN:%X MTBA:%X L:%X K:%X\n", + lcm, mxl, mmag, mmin, mtba, l, k); +} + +void +calcTEX1(Raster *raster, uint64 *tex1, int32 filter) +{ + enum { + NEAREST = 0, + LINEAR, + NEAREST_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_NEAREST, + LINEAR_MIPMAP_LINEAR, + }; + Ps2Raster *natras = PLUGINOFFSET(Ps2Raster, raster, nativeRasterOffset); + uint64 t1 = natras->tex1low; + uint64 k = natras->kl & 0xFFF; + uint64 l = (natras->kl >> 12) & 0x3; + t1 |= k << 32; + t1 |= l << 19; + switch(filter){ + case Texture::NEAREST: + t1 |= (NEAREST << 5) | + (NEAREST << 6); + break; + case Texture::LINEAR: + t1 |= (LINEAR << 5) | + (LINEAR << 6); + break; + case Texture::MIPNEAREST: + t1 |= (NEAREST << 5) | + (NEAREST_MIPMAP_NEAREST << 6); + break; + case Texture::MIPLINEAR: + t1 |= (LINEAR << 5) | + (LINEAR_MIPMAP_NEAREST << 6); + break; + case Texture::LINEARMIPNEAREST: + t1 |= (NEAREST << 5) | + (NEAREST_MIPMAP_LINEAR << 6); + break; + case Texture::LINEARMIPLINEAR: + t1 |= (LINEAR << 5) | + (LINEAR_MIPMAP_LINEAR << 6); + break; + } + *tex1 = t1; +} + +struct StreamRasterExt +{ + int32 width; + int32 height; + int32 depth; + uint16 rasterFormat; + int16 version; + uint64 tex0; + uint32 paletteOffset; + uint32 tex1low; + uint64 miptbp1; + uint64 miptbp2; + uint32 pixelSize; + uint32 paletteSize; + uint32 totalSize; + uint32 mipmapVal; +}; + +Texture* +readNativeTexture(Stream *stream) +{ + uint32 length, oldversion, version; + uint32 fourcc; + Raster *raster; + Ps2Raster *natras; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + fourcc = stream->readU32(); + if(fourcc != FOURCC_PS2){ + RWERROR((ERR_PLATFORM, fourcc)); + return nil; + } + Texture *tex = Texture::create(nil); + if(tex == nil) + return nil; + + // Texture + tex->filterAddressing = stream->readU32(); + if(!findChunk(stream, ID_STRING, &length, nil)){ + RWERROR((ERR_CHUNK, "STRING")); + goto fail; + } + stream->read8(tex->name, length); + if(!findChunk(stream, ID_STRING, &length, nil)){ + RWERROR((ERR_CHUNK, "STRING")); + goto fail; + } + stream->read8(tex->mask, length); + + // Raster + StreamRasterExt streamExt; + oldversion = rw::version; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + if(!findChunk(stream, ID_STRUCT, nil, &version)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + ASSERTLITTLE; + stream->read8(&streamExt, 0x40); +/* +printf("%X %X %X %X %X %016llX %X %X %016llX %016llX %X %X %X %X\n", +streamExt.width, +streamExt.height, +streamExt.depth, +streamExt.rasterFormat, +streamExt.version, +streamExt.tex0, +streamExt.paletteOffset, +streamExt.tex1low, +streamExt.miptbp1, +streamExt.miptbp2, +streamExt.pixelSize, +streamExt.paletteSize, +streamExt.totalSize, +streamExt.mipmapVal); +*/ + noNewStyleRasters = streamExt.version < 2; + rw::version = version; + raster = Raster::create(streamExt.width, streamExt.height, + streamExt.depth, streamExt.rasterFormat, + PLATFORM_PS2); + noNewStyleRasters = 0; + rw::version = oldversion; + tex->raster = raster; + natras = PLUGINOFFSET(Ps2Raster, raster, nativeRasterOffset); +//printf("%X %X\n", natras->paletteBase, natras->tex1low); +// printf("%08X%08X %08X%08X %08X%08X\n", +// (uint32)natras->tex0, (uint32)(natras->tex0>>32), +// (uint32)natras->miptbp1, (uint32)(natras->miptbp1>>32), +// (uint32)natras->miptbp2, (uint32)(natras->miptbp2>>32)); +// printTEX0(natras->tex0); + uint64 tex1; + calcTEX1(raster, &tex1, tex->filterAddressing & 0xF); +// printTEX1(tex1); + + // TODO: GTA SA LD_OTB.txd loses here + assert(natras->pixelSize >= streamExt.pixelSize); + assert(natras->paletteSize >= streamExt.paletteSize); + +//if(natras->tex0 != streamExt.tex0){ +//printf("TEX0: %016llX\n %016llX\n", natras->tex0, streamExt.tex0); +//printTEX0(natras->tex0); +//printTEX0(streamExt.tex0); +//fflush(stdout); +//} +//if(natras->tex1low != streamExt.tex1low) +//printf("TEX1: %08X\n %08X\n", natras->tex1low, streamExt.tex1low); +//if(natras->miptbp1 != streamExt.miptbp1) +//printf("MIP1: %016llX\n %016llX\n", natras->miptbp1, streamExt.miptbp1); +//if(natras->miptbp2 != streamExt.miptbp2) +//printf("MIP2: %016llX\n %016llX\n", natras->miptbp2, streamExt.miptbp2); +//if(natras->paletteBase != streamExt.paletteOffset) +//printf("PAL: %08X\n %08X\n", natras->paletteBase, streamExt.paletteOffset); +//if(natras->pixelSize != streamExt.pixelSize) +//printf("PXS: %08X\n %08X\n", natras->pixelSize, streamExt.pixelSize); +//if(natras->paletteSize != streamExt.paletteSize) +//printf("PLS: %08X\n %08X\n", natras->paletteSize, streamExt.paletteSize); +//if(natras->totalSize != streamExt.totalSize) +//printf("TSZ: %08X\n %08X\n", natras->totalSize, streamExt.totalSize); + + // junk addresses, no need to store them + streamExt.tex0 &= ~0x3FFFULL; + streamExt.tex0 &= ~(0x3FFFULL << 37); + + assert(natras->tex0 == streamExt.tex0); + natras->tex0 = streamExt.tex0; + assert(natras->paletteBase == streamExt.paletteOffset); + natras->paletteBase = streamExt.paletteOffset; + assert(natras->tex1low == streamExt.tex1low); + natras->tex1low = streamExt.tex1low; + assert(natras->miptbp1 == streamExt.miptbp1); + natras->miptbp1 = streamExt.miptbp1; + assert(natras->miptbp2 == streamExt.miptbp2); + natras->miptbp2 = streamExt.miptbp2; + assert(natras->pixelSize == streamExt.pixelSize); + natras->pixelSize = streamExt.pixelSize; + assert(natras->paletteSize == streamExt.paletteSize); + natras->paletteSize = streamExt.paletteSize; + assert(natras->totalSize == streamExt.totalSize); + natras->totalSize = streamExt.totalSize; + natras->kl = streamExt.mipmapVal; +//printf("%X %X\n", natras->paletteBase, natras->tex1low); +// printf("%08X%08X %08X%08X %08X%08X\n", +// (uint32)natras->tex0, (uint32)(natras->tex0>>32), +// (uint32)natras->miptbp1, (uint32)(natras->miptbp1>>32), +// (uint32)natras->miptbp2, (uint32)(natras->miptbp2>>32)); +// printTEX0(natras->tex0); + calcTEX1(raster, &tex1, tex->filterAddressing & 0xF); +// printTEX1(tex1); + + // this is weird stuff + if(streamExt.version < 2){ + if(streamExt.version == 1){ + // Version 1 has swizzled 8 bit textures + if(!(natras->flags & Ps2Raster::NEWSTYLE)) + natras->flags |= Ps2Raster::SWIZZLED8; + else + assert(0 && "can't happen"); + }else{ + // Version 0 has no swizzling at all + if(!(natras->flags & Ps2Raster::NEWSTYLE)) + natras->flags &= ~Ps2Raster::SWIZZLED8; + else + assert(0 && "can't happen"); + } + } + + if(!findChunk(stream, ID_STRUCT, &length, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + goto fail; + } + if(streamExt.version < 2){ + stream->read8(raster->pixels, length); + }else{ + stream->read8(((Ps2Raster::PixelPtr*)raster->originalPixels)->pixels, natras->pixelSize); + stream->read8(raster->palette-0x50, natras->paletteSize); + } +//printf("\n"); + return tex; + +fail: + tex->destroy(); + return nil; +} + +void +writeNativeTexture(Texture *tex, Stream *stream) +{ + Raster *raster = tex->raster; + Ps2Raster *ras = PLUGINOFFSET(Ps2Raster, raster, nativeRasterOffset); + writeChunkHeader(stream, ID_STRUCT, 8); + stream->writeU32(FOURCC_PS2); + stream->writeU32(tex->filterAddressing); + int32 len = strlen(tex->name)+4 & ~3; + writeChunkHeader(stream, ID_STRING, len); + stream->write8(tex->name, len); + len = strlen(tex->mask)+4 & ~3; + writeChunkHeader(stream, ID_STRING, len); + stream->write8(tex->mask, len); + + int32 sz = ras->pixelSize + ras->paletteSize; + writeChunkHeader(stream, ID_STRUCT, 12 + 64 + 12 + sz); + writeChunkHeader(stream, ID_STRUCT, 64); + StreamRasterExt streamExt; + streamExt.width = raster->width; + streamExt.height = raster->height; + streamExt.depth = raster->depth; + streamExt.rasterFormat = raster->format | raster->type; + streamExt.version = 0; + if(ras->flags == Ps2Raster::SWIZZLED8 && raster->depth == 8) + streamExt.version = 1; + if(ras->flags & Ps2Raster::NEWSTYLE) + streamExt.version = 2; + streamExt.tex0 = ras->tex0; + streamExt.paletteOffset = ras->paletteBase; + streamExt.tex1low = ras->tex1low; + streamExt.miptbp1 = ras->miptbp1; + streamExt.miptbp2 = ras->miptbp2; + streamExt.pixelSize = ras->pixelSize; + streamExt.paletteSize = ras->paletteSize; + streamExt.totalSize = ras->totalSize; + streamExt.mipmapVal = ras->kl; + ASSERTLITTLE; + stream->write8(&streamExt, 64); + + writeChunkHeader(stream, ID_STRUCT, sz); + if(streamExt.version < 2){ + stream->write8(raster->pixels, sz); + }else{ + stream->write8(((Ps2Raster::PixelPtr*)raster->originalPixels)->pixels, ras->pixelSize); + stream->write8(raster->palette-0x50, ras->paletteSize); + } +} + +uint32 +getSizeNativeTexture(Texture *tex) +{ + uint32 size = 12 + 8; + size += 12 + strlen(tex->name)+4 & ~3; + size += 12 + strlen(tex->mask)+4 & ~3; + size += 12; + size += 12 + 64; + Ps2Raster *ras = PLUGINOFFSET(Ps2Raster, tex->raster, nativeRasterOffset); + size += 12 + ras->pixelSize + ras->paletteSize; + return size; +} + +} +} diff --git a/src/ps2/ps2skin.cpp b/src/ps2/ps2skin.cpp new file mode 100644 index 0000000..d4c9d79 --- /dev/null +++ b/src/ps2/ps2skin.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include + +#include "../rwbase.h" +#include "../rwerror.h" +#include "../rwplg.h" +#include "../rwpipeline.h" +#include "../rwobjects.h" +#include "../rwanim.h" +#include "../rwengine.h" +#include "../rwplugins.h" +#include "rwps2.h" +#include "rwps2plg.h" + +#include "rwps2impl.h" + +#define PLUGIN_ID ID_SKIN + +namespace rw { +namespace ps2 { + +static void* +skinOpen(void *o, int32, int32) +{ + skinGlobals.pipelines[PLATFORM_PS2] = makeSkinPipeline(); + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_PS2])->groupPipeline->destroy(); + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_PS2])->groupPipeline = nil; + ((ObjPipeline*)skinGlobals.pipelines[PLATFORM_PS2])->destroy(); + skinGlobals.pipelines[PLATFORM_PS2] = nil; + return o; +} + +void +initSkin(void) +{ + Driver::registerPlugin(PLATFORM_PS2, 0, ID_SKIN, + skinOpen, skinClose); +} + +ObjPipeline* +makeSkinPipeline(void) +{ + MatPipeline *pipe = MatPipeline::create(); + pipe->pluginID = ID_SKIN; + pipe->pluginData = 1; + pipe->attribs[AT_XYZ] = &attribXYZ; + pipe->attribs[AT_UV] = &attribUV; + pipe->attribs[AT_RGBA] = &attribRGBA; + pipe->attribs[AT_NORMAL] = &attribNormal; + pipe->attribs[AT_NORMAL+1] = &attribWeights; + uint32 vertCount = MatPipeline::getVertCount(VU_Lights-0x100, 5, 3, 2); + pipe->setTriBufferSizes(5, vertCount); + pipe->vifOffset = pipe->inputStride*vertCount; + pipe->instanceCB = skinInstanceCB; + pipe->uninstanceCB = genericUninstanceCB; + pipe->preUninstCB = skinPreCB; + pipe->postUninstCB = skinPostCB; + + ObjPipeline *opipe = ObjPipeline::create(); + opipe->pluginID = ID_SKIN; + opipe->pluginData = 1; + opipe->groupPipeline = pipe; + return opipe; +} + +Stream* +readNativeSkin(Stream *stream, int32, void *object, int32 offset) +{ + uint8 header[4]; + Geometry *geometry = (Geometry*)object; + uint32 platform; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + platform = stream->readU32(); + if(platform != PLATFORM_PS2){ + RWERROR((ERR_PLATFORM, platform)); + return nil; + } + stream->read8(header, 4); + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, offset) = skin; + + // numUsedBones and numWeights appear in/after 34003 + // but not in/before 33002 (probably rw::version >= 0x34000) + bool oldFormat = header[1] == 0; + + // Use numBones for numUsedBones to allocate data + if(oldFormat) + skin->init(header[0], header[0], 0); + else + skin->init(header[0], header[1], 0); + skin->numWeights = header[2]; + + if(!oldFormat) + stream->read8(skin->usedBones, skin->numUsedBones); + if(skin->numBones) + stream->read32(skin->inverseMatrices, skin->numBones*64); + + // dummy data in case we need to write data in the new format + if(oldFormat){ + skin->numWeights = 4; + for(int32 i = 0; i < skin->numUsedBones; i++) + skin->usedBones[i] = i; + } + + if(!oldFormat){ + // TODO: what is this? + stream->seek(4*4); + + readSkinSplitData(stream, skin); + } + return stream; +} + +Stream* +writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset) +{ + uint8 header[4]; + + writeChunkHeader(stream, ID_STRUCT, len-12); + stream->writeU32(PLATFORM_PS2); + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + // not sure which version introduced the new format + bool oldFormat = version < 0x34000; + header[0] = skin->numBones; + if(oldFormat){ + header[1] = 0; + header[2] = 0; + }else{ + header[1] = skin->numUsedBones; + header[2] = skin->numWeights; + } + header[3] = 0; + stream->write8(header, 4); + + if(!oldFormat) + stream->write8(skin->usedBones, skin->numUsedBones); + stream->write32(skin->inverseMatrices, skin->numBones*64); + if(!oldFormat){ + uint32 buffer[4] = { 0, 0, 0, 0, }; + stream->write32(buffer, 4*4); + + writeSkinSplitData(stream, skin); + } + return stream; +} + +int32 +getSizeNativeSkin(void *object, int32 offset) +{ + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin == nil) + return -1; + int32 size = 12 + 4 + 4 + skin->numBones*64; + // not sure which version introduced the new format + if(version >= 0x34000) + size += skin->numUsedBones + 16 + skinSplitDataSize(skin); + return size; +} + +void +instanceSkinData(Geometry*, Mesh *m, Skin *skin, uint32 *data) +{ + uint16 j; + float32 *weights = (float32*)data; + uint32 *indices = data; + for(uint32 i = 0; i < m->numIndices; i++){ + j = m->indices[i]; + for(int32 k = 0; k < 4; k++){ + *weights++ = skin->weights[j*4+k]; + *indices &= ~0x3FF; + *indices++ |= skin->indices[j*4+k] && skin->weights[j*4+k] ? + (skin->indices[j*4+k]+1) << 2 : 0; + } + } +} + +void +skinInstanceCB(MatPipeline *, Geometry *g, Mesh *m, uint8 **data) +{ + Skin *skin = Skin::get(g); + if(skin == nil) + return; + instanceSkinData(g, m, skin, (uint32*)data[4]); +} + +// TODO: call base function perhaps? +int32 +findVertexSkin(Geometry *g, uint32 flags[], uint32 mask, Vertex *v) +{ + Skin *skin = Skin::get(g); + float32 *wghts = nil; + uint8 *inds = nil; + if(skin){ + wghts = skin->weights; + inds = skin->indices; + } + + V3d *verts = g->morphTargets[0].vertices; + TexCoords *tex = g->texCoords[0]; + TexCoords *tex1 = g->texCoords[1]; + V3d *norms = g->morphTargets[0].normals; + RGBA *cols = g->colors; + + for(int32 i = 0; i < g->numVertices; i++){ + uint32 flag = flags ? flags[i] : ~0; + if(mask & flag & 0x1 && !equal(*verts, v->p)) + goto cont; + if(mask & flag & 0x10 && !equal(*norms, v->n)) + goto cont; + if(mask & flag & 0x100 && !equal(*cols, v->c)) + goto cont; + if(mask & flag & 0x1000 && !equal(*tex, v->t)) + goto cont; + if(mask & flag & 0x2000 && !equal(*tex1, v->t1)) + goto cont; + if(mask & flag & 0x10000 && + !(wghts[0] == v->w[0] && wghts[1] == v->w[1] && + wghts[2] == v->w[2] && wghts[3] == v->w[3] && + inds[0] == v->i[0] && inds[1] == v->i[1] && + inds[2] == v->i[2] && inds[3] == v->i[3])) + goto cont; + return i; + cont: + verts++; + tex++; + tex1++; + norms++; + cols++; + wghts += 4; + inds += 4; + } + return -1; +} + +void +insertVertexSkin(Geometry *geo, int32 i, uint32 mask, Vertex *v) +{ + Skin *skin = Skin::get(geo); + insertVertex(geo, i, mask, v); + if(mask & 0x10000){ + memcpy(&skin->weights[i*4], v->w, 16); + memcpy(&skin->indices[i*4], v->i, 4); + } +} + +/* +void +skinUninstanceCB(MatPipeline*, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]) +{ + float32 *verts = (float32*)data[AT_XYZ]; + float32 *texcoords = (float32*)data[AT_UV]; + uint8 *colors = (uint8*)data[AT_RGBA]; + int8 *norms = (int8*)data[AT_NORMAL]; + uint32 *wghts = (uint32*)data[AT_NORMAL+1]; + uint32 mask = 0x1; // vertices + if(geo->flags & Geometry::NORMALS) + mask |= 0x10; + if(geo->flags & Geometry::PRELIT) + mask |= 0x100; + if(geo->numTexCoordSets > 0) + mask |= 0x1000; + mask |= 0x10000; + + Vertex v; + for(uint32 i = 0; i < mesh->numIndices; i++){ + if(mask & 0x1) + memcpy(&v.p, verts, 12); + if(mask & 0x10){ + v.n[0] = norms[0]/127.0f; + v.n[1] = norms[1]/127.0f; + v.n[2] = norms[2]/127.0f; + } + if(mask & 0x100) + memcpy(&v.c, colors, 4); + if(mask & 0x1000) + memcpy(&v.t, texcoords, 8); + for(int j = 0; j < 4; j++){ + ((uint32*)v.w)[j] = wghts[j] & ~0x3FF; + v.i[j] = (wghts[j] & 0x3FF) >> 2; + if(v.i[j]) v.i[j]--; + if(v.w[j] == 0.0f) v.i[j] = 0; + } + int32 idx = findVertexSkin(geo, flags, mask, &v); + if(idx < 0) + idx = geo->numVertices++; + mesh->indices[i] = idx; + flags[idx] = mask; + insertVertexSkin(geo, idx, mask, &v); + verts += 3; + texcoords += 2; + colors += 4; + norms += 3; + wghts += 4; + } +} +*/ + +void +skinPreCB(MatPipeline*, Geometry *geo) +{ + Skin *skin = Skin::get(geo); + if(skin == nil) + return; + uint8 *data = skin->data; + float *invMats = skin->inverseMatrices; + // meshHeader->totalIndices is highest possible number of vertices again + skin->init(skin->numBones, skin->numBones, geo->meshHeader->totalIndices); + memcpy(skin->inverseMatrices, invMats, skin->numBones*64); + rwFree(data); +} + +void +skinPostCB(MatPipeline*, Geometry *geo) +{ + Skin *skin = Skin::get(geo); + if(skin){ + skin->findNumWeights(geo->numVertices); + skin->findUsedBones(geo->numVertices); + } +} + +} +} diff --git a/src/ps2/rwps2.h b/src/ps2/rwps2.h new file mode 100644 index 0000000..e8c779b --- /dev/null +++ b/src/ps2/rwps2.h @@ -0,0 +1,257 @@ +namespace rw { + +#ifdef RW_PS2 +struct EngineOpenParams +{ +}; +#endif + +namespace ps2 { + +void registerPlatformPlugins(void); + +extern Device renderdevice; + +struct Im2DVertex +{ + float32 x, y, z, w; + uint32 r, g, b, a; + float32 u, v, q, PAD; + + void setScreenX(float32 x) { this->x = x; } + void setScreenY(float32 y) { this->y = y; } + void setScreenZ(float32 z) { this->z = z; } + void setCameraZ(float32 z) { this->w = z; } + void setRecipCameraZ(float32 recipz) { this->q = recipz; } + void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { + this->r = r; this->g = g; this->b = b; this->a = a; } + void setU(float32 u, float recipz) { this->u = u; } + void setV(float32 v, float recipz) { this->v = v; } + + float getScreenX(void) { return this->x; } + float getScreenY(void) { return this->y; } + float getScreenZ(void) { return this->z; } + float getCameraZ(void) { return this->w; } + float getRecipCameraZ(void) { return this->q; } + RGBA getColor(void) { return makeRGBA(this->r, this->g, this->b, this->a); } + float getU(void) { return this->u; } + float getV(void) { return this->v; } +}; + + +struct InstanceData +{ + uint32 dataSize; + uint8 *dataRaw; + uint8 *data; + Material *material; +}; + +struct InstanceDataHeader : rw::InstanceDataHeader +{ + uint32 numMeshes; + InstanceData *instanceMeshes; +}; + +enum { + VU_Lights = 0x3d0 +}; + +enum PS2Attribs { + AT_V2_32 = 0x64000000, + AT_V2_16 = 0x65000000, + AT_V2_8 = 0x66000000, + AT_V3_32 = 0x68000000, + AT_V3_16 = 0x69000000, + AT_V3_8 = 0x6A000000, + AT_V4_32 = 0x6C000000, + AT_V4_16 = 0x6D000000, + AT_V4_8 = 0x6E000000, + AT_UNSGN = 0x00004000, + + AT_RW = 0x6 +}; + +// Not really types as in RW but offsets +enum PS2AttibTypes { + AT_XYZ = 0, + AT_UV = 1, + AT_RGBA = 2, + AT_NORMAL = 3 +}; + +void *destroyNativeData(void *object, int32, int32); +Stream *readNativeData(Stream *stream, int32 len, void *object, int32, int32); +Stream *writeNativeData(Stream *stream, int32 len, void *object, int32, int32); +int32 getSizeNativeData(void *object, int32, int32); +void registerNativeDataPlugin(void); + +void printDMA(InstanceData *inst); +void printDMAVIF(InstanceData *inst); +void sizedebug(InstanceData *inst); + +void fixDmaOffsets(InstanceData *inst); // only RW_PS2 +int32 unfixDmaOffsets(InstanceData *inst); + +struct PipeAttribute +{ + const char *name; + uint32 attrib; +}; + +extern PipeAttribute attribXYZ; +extern PipeAttribute attribXYZW; +extern PipeAttribute attribUV; +extern PipeAttribute attribUV2; +extern PipeAttribute attribRGBA; +extern PipeAttribute attribNormal; +extern PipeAttribute attribWeights; + +class MatPipeline : public rw::Pipeline +{ +public: + uint32 vifOffset; + uint32 inputStride; + // number of vertices for tri strips and lists + uint32 triStripCount, triListCount; + PipeAttribute *attribs[10]; + void (*instanceCB)(MatPipeline*, Geometry*, Mesh*, uint8**); + void (*uninstanceCB)(MatPipeline*, Geometry*, uint32*, Mesh*, uint8**); + void (*preUninstCB)(MatPipeline*, Geometry*); + void (*postUninstCB)(MatPipeline*, Geometry*); + // RW has more: + // instanceTestCB() + // resEntryAllocCB() + // bridgeCB() + // postMeshCB() + // vu1code + // primtype + + static uint32 getVertCount(uint32 top, uint32 inAttribs, + uint32 outAttribs, uint32 outBufs) { + return (top-outBufs)/(inAttribs*2+outAttribs*outBufs); + } + + void init(void); + static MatPipeline *create(void); + void destroy(void); + void dump(void); + void setTriBufferSizes(uint32 inputStride, uint32 bufferSize); + void instance(Geometry *g, InstanceData *inst, Mesh *m); + uint8 *collectData(Geometry *g, InstanceData *inst, Mesh *m, uint8 *data[]); +}; + +class ObjPipeline : public rw::ObjPipeline +{ +public: + void init(void); + static ObjPipeline *create(void); + + MatPipeline *groupPipeline; + // RW has more: + // setupCB() + // finalizeCB() + // lightOffset + // lightSize +}; + +struct Vertex { + V3d p; + TexCoords t; + TexCoords t1; + RGBA c; + V3d n; + // skin + float32 w[4]; + uint8 i[4]; +}; + +void insertVertex(Geometry *geo, int32 i, uint32 mask, Vertex *v); + +extern ObjPipeline *defaultObjPipe; +extern MatPipeline *defaultMatPipe; + +void genericUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]); +void genericPreCB(MatPipeline *pipe, Geometry *geo); // skin and ADC +//void defaultUninstanceCB(MatPipeline *pipe, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]); +void skinInstanceCB(MatPipeline *, Geometry *g, Mesh *m, uint8 **data); +//void skinUninstanceCB(MatPipeline*, Geometry *geo, uint32 flags[], Mesh *mesh, uint8 *data[]); + +ObjPipeline *makeDefaultPipeline(void); +void dumpPipeline(rw::Pipeline *pipe); + +// ADC plugin + +// Each element in adcBits corresponds to an index in Mesh->indices, +// this assumes the Mesh indices are ADC formatted. +// ADCData->numBits != Mesh->numIndices. ADCData->numBits is probably +// equal to Mesh->numIndices before the Mesh gets ADC formatted. +// +// Can't convert between ADC-formatted and non-ADC-formatted yet :( + +struct ADCData +{ + bool32 adcFormatted; + int8 *adcBits; + int32 numBits; +}; +extern int32 adcOffset; +void registerADCPlugin(void); + +int8 *getADCbits(Geometry *geo); +int8 *getADCbitsForMesh(Geometry *geo, Mesh *mesh); +void convertADC(Geometry *g); +void unconvertADC(Geometry *geo); +void allocateADC(Geometry *geo); + +// PDS plugin + +Pipeline *getPDSPipe(uint32 data); +void registerPDSPipe(Pipeline *pipe); +void registerPDSPlugin(int32 n); +void registerPluginPDSPipes(void); + +// Native Texture and Raster + +struct Ps2Raster +{ + enum Flags { + NEWSTYLE = 0x1, // has GIF tags and transfer DMA chain + SWIZZLED8 = 0x2, + SWIZZLED4 = 0x4 + }; + struct PixelPtr { + // RW has pixels as second element but we don't want this struct + // to be longer than 16 bytes + uint8 *pixels; + // palette can be allocated in last level, in that case numTransfers is + // one less than numTotalTransfers. + int32 numTransfers; + int32 numTotalTransfers; + }; + + uint64 tex0; + uint32 paletteBase; // block address from beginning of GS data (words/64) + uint16 kl; + uint8 tex1low; // MXL and LCM of TEX1 + uint8 unk2; + uint64 miptbp1; + uint64 miptbp2; + uint32 pixelSize; // in bytes + uint32 paletteSize; // in bytes + uint32 totalSize; // total size of texture on GS in words + int8 flags; + + uint8 *data; //tmp + uint32 dataSize; +}; + +extern int32 nativeRasterOffset; +void registerNativeRaster(void); + +Texture *readNativeTexture(Stream *stream); +void writeNativeTexture(Texture *tex, Stream *stream); +uint32 getSizeNativeTexture(Texture *tex); + +} +} diff --git a/src/ps2/rwps2impl.h b/src/ps2/rwps2impl.h new file mode 100644 index 0000000..f455bd2 --- /dev/null +++ b/src/ps2/rwps2impl.h @@ -0,0 +1,16 @@ +namespace rw { +namespace ps2 { + +Raster *rasterCreate(Raster *raster); +uint8 *rasterLock(Raster*, int32 level, int32 lockMode); +void rasterUnlock(Raster*, int32 level); +uint8 *rasterLockPalette(Raster*, int32 lockMode); +void rasterUnlockPalette(Raster*); +int32 rasterNumLevels(Raster*); +bool32 imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); +bool32 rasterFromImage(Raster *raster, Image *image); +Image *rasterToImage(Raster *raster); + +} +} diff --git a/src/ps2/rwps2plg.h b/src/ps2/rwps2plg.h new file mode 100644 index 0000000..8776a51 --- /dev/null +++ b/src/ps2/rwps2plg.h @@ -0,0 +1,27 @@ +namespace rw { +namespace ps2 { + +// MatFX plugin + +void initMatFX(void); +ObjPipeline *makeMatFXPipeline(void); + +// Skin plugin + +void initSkin(void); +ObjPipeline *makeSkinPipeline(void); + +void insertVertexSkin(Geometry *geo, int32 i, uint32 mask, Vertex *v); +int32 findVertexSkin(Geometry *g, uint32 flags[], uint32 mask, Vertex *v); + +Stream *readNativeSkin(Stream *stream, int32, void *object, int32 offset); +Stream *writeNativeSkin(Stream *stream, int32 len, void *object, int32 offset); +int32 getSizeNativeSkin(void *object, int32 offset); + +void instanceSkinData(Geometry *g, Mesh *m, Skin *skin, uint32 *data); + +void skinPreCB(MatPipeline*, Geometry*); +void skinPostCB(MatPipeline*, Geometry*); + +} +} diff --git a/src/raster.cpp b/src/raster.cpp new file mode 100644 index 0000000..183ec6b --- /dev/null +++ b/src/raster.cpp @@ -0,0 +1,374 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +//#include "ps2/rwps2.h" +//#include "d3d/rwd3d.h" +//#include "d3d/rwxbox.h" +//#include "d3d/rwd3d8.h" +//#include "d3d/rwd3d9.h" + +#define PLUGIN_ID 0 + +namespace rw { + +int32 Raster::numAllocated; + +struct RasterGlobals +{ + int32 sp; + Raster *stack[32]; +}; +int32 rasterModuleOffset; + +#define RASTERGLOBAL(v) (PLUGINOFFSET(RasterGlobals, engine, rasterModuleOffset)->v) + +static void* +rasterOpen(void *object, int32 offset, int32 size) +{ + int i; + rasterModuleOffset = offset; + RASTERGLOBAL(sp) = -1; + for(i = 0; i < nelem(RASTERGLOBAL(stack)); i++) + RASTERGLOBAL(stack)[i] = nil; + return object; +} + +static void* +rasterClose(void *object, int32 offset, int32 size) +{ + return object; +} + +void +Raster::registerModule(void) +{ + Engine::registerPlugin(sizeof(RasterGlobals), ID_RASTERMODULE, rasterOpen, rasterClose); +} + +Raster* +Raster::create(int32 width, int32 height, int32 depth, int32 format, int32 platform) +{ + // TODO: pass arguments through to the driver and create the raster there + Raster *raster = (Raster*)rwMalloc(s_plglist.size, MEMDUR_EVENT); // TODO + assert(raster != nil); + numAllocated++; + raster->parent = raster; + raster->offsetX = 0; + raster->offsetY = 0; + raster->platform = platform ? platform : rw::platform; + raster->type = format & 0x7; + raster->flags = format & 0xF8; + raster->privateFlags = 0; + raster->format = format & 0xFF00; + raster->width = width; + raster->height = height; + raster->depth = depth; + raster->pixels = raster->palette = nil; + s_plglist.construct(raster); + +// printf("%d %d %d %d\n", raster->type, raster->width, raster->height, raster->depth); + return engine->driver[raster->platform]->rasterCreate(raster); +} + +void +Raster::subRaster(Raster *parent, Rect *r) +{ + if((this->flags & DONTALLOCATE) == 0) + return; + this->width = r->w; + this->height = r->h; + this->offsetX += r->x; + this->offsetY += r->y; + this->parent = parent->parent; +} + +void +Raster::destroy(void) +{ + s_plglist.destruct(this); + rwFree(this); + numAllocated--; +} + +uint8* +Raster::lock(int32 level, int32 lockMode) +{ + return engine->driver[this->platform]->rasterLock(this, level, lockMode); +} + +void +Raster::unlock(int32 level) +{ + engine->driver[this->platform]->rasterUnlock(this, level); +} + +uint8* +Raster::lockPalette(int32 lockMode) +{ + return engine->driver[this->platform]->rasterLockPalette(this, lockMode); +} + +void +Raster::unlockPalette(void) +{ + engine->driver[this->platform]->rasterUnlockPalette(this); +} + +int32 +Raster::getNumLevels(void) +{ + return engine->driver[this->platform]->rasterNumLevels(this); +} + +int32 +Raster::calculateNumLevels(int32 width, int32 height) +{ + int32 size = width >= height ? width : height; + int32 n; + for(n = 0; size != 0; n++) + size /= 2; + return n; +} + +bool +Raster::formatHasAlpha(int32 format) +{ + return (format & 0xF00) == Raster::C8888 || + (format & 0xF00) == Raster::C1555 || + (format & 0xF00) == Raster::C4444; +} + +bool32 +Raster::imageFindRasterFormat(Image *image, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat, + int32 platform) +{ + return engine->driver[platform ? platform : rw::platform]->imageFindRasterFormat( + image, type, pWidth, pHeight, pDepth, pFormat); +} + +Raster* +Raster::setFromImage(Image *image, int32 platform) +{ + if(engine->driver[platform ? platform : rw::platform]->rasterFromImage(this, image)) + return this; + return nil; +} + +Raster* +Raster::createFromImage(Image *image, int32 platform) +{ + Raster *raster; + int32 width, height, depth, format; + if(!imageFindRasterFormat(image, TEXTURE, &width, &height, &depth, &format, platform)) + return nil; + raster = Raster::create(width, height, depth, format, platform); + if(raster == nil) + return nil; + return raster->setFromImage(image, platform); +} + +Image* +Raster::toImage(void) +{ + return engine->driver[this->platform]->rasterToImage(this); +} + +void +Raster::show(uint32 flags) +{ + engine->device.showRaster(this, flags); +} + +Raster* +Raster::pushContext(Raster *raster) +{ + RasterGlobals *g = PLUGINOFFSET(RasterGlobals, engine, rasterModuleOffset); + if(g->sp >= (int32)nelem(g->stack)-1) + return nil; + return g->stack[++g->sp] = raster; +} + +Raster* +Raster::popContext(void) +{ + RasterGlobals *g = PLUGINOFFSET(RasterGlobals, engine, rasterModuleOffset); + if(g->sp < 0) + return nil; + return g->stack[g->sp--]; +} + +Raster* +Raster::getCurrentContext(void) +{ + RasterGlobals *g = PLUGINOFFSET(RasterGlobals, engine, rasterModuleOffset); + if(g->sp < 0 || g->sp >= (int32)nelem(g->stack)) + return nil; + return g->stack[g->sp]; +} + +bool32 +Raster::renderFast(int32 x, int32 y) +{ + return engine->device.rasterRenderFast(this,x, y); +} + +void +conv_RGBA8888_from_RGBA8888(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; +} + +void +conv_BGRA8888_from_RGBA8888(uint8 *out, uint8 *in) +{ + out[2] = in[0]; + out[1] = in[1]; + out[0] = in[2]; + out[3] = in[3]; +} + +void +conv_RGBA8888_from_RGB888(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = 0xFF; +} + +void +conv_BGRA8888_from_RGB888(uint8 *out, uint8 *in) +{ + out[2] = in[0]; + out[1] = in[1]; + out[0] = in[2]; + out[3] = 0xFF; +} + +void +conv_RGB888_from_RGB888(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void +conv_BGR888_from_RGB888(uint8 *out, uint8 *in) +{ + out[2] = in[0]; + out[1] = in[1]; + out[0] = in[2]; +} + +void +conv_ARGB1555_from_ARGB1555(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1]; +} + +void +conv_ARGB1555_from_RGB555(uint8 *out, uint8 *in) +{ + out[0] = in[0]; + out[1] = in[1] | 0x80; +} + +void +conv_RGBA5551_from_ARGB1555(uint8 *out, uint8 *in) +{ + uint32 r, g, b, a; + a = (in[1]>>7) & 1; + r = (in[1]>>2) & 0x1F; + g = (in[1]&3)<<3 | (in[0]>>5)&7; + b = in[0] & 0x1F; + out[0] = a | b<<1 | g<<6; + out[1] = g>>2 | r<<3; +} + +void +conv_RGBA8888_from_ARGB1555(uint8 *out, uint8 *in) +{ + uint32 r, g, b, a; + a = (in[1]>>7) & 1; + r = (in[1]>>2) & 0x1F; + g = (in[1]&3)<<3 | (in[0]>>5)&7; + b = in[0] & 0x1F; + out[0] = r*0xFF/0x1f; + out[1] = g*0xFF/0x1f; + out[2] = b*0xFF/0x1f; + out[3] = a*0xFF; +} + +void +conv_ABGR1555_from_ARGB1555(uint8 *out, uint8 *in) +{ + uint32 r, b; + r = (in[1]>>2) & 0x1F; + b = in[0] & 0x1F; + out[1] = in[1]&0x83 | b<<2; + out[0] = in[0]&0xE0 | r; +} + +void +expandPal4(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w/2; x++){ + dst[y*dststride + x*2 + 0] = src[y*srcstride + x] & 0xF; + dst[y*dststride + x*2 + 1] = src[y*srcstride + x] >> 4; + } +} +void +compressPal4(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w/2; x++) + dst[y*dststride + x] = src[y*srcstride + x*2 + 0] | src[y*srcstride + x*2 + 1] << 4; +} + +void +expandPal4_BE(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w/2; x++){ + dst[y*dststride + x*2 + 1] = src[y*srcstride + x] & 0xF; + dst[y*dststride + x*2 + 0] = src[y*srcstride + x] >> 4; + } +} +void +compressPal4_BE(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w/2; x++) + dst[y*dststride + x] = src[y*srcstride + x*2 + 1] | src[y*srcstride + x*2 + 0] << 4; +} + +void +copyPal8(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h) +{ + int32 x, y; + for(y = 0; y < h; y++) + for(x = 0; x < w; x++) + dst[y*dststride + x] = src[y*srcstride + x]; +} + + +} diff --git a/src/render.cpp b/src/render.cpp new file mode 100644 index 0000000..814f682 --- /dev/null +++ b/src/render.cpp @@ -0,0 +1,78 @@ +#include + +#include "rwbase.h" +#include "rwplg.h" +#include "rwengine.h" + +namespace rw { + +void SetRenderState(int32 state, uint32 value){ + engine->device.setRenderState(state, (void*)(uintptr)value); } + +void SetRenderStatePtr(int32 state, void *value){ + engine->device.setRenderState(state, value); } + +uint32 GetRenderState(int32 state){ + return (uint32)(uintptr)engine->device.getRenderState(state); } + +void *GetRenderStatePtr(int32 state){ + return engine->device.getRenderState(state); } + +// Im2D + +namespace im2d { + +float32 GetNearZ(void) { return engine->device.zNear; } +float32 GetFarZ(void) { return engine->device.zFar; } +void +RenderLine(void *verts, int32 numVerts, int32 vert1, int32 vert2) +{ + engine->device.im2DRenderLine(verts, numVerts, vert1, vert2); +} +void +RenderTriangle(void *verts, int32 numVerts, int32 vert1, int32 vert2, int32 vert3) +{ + engine->device.im2DRenderTriangle(verts, numVerts, vert1, vert2, vert3); +} +void +RenderIndexedPrimitive(PrimitiveType type, void *verts, int32 numVerts, void *indices, int32 numIndices) +{ + engine->device.im2DRenderIndexedPrimitive(type, verts, numVerts, indices, numIndices); +} +void +RenderPrimitive(PrimitiveType type, void *verts, int32 numVerts) +{ + engine->device.im2DRenderPrimitive(type, verts, numVerts); +} + +} + +// Im3D + +namespace im3d { + +void +Transform(void *vertices, int32 numVertices, Matrix *world, uint32 flags) +{ + engine->device.im3DTransform(vertices, numVertices, world, flags); +} +void +RenderPrimitive(PrimitiveType primType) +{ + engine->device.im3DRenderPrimitive(primType); +} +void +RenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices) +{ + engine->device.im3DRenderIndexedPrimitive(primType, indices, numIndices); +} +void +End(void) +{ + engine->device.im3DEnd(); +} + +} + +} + diff --git a/src/rwanim.h b/src/rwanim.h new file mode 100644 index 0000000..e6dd1d5 --- /dev/null +++ b/src/rwanim.h @@ -0,0 +1,195 @@ +#include + +namespace rw { + +struct Animation; + +// These sizes of these are sadly not platform independent +// because pointer sizes can vary. + +struct KeyFrameHeader +{ + KeyFrameHeader *prev; + float32 time; + + KeyFrameHeader *next(int32 sz){ + return (KeyFrameHeader*)((uint8*)this + sz); } +}; + +struct InterpFrameHeader +{ + KeyFrameHeader *keyFrame1; + KeyFrameHeader *keyFrame2; +}; + +struct AnimInterpolatorInfo +{ + typedef void (*ApplyCB)(void *result, void *frame); + typedef void (*BlendCB)(void *out, void *in1, void *in2, float32 a); + typedef void (*InterpCB)(void *out, void *in1, void *in2, float32 t, + void *custom); + typedef void (*AddCB)(void *out, void *in1, void *in2); + typedef void (*MulRecipCB)(void *frame, void *start); + + int32 id; + int32 interpKeyFrameSize; + int32 animKeyFrameSize; + int32 customDataSize; + + ApplyCB applyCB; + BlendCB blendCB; + InterpCB interpCB; + AddCB addCB; + MulRecipCB mulRecipCB; + void (*streamRead)(Stream *stream, Animation *anim); + void (*streamWrite)(Stream *stream, Animation *anim); + uint32 (*streamGetSize)(Animation *anim); + + static void registerInterp(AnimInterpolatorInfo *interpInfo); + static void unregisterInterp(AnimInterpolatorInfo *interpInfo); + static AnimInterpolatorInfo *find(int32 id); +}; + +struct Animation +{ + AnimInterpolatorInfo *interpInfo; + int32 numFrames; + int32 flags; + float32 duration; + void *keyframes; + void *customData; + + static Animation *create(AnimInterpolatorInfo*, int32 numFrames, + int32 flags, float duration); + void destroy(void); + int32 getNumNodes(void); + KeyFrameHeader *getAnimFrame(int32 n){ + return (KeyFrameHeader*)((uint8*)this->keyframes + + n*this->interpInfo->animKeyFrameSize); + } + static Animation *streamRead(Stream *stream); + static Animation *streamReadLegacy(Stream *stream); + bool streamWrite(Stream *stream); + bool streamWriteLegacy(Stream *stream); + uint32 streamGetSize(void); +}; + +struct AnimInterpolator +{ + Animation *currentAnim; + float32 currentTime; + void *nextFrame; + int32 maxInterpKeyFrameSize; + int32 currentInterpKeyFrameSize; + int32 currentAnimKeyFrameSize; + int32 numNodes; + // TODO some callbacks, parent/sub + // cached from the InterpolatorInfo + AnimInterpolatorInfo::ApplyCB applyCB; + AnimInterpolatorInfo::BlendCB blendCB; + AnimInterpolatorInfo::InterpCB interpCB; + AnimInterpolatorInfo::AddCB addCB; + // after this interpolated frames + + static AnimInterpolator *create(int32 numNodes, int32 maxKeyFrameSize); + void destroy(void); + bool32 setCurrentAnim(Animation *anim); + void addTime(float32 t); + void *getFrames(void){ return this+1;} + InterpFrameHeader *getInterpFrame(int32 n){ + return (InterpFrameHeader*)((uint8*)getFrames() + + n*currentInterpKeyFrameSize); + } + KeyFrameHeader *getAnimFrame(int32 n){ + return (KeyFrameHeader*)((uint8*)currentAnim->keyframes + + n*currentAnimKeyFrameSize); + } +}; + +// +// UV anim +// + +struct UVAnimParamData +{ + float32 theta; // rotation + float32 s0; // scale x + float32 s1; // scale y + float32 skew; // skew + float32 x; // x pos + float32 y; // y pos +}; + +struct UVAnimKeyFrame +{ + UVAnimKeyFrame *prev; + float32 time; + float32 uv[6]; +}; + +struct UVAnimInterpFrame +{ + UVAnimKeyFrame *keyFrame1; + UVAnimKeyFrame *keyFrame2; + float32 uv[6]; +}; + +struct UVAnimDictionary; + +// RW does it differently...maybe we should implement RtDict +// and make it more general? + +struct UVAnimCustomData +{ + char name[32]; + int32 nodeToUVChannel[8]; + int32 refCount; + + void destroy(Animation *anim); + static UVAnimCustomData *get(Animation *anim){ + return (UVAnimCustomData*)anim->customData; } +}; + +// This should be more general probably +struct UVAnimDictEntry +{ + Animation *anim; + LLLink inDict; + static UVAnimDictEntry *fromDict(LLLink *lnk){ + return LLLinkGetData(lnk, UVAnimDictEntry, inDict); } +}; + +// This too +struct UVAnimDictionary +{ + LinkList animations; + + static UVAnimDictionary *create(void); + void destroy(void); + int32 count(void) { return this->animations.count(); } + void add(Animation *anim); + Animation *find(const char *name); + + static UVAnimDictionary *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); +}; + +extern UVAnimDictionary *currentUVAnimDictionary; + +// Material plugin +struct UVAnim +{ + Matrix *uv[2]; + AnimInterpolator *interp[8]; + + static bool32 exists(Material *mat); + static void addTime(Material *mat, float32 t); + static void applyUpdate(Material *mat); +}; + +extern int32 uvAnimOffset; + +void registerUVAnimPlugin(void); + +} diff --git a/src/rwbase.h b/src/rwbase.h new file mode 100644 index 0000000..6b40302 --- /dev/null +++ b/src/rwbase.h @@ -0,0 +1,709 @@ +#ifndef RW_PS2 +#include +#endif +#include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +// TODO: clean up the opengl defines +// and figure out what we even want here... +#ifdef RW_GL3 +#define RW_OPENGL +#define RWDEVICE gl3 +// doesn't help +//#define RW_GL_USE_VAOS +#endif + +#ifdef RW_GLES2 +#define RW_GLES +#endif +#ifdef RW_GLES3 +#define RW_GLES +#endif + +#ifdef RW_D3D9 +#define RWDEVICE d3d +#define RWHALFPIXEL +#endif + +#ifdef RW_D3D8 +#define RWDEVICE d3d +#endif + +#ifdef RW_PS2 +#define RWHALFPIXEL +#define RWDEVICE ps2 +#endif + +#ifdef RW_WDGL +#define RW_OPENGL +#endif + +namespace rw { + +#ifdef RW_PS2 + typedef char int8; + typedef short int16; + typedef int int32; + typedef long long int64; + typedef unsigned char uint8; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned long long uint64; + typedef unsigned int uintptr; +#else + /* get rid of the stupid _t */ + typedef int8_t int8; + typedef int16_t int16; + typedef int32_t int32; + typedef int64_t int64; + typedef uint8_t uint8; + typedef uint16_t uint16; + typedef uint32_t uint32; + typedef uint64_t uint64; + typedef uintptr_t uintptr; +#endif + +typedef float float32; +typedef int32 bool32; +typedef uint8 byte; +typedef uint32 uint; + +#define nil NULL + +#define nelem(A) (sizeof(A) / sizeof A[0]) + +#ifdef __GNUC__ +#define RWALIGN(n) __attribute__ ((aligned (n))) +#else +#ifdef _MSC_VER +#define RWALIGN(n) __declspec(align(n)) +#else +#define RWALIGN(n) // unknown compiler...ignore +#endif +#endif + +// Lists + +struct LLLink +{ + LLLink *next; + LLLink *prev; + void init(void){ + this->next = nil; + this->prev = nil; + } + void remove(void){ + this->prev->next = this->next; + this->next->prev = this->prev; + } +}; + +#define LLLinkGetData(linkvar,type,entry) \ + ((type*)(((rw::uint8*)(linkvar))-offsetof(type,entry))) + +// Have to be careful since the link might be deleted. +#define FORLIST(_link, _list) \ + for(rw::LLLink *_next = nil, *_link = (_list).link.next; \ + _next = (_link)->next, (_link) != (_list).end(); \ + (_link) = _next) + +struct LinkList +{ + LLLink link; + void init(void){ + this->link.next = &this->link; + this->link.prev = &this->link; + } + bool32 isEmpty(void){ + return this->link.next == &this->link; + } + void add(LLLink *link){ + link->next = this->link.next; + link->prev = &this->link; + this->link.next->prev = link; + this->link.next = link; + } + void append(LLLink *link){ + link->next = &this->link; + link->prev = this->link.prev; + this->link.prev->next = link; + this->link.prev = link; + } + LLLink *end(void){ + return &this->link; + } + int32 count(void){ + int32 n = 0; + FORLIST(lnk, (*this)) + n++; + return n; + } +}; + +// Mathematical types + +struct RGBA +{ + uint8 red; + uint8 green; + uint8 blue; + uint8 alpha; +}; +inline RGBA makeRGBA(uint8 r, uint8 g, uint8 b, uint8 a) { RGBA c = { r, g, b, a }; return c; } +inline bool32 equal(const RGBA &c1, const RGBA &c2) { return c1.red == c2.red && c1.green == c2.green && c1.blue == c2.blue && c1.alpha == c2.alpha; } +#define RWRGBAINT(r, g, b, a) ((uint32)((((a)&0xff)<<24)|(((b)&0xff)<<16)|(((g)&0xff)<<8)|((r)&0xff))) + +struct RGBAf +{ + float32 red; + float32 green; + float32 blue; + float32 alpha; +}; +inline RGBAf makeRGBAf(float32 r, float32 g, float32 b, float32 a) { RGBAf c = { r, g, b, a }; return c; } +inline bool32 equal(const RGBAf &c1, const RGBAf &c2) { return c1.red == c2.red && c1.green == c2.green && c1.blue == c2.blue && c1.alpha == c2.alpha; } +inline RGBAf add(const RGBAf &a, const RGBAf &b) { return makeRGBAf(a.red+b.red, a.green+b.green, a.blue+b.blue, a.alpha+b.alpha); } +inline RGBAf modulate(const RGBAf &a, const RGBAf &b) { return makeRGBAf(a.red*b.red, a.green*b.green, a.blue*b.blue, a.alpha*b.alpha); } +inline RGBAf scale(const RGBAf &a, float32 f) { return makeRGBAf(a.red*f, a.green*f, a.blue*f, a.alpha*f); } +inline void clamp(RGBAf *a) { + if(a->red > 1.0f) a->red = 1.0f; + if(a->red < 0.0f) a->red = 0.0f; + if(a->green > 1.0f) a->green = 1.0f; + if(a->green < 0.0f) a->green = 0.0f; + if(a->blue > 1.0f) a->blue = 1.0f; + if(a->blue < 0.0f) a->blue = 0.0f; + if(a->alpha > 1.0f) a->alpha = 1.0f; + if(a->alpha < 0.0f) a->alpha = 0.0f; +} + +inline void convColor(RGBA *i, const RGBAf *f){ + int32 c; + c = (int32)(f->red*255.0f + 0.5f); + i->red = (uint8)c; + c = (int32)(f->green*255.0f + 0.5f); + i->green = (uint8)c; + c = (int32)(f->blue*255.0f + 0.5f); + i->blue = (uint8)c; + c = (int32)(f->alpha*255.0f + 0.5f); + i->alpha = (uint8)c; +} + +inline void convColor(RGBAf *f, const RGBA *i){ + f->red = i->red/255.0f; + f->green = i->green/255.0f; + f->blue = i->blue/255.0f; + f->alpha = i->alpha/255.0f; +} + +struct TexCoords +{ + float32 u, v; +}; +inline bool32 equal(const TexCoords &t1, const TexCoords &t2) { return t1.u == t2.u && t1.v == t2.v; } + +struct V2d; +struct V3d; +struct Quat; +struct Matrix; + +struct V2d +{ + float32 x, y; + void set(float32 x, float32 y){ + this->x = x; this->y = y; } +}; + +inline V2d makeV2d(float32 x, float32 y) { V2d v = { x, y }; return v; } +inline bool32 equal(const V2d &v1, const V2d &v2) { return v1.x == v2.x && v1.y == v2.y; } +inline V2d neg(const V2d &a) { return makeV2d(-a.x, -a.y); } +inline V2d add(const V2d &a, const V2d &b) { return makeV2d(a.x+b.x, a.y+b.y); } +inline V2d sub(const V2d &a, const V2d &b) { return makeV2d(a.x-b.x, a.y-b.y); } +inline V2d scale(const V2d &a, float32 r) { return makeV2d(a.x*r, a.y*r); } +inline float32 length(const V2d &v) { return sqrtf(v.x*v.x + v.y*v.y); } +inline V2d normalize(const V2d &v) { return scale(v, 1.0f/length(v)); } + +struct V3d +{ + float32 x, y, z; + void set(float32 x, float32 y, float32 z){ + this->x = x; this->y = y; this->z = z; } + static void transformPoints(V3d *out, const V3d *in, int32 n, const Matrix *m); + static void transformVectors(V3d *out, const V3d *in, int32 n, const Matrix *m); +}; + +inline V3d makeV3d(float32 x, float32 y, float32 z) { V3d v = { x, y, z }; return v; } +inline bool32 equal(const V3d &v1, const V3d &v2) { return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; } +inline V3d neg(const V3d &a) { return makeV3d(-a.x, -a.y, -a.z); } +inline V3d add(const V3d &a, const V3d &b) { return makeV3d(a.x+b.x, a.y+b.y, a.z+b.z); } +inline V3d sub(const V3d &a, const V3d &b) { return makeV3d(a.x-b.x, a.y-b.y, a.z-b.z); } +inline V3d scale(const V3d &a, float32 r) { return makeV3d(a.x*r, a.y*r, a.z*r); } +inline float32 length(const V3d &v) { return sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); } +inline V3d normalize(const V3d &v) { return scale(v, 1.0f/length(v)); } +inline V3d setlength(const V3d &v, float32 l) { return scale(v, l/length(v)); } +V3d cross(const V3d &a, const V3d &b); +inline float32 dot(const V3d &a, const V3d &b) { return a.x*b.x + a.y*b.y + a.z*b.z; } +inline V3d lerp(const V3d &a, const V3d &b, float32 r){ + return makeV3d(a.x + r*(b.x - a.x), + a.y + r*(b.y - a.y), + a.z + r*(b.z - a.z)); +}; + +struct V4d +{ + float32 x, y, z, w; +}; +inline bool32 equal(const V4d &v1, const V4d &v2) { return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z && v1.w == v2.w; } + +enum CombineOp +{ + COMBINEREPLACE, + COMBINEPRECONCAT, + COMBINEPOSTCONCAT +}; + + +Quat makeQuat(float32 w, float32 x, float32 y, float32 z); +Quat makeQuat(float32 w, const V3d &vec); + +struct Quat +{ + // order is important for streaming and RW compatibility + float32 x, y, z, w; + + static Quat rotation(float32 angle, const V3d &axis){ + return makeQuat(cosf(angle/2.0f), scale(normalize(axis), sinf(angle/2.0f))); } + void set(float32 w, float32 x, float32 y, float32 z){ + this->w = w; this->x = x; this->y = y; this->z = z; } + V3d vec(void){ return makeV3d(x, y, z); } + + Quat *rotate(const V3d *axis, float32 angle, CombineOp op); +}; + +inline Quat makeQuat(float32 w, float32 x, float32 y, float32 z) { Quat q = { x, y, z, w }; return q; } +inline Quat makeQuat(float32 w, const V3d &vec) { Quat q = { vec.x, vec.y, vec.z, w }; return q; } +inline Quat add(const Quat &q, const Quat &p) { return makeQuat(q.w+p.w, q.x+p.x, q.y+p.y, q.z+p.z); } +inline Quat sub(const Quat &q, const Quat &p) { return makeQuat(q.w-p.w, q.x-p.x, q.y-p.y, q.z-p.z); } +inline Quat negate(const Quat &q) { return makeQuat(-q.w, -q.x, -q.y, -q.z); } +inline float32 dot(const Quat &q, const Quat &p) { return q.w*p.w + q.x*p.x + q.y*p.y + q.z*p.z; } +inline Quat scale(const Quat &q, float32 r) { return makeQuat(q.w*r, q.x*r, q.y*r, q.z*r); } +inline float32 length(const Quat &q) { return sqrtf(q.w*q.w + q.x*q.x + q.y*q.y + q.z*q.z); } +inline Quat normalize(const Quat &q) { return scale(q, 1.0f/length(q)); } +inline Quat conj(const Quat &q) { return makeQuat(q.w, -q.x, -q.y, -q.z); } +Quat mult(const Quat &q, const Quat &p); +inline V3d rotate(const V3d &v, const Quat &q) { return mult(mult(q, makeQuat(0.0f, v)), conj(q)).vec(); } +Quat lerp(const Quat &q, const Quat &p, float32 r); +Quat slerp(const Quat &q, const Quat &p, float32 a); + +struct RawMatrix +{ + V3d right; + float32 rightw; + V3d up; + float32 upw; + V3d at; + float32 atw; + V3d pos; + float32 posw;; + + static void mult(RawMatrix *dst, RawMatrix *src1, RawMatrix *src2); + static void transpose(RawMatrix *dst, RawMatrix *src); + static void setIdentity(RawMatrix *dst); +}; + +struct Matrix +{ + enum Type { + TYPENORMAL = 1, + TYPEORTHOGONAL = 2, + TYPEORTHONORMAL = 3, + TYPEMASK = 3 + }; + enum Flags { + IDENTITY = 0x20000 + }; + struct Tolerance { + float32 normal; + float32 orthogonal; + float32 identity; + }; + + V3d right; + uint32 flags; + V3d up; + uint32 pad1; + V3d at; + uint32 pad2; + V3d pos; + uint32 pad3; + + static Matrix *create(void); + void destroy(void); + void setIdentity(void); + void optimize(Tolerance *tolerance = nil); + void update(void) { flags &= ~(IDENTITY|TYPEMASK); } + static Matrix *mult(Matrix *dst, const Matrix *src1, const Matrix *src2); + static Matrix *invert(Matrix *dst, const Matrix *src); + static Matrix *transpose(Matrix *dst, const Matrix *src); + Matrix *rotate(const V3d *axis, float32 angle, CombineOp op); + Matrix *rotate(const Quat &q, CombineOp op); + Matrix *translate(const V3d *translation, CombineOp op); + Matrix *scale(const V3d *scl, CombineOp op); + Matrix *transform(const Matrix *mat, CombineOp op); + Quat getRotation(void); + void lookAt(const V3d &dir, const V3d &up); + + // helper functions. consider private + static void mult_(Matrix *dst, const Matrix *src1, const Matrix *src2); + static void invertOrthonormal(Matrix *dst, const Matrix *src); + static Matrix *invertGeneral(Matrix *dst, const Matrix *src); + static void makeRotation(Matrix *dst, const V3d *axis, float32 angle); + static void makeRotation(Matrix *dst, const Quat &q); +private: + float32 normalError(void); + float32 orthogonalError(void); + float32 identityError(void); +}; + +inline void convMatrix(Matrix *dst, RawMatrix *src){ + *dst = *(Matrix*)src; + dst->optimize(); +} + +inline void convMatrix(RawMatrix *dst, Matrix *src){ + *dst = *(RawMatrix*)src; + dst->rightw = 0.0; + dst->upw = 0.0; + dst->atw = 0.0; + dst->posw = 1.0; +} + +struct Line +{ + V3d start; + V3d end; +}; + +struct Rect +{ + int32 h, w; + int32 x, y; +}; + +struct Sphere +{ + V3d center; + float32 radius; +}; + +struct Plane +{ + V3d normal; + float32 distance; +}; + +struct BBox +{ + V3d sup; + V3d inf; + + void initialize(V3d *point); + void addPoint(V3d *point); + void calculate(V3d *points, int32 n); + bool containsPoint(V3d *point); +}; + +enum PrimitiveType +{ + PRIMTYPENONE = 0, + PRIMTYPELINELIST, + PRIMTYPEPOLYLINE, + PRIMTYPETRILIST, + PRIMTYPETRISTRIP, + PRIMTYPETRIFAN, + PRIMTYPEPOINTLIST +}; + +/* + * Memory + */ + +void memNative32_func(void *data, uint32 size); +void memNative16_func(void *data, uint32 size); +void memLittle32_func(void *data, uint32 size); +void memLittle16_func(void *data, uint32 size); + +#ifdef BIGENDIAN +inline void memNative32(void *data, uint32 size) { memNative32_func(data, size); } +inline void memNative16(void *data, uint32 size) { memNative16_func(data, size); } +inline void memLittle32(void *data, uint32 size) { memLittle32_func(data, size); } +inline void memLittle16(void *data, uint32 size) { memLittle16_func(data, size); } +#define ASSERTLITTLE assert(0 && "unsafe code on big-endian") +#else +inline void memNative32(void *data, uint32 size) { } +inline void memNative16(void *data, uint32 size) { } +inline void memLittle32(void *data, uint32 size) { } +inline void memLittle16(void *data, uint32 size) { } +#define ASSERTLITTLE +#endif + +/* + * Streams + */ + +void makePath(char *filename); + +class Stream +{ +public: + virtual ~Stream(void) { close(); } + virtual void close(void) {} + virtual uint32 write8(const void *data, uint32 length) = 0; + virtual uint32 read8(void *data, uint32 length) = 0; + virtual void seek(int32 offset, int32 whence = 1) = 0; + virtual uint32 tell(void) = 0; + virtual bool eof(void) = 0; + uint32 write32(const void *data, uint32 length); + uint32 write16(const void *data, uint32 length); + uint32 read32(void *data, uint32 length); + uint32 read16(void *data, uint32 length); + int32 writeI8(int8 val); + int32 writeU8(uint8 val); + int32 writeI16(int16 val); + int32 writeU16(uint16 val); + int32 writeI32(int32 val); + int32 writeU32(uint32 val); + int32 writeF32(float32 val); + int8 readI8(void); + uint8 readU8(void); + int16 readI16(void); + uint16 readU16(void); + int32 readI32(void); + uint32 readU32(void); + float32 readF32(void); +}; + +class StreamMemory : public Stream +{ + uint8 *data; + uint32 length; + uint32 capacity; + uint32 position; +public: + void close(void); + uint32 write8(const void *data, uint32 length); + uint32 read8(void *data, uint32 length); + void seek(int32 offset, int32 whence = 1); + uint32 tell(void); + bool eof(void); + StreamMemory *open(uint8 *data, uint32 length, uint32 capacity = 0); + uint32 getLength(void); + + enum { + S_EOF = 0xFFFFFFFF + }; +}; + +class StreamFile : public Stream +{ + FILE *file; +public: + StreamFile(void) { file = nil; } + void close(void); + uint32 write8(const void *data, uint32 length); + uint32 read8(void *data, uint32 length); + void seek(int32 offset, int32 whence = 1); + uint32 tell(void); + bool eof(void); + StreamFile *open(const char *path, const char *mode); +}; + +enum Platform +{ + PLATFORM_NULL = 0, + // D3D7 + PLATFORM_GL = 2, + // MAC + PLATFORM_PS2 = 4, + PLATFORM_XBOX = 5, + // GAMECUBE + // SOFTRAS + PLATFORM_D3D8 = 8, + PLATFORM_D3D9 = 9, + // PSP + + // non-stock-RW platforms + + PLATFORM_WDGL = 11, // WarDrum OpenGL + PLATFORM_GL3 = 12, // my GL3 implementation + + NUM_PLATFORMS, + + FOURCC_PS2 = 0x00325350 // 'PS2\0' +}; + +#define MAKEPLUGINID(v, id) (((v & 0xFFFFFF) << 8) | (id & 0xFF)) +#define MAKEPIPEID(v, id) (((v & 0xFFFF) << 16) | (id & 0xFFFF)) + +enum VendorID +{ + VEND_CORE = 0, + VEND_CRITERIONTK = 1, + VEND_CRITERIONINT = 4, + VEND_CRITERIONWORLD = 5, + // Used for rasters (platform-specific) + VEND_RASTER = 10, + // Used for driver/device allocation tags + VEND_DRIVER = 11 +}; + +// TODO: modules (VEND_CRITERIONINT) + +enum PluginID +{ + // Core + ID_NAOBJECT = MAKEPLUGINID(VEND_CORE, 0x00), + ID_STRUCT = MAKEPLUGINID(VEND_CORE, 0x01), + ID_STRING = MAKEPLUGINID(VEND_CORE, 0x02), + ID_EXTENSION = MAKEPLUGINID(VEND_CORE, 0x03), + ID_CAMERA = MAKEPLUGINID(VEND_CORE, 0x05), + ID_TEXTURE = MAKEPLUGINID(VEND_CORE, 0x06), + ID_MATERIAL = MAKEPLUGINID(VEND_CORE, 0x07), + ID_MATLIST = MAKEPLUGINID(VEND_CORE, 0x08), + ID_WORLD = MAKEPLUGINID(VEND_CORE, 0x0B), + ID_MATRIX = MAKEPLUGINID(VEND_CORE, 0x0D), + ID_FRAMELIST = MAKEPLUGINID(VEND_CORE, 0x0E), + ID_GEOMETRY = MAKEPLUGINID(VEND_CORE, 0x0F), + ID_CLUMP = MAKEPLUGINID(VEND_CORE, 0x10), + ID_LIGHT = MAKEPLUGINID(VEND_CORE, 0x12), + ID_ATOMIC = MAKEPLUGINID(VEND_CORE, 0x14), + ID_TEXTURENATIVE = MAKEPLUGINID(VEND_CORE, 0x15), + ID_TEXDICTIONARY = MAKEPLUGINID(VEND_CORE, 0x16), + ID_IMAGE = MAKEPLUGINID(VEND_CORE, 0x18), + ID_GEOMETRYLIST = MAKEPLUGINID(VEND_CORE, 0x1A), + ID_ANIMANIMATION = MAKEPLUGINID(VEND_CORE, 0x1B), + ID_RIGHTTORENDER = MAKEPLUGINID(VEND_CORE, 0x1F), + ID_UVANIMDICT = MAKEPLUGINID(VEND_CORE, 0x2B), + + // Toolkit + ID_SKYMIPMAP = MAKEPLUGINID(VEND_CRITERIONTK, 0x10), + ID_SKIN = MAKEPLUGINID(VEND_CRITERIONTK, 0x16), + ID_HANIM = MAKEPLUGINID(VEND_CRITERIONTK, 0x1E), + ID_USERDATA = MAKEPLUGINID(VEND_CRITERIONTK, 0x1F), + ID_MATFX = MAKEPLUGINID(VEND_CRITERIONTK, 0x20), + ID_PDS = MAKEPLUGINID(VEND_CRITERIONTK, 0x31), + ID_ADC = MAKEPLUGINID(VEND_CRITERIONTK, 0x34), + ID_UVANIMATION = MAKEPLUGINID(VEND_CRITERIONTK, 0x35), + + // World + ID_MESH = MAKEPLUGINID(VEND_CRITERIONWORLD, 0x0E), + ID_NATIVEDATA = MAKEPLUGINID(VEND_CRITERIONWORLD, 0x10), + ID_VERTEXFMT = MAKEPLUGINID(VEND_CRITERIONWORLD, 0x11), + + // custom native raster + ID_RASTERGL = MAKEPLUGINID(VEND_RASTER, PLATFORM_GL), + ID_RASTERPS2 = MAKEPLUGINID(VEND_RASTER, PLATFORM_PS2), + ID_RASTERXBOX = MAKEPLUGINID(VEND_RASTER, PLATFORM_XBOX), + ID_RASTERD3D8 = MAKEPLUGINID(VEND_RASTER, PLATFORM_D3D8), + ID_RASTERD3D9 = MAKEPLUGINID(VEND_RASTER, PLATFORM_D3D9), + ID_RASTERWDGL = MAKEPLUGINID(VEND_RASTER, PLATFORM_WDGL), + ID_RASTERGL3 = MAKEPLUGINID(VEND_RASTER, PLATFORM_GL3), + + // anything driver/device related (only as allocation tag) + ID_DRIVER = MAKEPLUGINID(VEND_DRIVER, 0) +}; + +enum CoreModuleID +{ + ID_NAMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x00), + // vector + // matrix + ID_FRAMEMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x03), + // stream + // camera + ID_IMAGEMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x06), + ID_RASTERMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x07), + ID_TEXTUREMODULE = MAKEPLUGINID(VEND_CRITERIONINT, 0x08) + // pip + // immediate + // resources + // device + // color + // unused + // error + // metrics + // driver + // chunk group +}; + +#define ECODE(c, s) c + +enum Errors +{ + ERR_NONE = 0x80000000, +#include "base.err" +}; + +#undef ECODE + +extern int32 version; +extern int32 build; +extern int32 platform; +extern bool32 streamAppendFrames; +extern char *debugFile; + +int strcmp_ci(const char *s1, const char *s2); +int strncmp_ci(const char *s1, const char *s2, int n); + +// 0x04000000 3.1 +// 0x08000000 3.2 +// 0x0C000000 3.3 +// 0x10000000 3.4 +// 0x14000000 3.5 +// 0x18000000 3.6 +// 0x1C000000 3.7 + +inline uint32 +libraryIDPack(int version, int build) +{ + if(version <= 0x31000) + return version>>8; + return ((version-0x30000) & 0x3FF00) << 14 | (version&0x3F) << 16 | + (build & 0xFFFF); +} + +inline int +libraryIDUnpackVersion(uint32 libid) +{ + if(libid & 0xFFFF0000) + return ((libid>>14 & 0x3FF00) + 0x30000) | + (libid>>16 & 0x3F); + else + return libid<<8; +} + +inline int +libraryIDUnpackBuild(uint32 libid) +{ + if(libid & 0xFFFF0000) + return libid & 0xFFFF; + else + return 0; +} + +struct ChunkHeaderInfo +{ + uint32 type; + uint32 length; + uint32 version, build; +}; + +// TODO?: make these methods of ChunkHeaderInfo? +bool writeChunkHeader(Stream *s, int32 type, int32 size); +bool readChunkHeaderInfo(Stream *s, ChunkHeaderInfo *header); +bool findChunk(Stream *s, uint32 type, uint32 *length, uint32 *version); + +int32 findPointer(void *p, void **list, int32 num); +uint8 *getFileContents(const char *name, uint32 *len); +} diff --git a/src/rwcharset.h b/src/rwcharset.h new file mode 100644 index 0000000..3988654 --- /dev/null +++ b/src/rwcharset.h @@ -0,0 +1,25 @@ +namespace rw { + +struct Charset +{ + struct Desc { + int32 count; // num of chars + int32 tileWidth, tileHeight; // chars in raster + int32 width, height; // of char + int32 width_internal, height_internal; // + border + } desc; + Raster *raster; + + static bool32 open(void); + static void close(void); + static Charset *create(const RGBA *foreground, const RGBA *background); + void destroy(void); + Charset *setColors(const RGBA *foreground, const RGBA *background); + void print(const char *str, int32 x, int32 y, bool32 hideSpaces); + void printBuffered(const char *str, int32 x, int32 y, bool32 hideSpaces); + static void flushBuffer(void); +private: + void printChar(int32 c, int32 x, int32 y); +}; + +} diff --git a/src/rwengine.h b/src/rwengine.h new file mode 100644 index 0000000..d09de11 --- /dev/null +++ b/src/rwengine.h @@ -0,0 +1,247 @@ +namespace rw { + +// uhhhm..... why are these not actual functions? +enum DeviceReq +{ + // Device initialization before Engine/Driver plugins are opened + DEVICEINIT, + // Device de-initialization after Engine/Driver plugins are closed + DEVICETERM, + + // Device/Context creation + DEVICEOPEN, + // Device/Context shutdown + DEVICECLOSE, + + // Device initialization after Engine/Driver plugins are opened + DEVICEFINALIZE, + // TODO? counterpart to FINALIZE? + + // Video adapters + DEVICEGETNUMSUBSYSTEMS, + DEVICEGETCURRENTSUBSYSTEM, + DEVICESETSUBSYSTEM, + DEVICEGETSUBSSYSTEMINFO, + + // Video modes + DEVICEGETNUMVIDEOMODES, + DEVICEGETCURRENTVIDEOMODE, + DEVICESETVIDEOMODE, + DEVICEGETVIDEOMODEINFO +}; + +typedef int DeviceSystem(DeviceReq req, void *arg, int32 n); + +struct Camera; +struct Image; +struct Texture; +struct Raster; +class ObjPipeline; + +// This is for the render device, we only have one +struct Device +{ + float32 zNear, zFar; + void (*beginUpdate)(Camera*); + void (*endUpdate)(Camera*); + void (*clearCamera)(Camera*, RGBA *col, uint32 mode); + void (*showRaster)(Raster *raster, uint32 flags); + bool32 (*rasterRenderFast)(Raster *raster, int32 x, int32 y); + void (*setRenderState)(int32 state, void *value); + void *(*getRenderState)(int32 state); + + void (*im2DRenderLine)(void *vertices, int32 numVertices, int32 vert1, int32 vert2); + void (*im2DRenderTriangle)(void *vertices, int32 numVertices, int32 vert1, int32 vert2, int32 vert3); + void (*im2DRenderPrimitive)(PrimitiveType primType, void *vertices, int32 numVertices); + void (*im2DRenderIndexedPrimitive)(PrimitiveType primType, void *vertices, int32 numVertices, void *indices, int32 numIndices); + + void (*im3DTransform)(void *vertices, int32 numVertices, Matrix *world, uint32 flags); + void (*im3DRenderPrimitive)(PrimitiveType primType); + void (*im3DRenderIndexedPrimitive)(PrimitiveType primType, void *indices, int32 numIndices); + void (*im3DEnd)(void); + + DeviceSystem *system; +}; + +// This is for platform-dependent but portable things +// so the engine has one for every platform +struct Driver +{ + ObjPipeline *defaultPipeline; + int32 rasterNativeOffset; + + Raster* (*rasterCreate)(Raster*); + uint8 *(*rasterLock)(Raster*, int32 level, int32 lockMode); + void (*rasterUnlock)(Raster*, int32 level); + uint8 *(*rasterLockPalette)(Raster*, int32 lockMode); + void (*rasterUnlockPalette)(Raster*); + int32 (*rasterNumLevels)(Raster*); + bool32 (*imageFindRasterFormat)(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); + bool32 (*rasterFromImage)(Raster*, Image*); + Image *(*rasterToImage)(Raster*); + + static PluginList s_plglist[NUM_PLATFORMS]; + static int32 registerPlugin(int32 platform, int32 size, uint32 id, + Constructor ctor, Destructor dtor){ + return s_plglist[platform].registerPlugin(size, id, + ctor, dtor, nil); + } +}; + +struct EngineOpenParams; + +enum MemHint +{ + MEMDUR_NA = 0, + // used inside function + MEMDUR_FUNCTION = 0x10000, + // used for one frame + MEMDUR_FRAME = 0x20000, + // used for longer time + MEMDUR_EVENT = 0x30000, + // used while the engine is running + MEMDUR_GLOBAL = 0x40000 +}; + +struct MemoryFunctions +{ + void *(*rwmalloc)(size_t sz, uint32 hint); + void *(*rwrealloc)(void *p, size_t sz, uint32 hint); + void (*rwfree)(void *p); + // These are temporary until we have better error handling + // TODO: Maybe don't put them here since they shouldn't really be switched out + void *(*rwmustmalloc)(size_t sz, uint32 hint); + void *(*rwmustrealloc)(void *p, size_t sz, uint32 hint); +}; + +struct SubSystemInfo +{ + char name[80]; +}; + +enum VideoModeFlags +{ + VIDEOMODEEXCLUSIVE = 1 +}; + +struct VideoMode +{ + int32 width; + int32 height; + int32 depth; + uint32 flags; +}; + +struct Camera; +struct World; + +// This is for platform independent things +// TODO: move more stuff into this +struct Engine +{ + enum State { + Dead = 0, + Initialized, + Opened, + Started + }; + Camera *currentCamera; + World *currentWorld; + LinkList frameDirtyList; + + // Dynamically allocated because of plugins + Driver *driver[NUM_PLATFORMS]; + Device device; + ObjPipeline *dummyDefaultPipeline; + + // These must always be available + static MemoryFunctions memfuncs; + static State state; + + static bool32 init(void); + static bool32 open(EngineOpenParams*); + static bool32 start(void); + static void term(void); + static void close(void); + static void stop(void); + + static int32 getNumSubSystems(void); + static int32 getCurrentSubSystem(void); + static bool32 setSubSystem(int32 subsys); + static SubSystemInfo *getSubSystemInfo(SubSystemInfo *info, int32 subsys); + + static int32 getNumVideoModes(void); + static int32 getCurrentVideoMode(void); + static bool32 setVideoMode(int32 mode); + static VideoMode *getVideoModeInfo(VideoMode *info, int32 mode); + + + static PluginList s_plglist; + static int32 registerPlugin(int32 size, uint32 id, + Constructor ctor, Destructor dtor){ + return s_plglist.registerPlugin(size, id, ctor, dtor, nil); + } +}; + +extern Engine *engine; + +#define RWTOSTR_(X) #X +#define RWTOSTR(X) RWTOSTR_(X) +#define RWHERE "file: " __FILE__ " line: " RWTOSTR(__LINE__) + +extern const char *allocLocation; + +inline void *malloc_LOC(size_t sz, uint32 hint, const char *here) { allocLocation = here; return rw::Engine::memfuncs.rwmalloc(sz,hint); } +inline void *realloc_LOC(void *p, size_t sz, uint32 hint, const char *here) { allocLocation = here; return rw::Engine::memfuncs.rwrealloc(p,sz,hint); } +inline void *mustmalloc_LOC(size_t sz, uint32 hint, const char *here) { allocLocation = here; return rw::Engine::memfuncs.rwmustmalloc(sz,hint); } +inline void *mustrealloc_LOC(void *p, size_t sz, uint32 hint, const char *here) { allocLocation = here; return rw::Engine::memfuncs.rwmustrealloc(p,sz,hint); } + +#define rwMalloc(s, h) rw::malloc_LOC(s,h,RWHERE) +#define rwMallocT(t, s, h) (t*)rw::malloc_LOC((s)*sizeof(t),h,RWHERE) +#define rwRealloc(p, s, h) rw::realloc_LOC(p,s,h,RWHERE) +#define rwReallocT(t, p, s, h) (t*)rw::realloc_LOC(p,(s)*sizeof(t),h,RWHERE) +#define rwFree(p) rw::Engine::memfuncs.rwfree(p) +#define rwNew(s, h) rw::mustmalloc_LOC(s,h,RWHERE) +#define rwNewT(t, s, h) (t*)rw::mustmalloc_LOC((s)*sizeof(t),h,RWHERE) +#define rwResize(p, s, h) rw::mustrealloc_LOC(p,s,h,RWHERE) +#define rwResizeT(t, p, s, h) (t*)rw::mustrealloc_LOC(p,(s)*sizeof(t),h,RWHERE) + +namespace null { + void beginUpdate(Camera*); + void endUpdate(Camera*); + void clearCamera(Camera*, RGBA *col, uint32 mode); + void showRaster(Raster*, uint32 flags); + + void setRenderState(int32 state, void *value); + void *getRenderState(int32 state); + + bool32 rasterRenderFast(Raster *raster, int32 x, int32 y); + + Raster *rasterCreate(Raster*); + uint8 *rasterLock(Raster*, int32 level, int32 lockMode); + void rasterUnlock(Raster*, int32 level); + uint8 *rasterLockPalette(Raster*, int32 lockMode); + void rasterUnlockPalette(Raster*); + int32 rasterNumLevels(Raster*); + bool32 imageFindRasterFormat(Image *img, int32 type, + int32 *width, int32 *height, int32 *depth, int32 *format); + bool32 rasterFromImage(Raster*, Image*); + Image *rasterToImage(Raster*); + + void im2DRenderLine(void*, int32, int32, int32); + void im2DRenderTriangle(void*, int32, int32, int32, int32); + void im2DRenderPrimitive(PrimitiveType, void*, int32); + void im2DRenderIndexedPrimitive(PrimitiveType, void*, int32, void*, int32); + + void im3DTransform(void *vertices, int32 numVertices, Matrix *world, uint32 flags); + void im3DRenderPrimitive(PrimitiveType primType); + void im3DRenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices); + void im3DEnd(void); + + int deviceSystem(DeviceReq req, void *arg0, int32 n); + + extern Device renderdevice; +} + +} diff --git a/src/rwerror.h b/src/rwerror.h new file mode 100644 index 0000000..8c78f56 --- /dev/null +++ b/src/rwerror.h @@ -0,0 +1,25 @@ +namespace rw { + +struct Error +{ + uint32 plugin; + uint32 code; +}; + +void setError(Error *e); +Error *getError(Error *e); + +#define _ERRORCODE(code, ...) code +char *dbgsprint(uint32 code, ...); + +/* ecode is supposed to be in format "(errorcode, printf-arguments..)" */ +#define RWERROR(ecode) do{ \ + rw::Error _e; \ + _e.plugin = PLUGIN_ID; \ + _e.code = _ERRORCODE ecode; \ + printf("%s:%d: ", __FILE__, __LINE__); \ + printf("%s\n", rw::dbgsprint ecode); \ + rw::setError(&_e); \ +}while(0) + +} diff --git a/src/rwobjects.h b/src/rwobjects.h new file mode 100644 index 0000000..32ee631 --- /dev/null +++ b/src/rwobjects.h @@ -0,0 +1,894 @@ +#include + +namespace rw { + +struct Object +{ + uint8 type; + uint8 subType; + uint8 flags; + uint8 privateFlags; + void *parent; + + void init(uint8 type, uint8 subType){ + this->type = type; + this->subType = subType; + this->flags = 0; + this->privateFlags = 0; + this->parent = nil; + } + void copy(Object *o){ + this->type = o->type; + this->subType = o->subType; + this->flags = o->flags; + this->privateFlags = o->privateFlags; + this->parent = nil; + } +}; + +struct Frame +{ + PLUGINBASE + typedef Frame *(*Callback)(Frame *f, void *data); + enum { ID = 0 }; + enum { // private flags + // The hierarchy has unsynched frames + HIERARCHYSYNCLTM = 0x01, // LTM not synched + HIERARCHYSYNCOBJ = 0x02, // attached objects not synched + HIERARCHYSYNC = HIERARCHYSYNCLTM | HIERARCHYSYNCOBJ, + // This frame is not synched + SUBTREESYNCLTM = 0x04, + SUBTREESYNCOBJ = 0x08, + SUBTREESYNC = SUBTREESYNCLTM | SUBTREESYNCOBJ, + SYNCLTM = HIERARCHYSYNCLTM | SUBTREESYNCLTM, + SYNCOBJ = HIERARCHYSYNCOBJ | SUBTREESYNCOBJ + // STATIC = 0x10 + }; + + Object object; + LLLink inDirtyList; + LinkList objectList; + Matrix matrix; + Matrix ltm; + + Frame *child; + Frame *next; + Frame *root; + + static int32 numAllocated; + + static Frame *create(void); + Frame *cloneHierarchy(void); + void destroy(void); + void destroyHierarchy(void); + Frame *addChild(Frame *f, bool32 append = 0); + Frame *removeChild(void); + Frame *forAllChildren(Callback cb, void *data); + Frame *getParent(void) const { + return (Frame*)this->object.parent; } + int32 count(void); + bool32 dirty(void) const { + return !!(this->root->object.privateFlags & HIERARCHYSYNC); } + Matrix *getLTM(void); + void rotate(const V3d *axis, float32 angle, CombineOp op); + void translate(const V3d *trans, CombineOp op); + void scale(const V3d *scale, CombineOp op); + void transform(const Matrix *mat, CombineOp op); + void updateObjects(void); + + + void syncHierarchyLTM(void); + void setHierarchyRoot(Frame *root); + Frame *cloneAndLink(void); + void purgeClone(void); + +#ifndef RWPUBLIC + static void registerModule(void); +#endif + static void syncDirty(void); +}; + +struct FrameList_ +{ + int32 numFrames; + Frame **frames; + + FrameList_ *streamRead(Stream *stream); + void streamWrite(Stream *stream); + static uint32 streamGetSize(Frame *f); +}; +Frame **makeFrameList(Frame *frame, Frame **flist); + +struct ObjectWithFrame +{ + typedef void (*Sync)(ObjectWithFrame*); + + Object object; + LLLink inFrame; + Sync syncCB; + + void setFrame(Frame *f){ + if(this->object.parent) + this->inFrame.remove(); + this->object.parent = f; + if(f){ + f->objectList.add(&this->inFrame); + f->updateObjects(); + } + } + void sync(void){ this->syncCB(this); } + static ObjectWithFrame *fromFrame(LLLink *lnk){ + return LLLinkGetData(lnk, ObjectWithFrame, inFrame); + } +}; + +struct Image +{ + int32 flags; + int32 width, height; + int32 depth; + int32 bpp; // bytes per pixel + int32 stride; + uint8 *pixels; + uint8 *palette; + + static int32 numAllocated; + + static Image *create(int32 width, int32 height, int32 depth); + void destroy(void); + void allocate(void); + void free(void); + void setPixels(uint8 *pixels); + void setPixelsDXT(int32 type, uint8 *pixels); + void setPalette(uint8 *palette); + void compressPalette(void); // turn 8 bit into 4 bit if possible + bool32 hasAlpha(void); + void convertTo32(void); + void palettize(int32 depth); + void unpalettize(bool forceAlpha = false); + void makeMask(void); + void applyMask(Image *mask); + void removeMask(void); + Image *extractMask(void); + + static void setSearchPath(const char*); + static void printSearchPath(void); + static char *getFilename(const char*); + static Image *read(const char *imageName); + static Image *readMasked(const char *imageName, const char *maskName); + + + typedef Image *(*fileRead)(const char *afilename); + typedef void (*fileWrite)(Image *image, const char *filename); + static bool32 registerFileFormat(const char *ext, fileRead read, fileWrite write); + +#ifndef RWPUBLIC + static void registerModule(void); +#endif +}; + +Image *readTGA(const char *filename); +void writeTGA(Image *image, const char *filename); +Image *readBMP(const char *filename); +void writeBMP(Image *image, const char *filename); +Image *readPNG(const char *filename); +void writePNG(Image *image, const char *filename); + +enum { QUANTDEPTH = 8 }; + +struct ColorQuant +{ + struct Node { + uint32 r, g, b, a; + int32 numPixels; + Node *parent; + Node *children[16]; + LLLink link; + + void destroy(void); + void addColor(RGBA color); + bool isLeaf(void) { for(int32 i = 0; i < 16; i++) if(this->children[i]) return false; return true; } + }; + + Node *root; + LinkList leaves; + + void init(void); + void destroy(void); + Node *createNode(int32 level); + Node *getNode(Node *root, uint32 addr, int32 level); + Node *findNode(Node *root, uint32 addr, int32 level); + void reduceNode(Node *node); + void addColor(RGBA color); + uint8 findColor(RGBA color); + void addImage(Image *img); + void makePalette(int32 numColors, RGBA *colors); + void matchImage(uint8 *dstPixels, uint32 dstStride, Image *src); +}; + +// used to emulate d3d and xbox textures +struct RasterLevels +{ + int32 numlevels; + uint32 format; + struct Level { + int32 width, height, size; + uint8 *data; + } levels[1]; // 0 is illegal :/ +}; + +struct Raster +{ + enum { FLIPWAITVSYNCH = 1 }; + + PLUGINBASE + int32 platform; + + // TODO: use bytes + int32 type; + int32 flags; + int32 privateFlags; + int32 format; + int32 width, height, depth; + int32 stride; + uint8 *pixels; + uint8 *palette; + // remember for locked rasters + uint8 *originalPixels; + int32 originalWidth; + int32 originalHeight; + int32 originalStride; + // subraster + Raster *parent; + int32 offsetX, offsetY; + + static int32 numAllocated; + + static Raster *create(int32 width, int32 height, int32 depth, + int32 format, int32 platform = 0); + void subRaster(Raster *parent, Rect *r); + void destroy(void); + static bool32 imageFindRasterFormat(Image *image, int32 type, + int32 *pWidth, int32 *pHeight, int32 *pDepth, int32 *pFormat, int32 platform = 0); + Raster *setFromImage(Image *image, int32 platform = 0); + static Raster *createFromImage(Image *image, int32 platform = 0); + Image *toImage(void); + uint8 *lock(int32 level, int32 lockMode); + void unlock(int32 level); + uint8 *lockPalette(int32 lockMode); + void unlockPalette(void); + int32 getNumLevels(void); + static int32 calculateNumLevels(int32 width, int32 height); + static bool formatHasAlpha(int32 format); + + void show(uint32 flags); + + static Raster *pushContext(Raster *raster); + static Raster *popContext(void); + static Raster *getCurrentContext(void); + bool32 renderFast(int32 x, int32 y); + +#ifndef RWPUBLIC + static void registerModule(void); +#endif + + enum Format { + DEFAULT = 0, + C1555 = 0x0100, + C565 = 0x0200, + C4444 = 0x0300, + LUM8 = 0x0400, + C8888 = 0x0500, + C888 = 0x0600, + D16 = 0x0700, + D24 = 0x0800, + D32 = 0x0900, + C555 = 0x0A00, + AUTOMIPMAP = 0x1000, + PAL8 = 0x2000, + PAL4 = 0x4000, + MIPMAP = 0x8000 + }; + enum Type { + NORMAL = 0x00, + ZBUFFER = 0x01, + CAMERA = 0x02, + TEXTURE = 0x04, + CAMERATEXTURE = 0x05, + DONTALLOCATE = 0x80 + }; + enum LockMode { + LOCKWRITE = 1, + LOCKREAD = 2, + LOCKNOFETCH = 4, // don't fetch pixel data + LOCKRAW = 8, + }; + + enum + { + // from RW + PRIVATELOCK_READ = 0x02, + PRIVATELOCK_WRITE = 0x04, + PRIVATELOCK_READ_PALETTE = 0x08, + PRIVATELOCK_WRITE_PALETTE = 0x10, + }; +}; + +void conv_RGBA8888_from_RGBA8888(uint8 *out, uint8 *in); +void conv_BGRA8888_from_RGBA8888(uint8 *out, uint8 *in); +void conv_RGBA8888_from_RGB888(uint8 *out, uint8 *in); +void conv_BGRA8888_from_RGB888(uint8 *out, uint8 *in); +void conv_RGB888_from_RGB888(uint8 *out, uint8 *in); +void conv_BGR888_from_RGB888(uint8 *out, uint8 *in); +void conv_ARGB1555_from_ARGB1555(uint8 *out, uint8 *in); +void conv_ARGB1555_from_RGB555(uint8 *out, uint8 *in); +void conv_RGBA5551_from_ARGB1555(uint8 *out, uint8 *in); +void conv_RGBA8888_from_ARGB1555(uint8 *out, uint8 *in); +void conv_ABGR1555_from_ARGB1555(uint8 *out, uint8 *in); +inline void conv_8_from_8(uint8 *out, uint8 *in) { *out = *in; } +// some swaps are the same, so these are just more descriptive names +inline void conv_RGBA8888_from_BGRA8888(uint8 *out, uint8 *in) { conv_BGRA8888_from_RGBA8888(out, in); } +inline void conv_RGB888_from_BGR888(uint8 *out, uint8 *in) { conv_BGR888_from_RGB888(out, in); } +inline void conv_ARGB1555_from_ABGR1555(uint8 *out, uint8 *in) { conv_ABGR1555_from_ARGB1555(out, in); } + +void expandPal4(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); +void compressPal4(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); +void expandPal4_BE(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); +void compressPal4_BE(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); +void copyPal8(uint8 *dst, uint32 dststride, uint8 *src, uint32 srcstride, int32 w, int32 h); + + + +#define IGNORERASTERIMP 0 + +struct TexDictionary; + +struct Texture +{ + enum FilterMode { + NEAREST = 1, + LINEAR, + MIPNEAREST, + MIPLINEAR, + LINEARMIPNEAREST, + LINEARMIPLINEAR + }; + enum Addressing { + WRAP = 1, + MIRROR, + CLAMP, + BORDER + }; + + PLUGINBASE + Raster *raster; + TexDictionary *dict; + LLLink inDict; + char name[32]; + char mask[32]; + uint32 filterAddressing; // VVVVUUUU FFFFFFFF + int32 refCount; + + LLLink inGlobalList; // actually not in RW + + static int32 numAllocated; + + static Texture *create(Raster *raster); + void addRef(void) { this->refCount++; } + void destroy(void); + static Texture *fromDict(LLLink *lnk){ + return LLLinkGetData(lnk, Texture, inDict); } + FilterMode getFilter(void) { return (FilterMode)(filterAddressing & 0xFF); } + void setFilter(FilterMode f) { filterAddressing = (filterAddressing & ~0xFF) | f; } + Addressing getAddressU(void) { return (Addressing)((filterAddressing >> 8) & 0xF); } + Addressing getAddressV(void) { return (Addressing)((filterAddressing >> 12) & 0xF); } + void setAddressU(Addressing u) { filterAddressing = (filterAddressing & ~0xF00) | u<<8; } + void setAddressV(Addressing v) { filterAddressing = (filterAddressing & ~0xF000) | v<<12; } + static Texture *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + static Texture *read(const char *name, const char *mask); + static Texture *streamReadNative(Stream *stream); + void streamWriteNative(Stream *stream); + uint32 streamGetSizeNative(void); + + static Texture *(*findCB)(const char *name); + static Texture *(*readCB)(const char *name, const char *mask); + static void setLoadTextures(bool32); // default: true + static void setCreateDummies(bool32); // default: false + static void setMipmapping(bool32); // default: false + static void setAutoMipmapping(bool32); // default: false + static bool32 getMipmapping(void); + static bool32 getAutoMipmapping(void); + +#ifndef RWPUBLIC + static void registerModule(void); +#endif +}; + + +struct SurfaceProperties +{ + float32 ambient; + float32 specular; + float32 diffuse; +}; + +struct Material +{ + PLUGINBASE + Texture *texture; + RGBA color; + SurfaceProperties surfaceProps; + Pipeline *pipeline; + int32 refCount; + + static int32 numAllocated; + + static Material *create(void); + void addRef(void) { this->refCount++; } + Material *clone(void); + void destroy(void); + void setTexture(Texture *tex); + static Material *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); +}; + +void registerMaterialRightsPlugin(void); + +struct Mesh +{ + uint16 *indices; + uint32 numIndices; + Material *material; +}; + +struct MeshHeader +{ + enum { + TRISTRIP = 1 + }; + uint32 flags; + uint16 numMeshes; + uint16 serialNum; + uint32 totalIndices; + uint32 pad; // needed for alignment of Meshes + // after this the meshes + + Mesh *getMeshes(void) { return (Mesh*)(this+1); } + void setupIndices(void); + uint32 guessNumTriangles(void); +}; + +struct Geometry; + +struct MorphTarget +{ + Geometry *parent; + Sphere boundingSphere; + V3d *vertices; + V3d *normals; + + Sphere calculateBoundingSphere(void) const; +}; + +struct InstanceDataHeader +{ + uint32 platform; +}; + +struct Triangle +{ + uint16 v[3]; + uint16 matId; +}; + +struct MaterialList +{ + Material **materials; + int32 numMaterials; + int32 space; + + void init(void); + void deinit(void); + int32 appendMaterial(Material *mat); + int32 findIndex(Material *mat); + static MaterialList *streamRead(Stream *stream, MaterialList *matlist); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); +}; + +struct Geometry +{ + PLUGINBASE + enum { ID = 8 }; + Object object; + uint32 flags; + uint16 lockedSinceInst; + int32 numTriangles; + int32 numVertices; + int32 numMorphTargets; + int32 numTexCoordSets; + + Triangle *triangles; + RGBA *colors; + TexCoords *texCoords[8]; + + MorphTarget *morphTargets; + MaterialList matList; + + MeshHeader *meshHeader; + InstanceDataHeader *instData; + + int32 refCount; + + static int32 numAllocated; + + static Geometry *create(int32 numVerts, int32 numTris, uint32 flags); + void addRef(void) { this->refCount++; } + void destroy(void); + void lock(int32 lockFlags); + void unlock(void); + void addMorphTargets(int32 n); + void calculateBoundingSphere(void); + bool32 hasColoredMaterial(void); + void allocateData(void); + MeshHeader *allocateMeshes(int32 numMeshes, uint32 numIndices, bool32 noIndices); + void generateTriangles(int8 *adc = nil); + void buildMeshes(void); + void buildTristrips(void); // private, used by buildMeshes + void correctTristripWinding(void); + void removeUnusedMaterials(void); + static Geometry *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + + enum Flags + { + TRISTRIP = 0x01, + POSITIONS = 0x02, + TEXTURED = 0x04, + PRELIT = 0x08, + NORMALS = 0x10, + LIGHT = 0x20, + MODULATE = 0x40, + TEXTURED2 = 0x80, + // When this flag is set the geometry has + // native geometry. When streamed out this geometry + // is written out instead of the platform independent data. + // When streamed in with this flag, the geometry is mostly empty. + NATIVE = 0x01000000, + // Just for documentation: RW sets this flag + // to prevent rendering when executing a pipeline, + // so only instancing will occur. + // librw's pipelines are different so it's unused here. + NATIVEINSTANCE = 0x02000000 + }; + + enum LockFlags + { + LOCKPOLYGONS = 0x0001, + LOCKVERTICES = 0x0002, + LOCKNORMALS = 0x0004, + LOCKPRELIGHT = 0x0008, + + LOCKTEXCOORDS = 0x0010, + LOCKTEXCOORDS1 = 0x0010, + LOCKTEXCOORDS2 = 0x0020, + LOCKTEXCOORDS3 = 0x0040, + LOCKTEXCOORDS4 = 0x0080, + LOCKTEXCOORDS5 = 0x0100, + LOCKTEXCOORDS6 = 0x0200, + LOCKTEXCOORDS7 = 0x0400, + LOCKTEXCOORDS8 = 0x0800, + LOCKTEXCOORDSALL = 0x0ff0, + + LOCKALL = 0x0fff + }; +}; + +void registerMeshPlugin(void); +void registerNativeDataPlugin(void); + +struct Clump; +struct World; + +struct Atomic +{ + PLUGINBASE + typedef void (*RenderCB)(Atomic *atomic); + enum { ID = 1 }; + enum { + // flags + COLLISIONTEST = 0x01, // unused here + RENDER = 0x04, + // private flags + WORLDBOUNDDIRTY = 0x01, + // for setGeometry + SAMEBOUNDINGSPHERE = 0x01 + }; + + ObjectWithFrame object; + Geometry *geometry; + Sphere boundingSphere; + Sphere worldBoundingSphere; + Clump *clump; + LLLink inClump; + ObjPipeline *pipeline; + RenderCB renderCB; + + World *world; + ObjectWithFrame::Sync originalSync; + + static int32 numAllocated; + + static Atomic *create(void); + Atomic *clone(void); + void destroy(void); + void setFrame(Frame *f) { + this->object.setFrame(f); + this->object.object.privateFlags |= WORLDBOUNDDIRTY; + } + Frame *getFrame(void) const { return (Frame*)this->object.object.parent; } + static Atomic *fromClump(LLLink *lnk){ + return LLLinkGetData(lnk, Atomic, inClump); } + void setGeometry(Geometry *geo, uint32 flags); + Sphere *getWorldBoundingSphere(void); + ObjPipeline *getPipeline(void); + void instance(void); + void uninstance(void); + void render(void) { this->renderCB(this); } + void setRenderCB(RenderCB renderCB){ + this->renderCB = renderCB; + if(this->renderCB == nil) + this->renderCB = defaultRenderCB; + }; + void setFlags(uint32 flags) { this->object.object.flags = flags; } + uint32 getFlags(void) const { return this->object.object.flags; } + static Atomic *streamReadClump(Stream *stream, + FrameList_ *frameList, Geometry **geometryList); + bool streamWriteClump(Stream *stream, FrameList_ *frmlst); + uint32 streamGetSize(void); + + static void defaultRenderCB(Atomic *atomic); +}; + +void registerAtomicRightsPlugin(void); + +struct Light +{ + PLUGINBASE + enum { ID = 3 }; + ObjectWithFrame object; + float32 radius; + RGBAf color; + float32 minusCosAngle; + LLLink inWorld; + + // clump extension + Clump *clump; + LLLink inClump; + + // world extension + World *world; + ObjectWithFrame::Sync originalSync; + + static int32 numAllocated; + + static Light *create(int32 type); + void destroy(void); + void setFrame(Frame *f) { this->object.setFrame(f); } + Frame *getFrame(void) const { return (Frame*)this->object.object.parent; } + static Light *fromClump(LLLink *lnk){ + return LLLinkGetData(lnk, Light, inClump); } + static Light *fromWorld(LLLink *lnk){ + return LLLinkGetData(lnk, Light, inWorld); } + void setAngle(float32 angle); + float32 getAngle(void); + void setColor(float32 r, float32 g, float32 b); + int32 getType(void){ return this->object.object.subType; } + void setFlags(uint32 flags) { this->object.object.flags = flags; } + uint32 getFlags(void) { return this->object.object.flags; } + static Light *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + + enum Type { + DIRECTIONAL = 1, + AMBIENT, + POINT = 0x80, // positioned + SPOT, + SOFTSPOT + }; + enum Flags { + LIGHTATOMICS = 1, + LIGHTWORLD = 2 + }; +}; + +struct FrustumPlane +{ + Plane plane; + /* Used for BBox tests: + * 0 = inf is closer to normal direction + * 1 = sup is closer to normal direction */ + uint8 closestX; + uint8 closestY; + uint8 closestZ; +}; + +struct Camera +{ + PLUGINBASE + enum { ID = 4 }; + enum { PERSPECTIVE = 1, PARALLEL }; + enum { CLEARIMAGE = 0x1, CLEARZ = 0x2}; + // return value of frustumTestSphere + enum { SPHEREOUTSIDE, SPHEREBOUNDARY, SPHEREINSIDE }; + + ObjectWithFrame object; + void (*beginUpdateCB)(Camera*); + void (*endUpdateCB)(Camera*); + V2d viewWindow; + V2d viewOffset; + float32 nearPlane, farPlane; + float32 fogPlane; + int32 projection; + + Matrix viewMatrix; + float32 zScale, zShift; + + FrustumPlane frustumPlanes[6]; + V3d frustumCorners[8]; + BBox frustumBoundBox; + + Raster *frameBuffer; + Raster *zBuffer; + + // Device dependant view and projection matrices + // optional + RawMatrix devView; + RawMatrix devProj; + + // clump link handled by plugin in RW + Clump *clump; + LLLink inClump; + + // world extension + /* RW: frustum sectors, space, position */ + World *world; + ObjectWithFrame::Sync originalSync; + void (*originalBeginUpdate)(Camera*); + void (*originalEndUpdate)(Camera*); + + static int32 numAllocated; + + static Camera *create(void); + Camera *clone(void); + void destroy(void); + void setFrame(Frame *f) { this->object.setFrame(f); } + Frame *getFrame(void)const { return (Frame*)this->object.object.parent; } + static Camera *fromClump(LLLink *lnk){ + return LLLinkGetData(lnk, Camera, inClump); } + void beginUpdate(void) { this->beginUpdateCB(this); } + void endUpdate(void) { this->endUpdateCB(this); } + void clear(RGBA *col, uint32 mode); + void showRaster(uint32 flags); + void setNearPlane(float32); + void setFarPlane(float32); + void setViewWindow(const V2d *window); + void setViewOffset(const V2d *offset); + void setProjection(int32 proj); + int32 frustumTestSphere(const Sphere *s) const; + static Camera *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + + // fov in degrees + void setFOV(float32 fov, float32 ratio); +}; + +struct Clump +{ + PLUGINBASE + enum { ID = 2 }; + Object object; + LinkList atomics; + LinkList lights; + LinkList cameras; + + World *world; + LLLink inWorld; + + static int32 numAllocated; + + static Clump *create(void); + Clump *clone(void); + void destroy(void); + static Clump *fromWorld(LLLink *lnk){ + return LLLinkGetData(lnk, Clump, inWorld); } + int32 countAtomics(void) { return this->atomics.count(); } + void addAtomic(Atomic *a); + void removeAtomic(Atomic *a); + int32 countLights(void) { return this->lights.count(); } + void addLight(Light *l); + void removeLight(Light *l); + int32 countCameras(void) { return this->cameras.count(); } + void addCamera(Camera *c); + void removeCamera(Camera *c); + void setFrame(Frame *f){ + this->object.parent = f; } + Frame *getFrame(void) const { + return (Frame*)this->object.parent; } + static Clump *streamRead(Stream *stream); + bool streamWrite(Stream *stream); + uint32 streamGetSize(void); + void render(void); +}; + +// used by enumerateLights for lighting callback +struct WorldLights +{ + int32 numAmbients; + RGBAf ambient; // all ambients added + int32 numDirectionals; + Light **directionals; // only directionals + int32 numLocals; + Light **locals; // points, (soft)spots +}; + +// A bit of a stub right now +struct World +{ + PLUGINBASE + enum { ID = 7 }; + Object object; + LinkList localLights; // these have positions (type >= 0x80) + LinkList globalLights; // these do not (type < 0x80) + LinkList clumps; + + static int32 numAllocated; + + static World *create(void); + void destroy(void); + void addLight(Light *light); + void removeLight(Light *light); + void addCamera(Camera *cam); + void removeCamera(Camera *cam); + void addAtomic(Atomic *atomic); + void removeAtomic(Atomic *atomic); + void addClump(Clump *clump); + void removeClump(Clump *clump); + void render(void); + void enumerateLights(Atomic *atomic, WorldLights *lightData); +}; + +struct TexDictionary +{ + PLUGINBASE + enum { ID = 6 }; + Object object; + LinkList textures; + LLLink inGlobalList; + + static int32 numAllocated; + + static TexDictionary *create(void); + static TexDictionary *fromLink(LLLink *lnk){ + return LLLinkGetData(lnk, TexDictionary, inGlobalList); } + void destroy(void); + int32 count(void) { return this->textures.count(); } + void add(Texture *t); + void addFront(Texture *t); + void remove(Texture *t); + Texture *find(const char *name); + static TexDictionary *streamRead(Stream *stream); + void streamWrite(Stream *stream); + uint32 streamGetSize(void); + + static void setCurrent(TexDictionary *txd); + static TexDictionary *getCurrent(void); +}; + +} diff --git a/src/rwpipeline.h b/src/rwpipeline.h new file mode 100644 index 0000000..9a0410a --- /dev/null +++ b/src/rwpipeline.h @@ -0,0 +1,65 @@ +namespace rw { + +struct Atomic; + +class Pipeline +{ +public: + uint32 pluginID; + uint32 pluginData; + int32 platform; + + void init(uint32 platform) { + this->pluginID = 0; + this->pluginData = 0; + this->platform = platform; + } +}; + +class ObjPipeline : public Pipeline +{ +public: + void init(uint32 platform); + static ObjPipeline *create(void); // always PLATFORM_NULL + void destroy(void); + + // not the most beautiful way of doing things but still + // better than virtual methods (i hope?). + struct { + void (*instance)(ObjPipeline *pipe, Atomic *atomic); + void (*uninstance)(ObjPipeline *pipe, Atomic *atomic); + void (*render)(ObjPipeline *pipe, Atomic *atomic); + } impl; + // just for convenience + void instance(Atomic *atomic) { this->impl.instance(this, atomic); } + void uninstance(Atomic *atomic) { this->impl.uninstance(this, atomic); } + void render(Atomic *atomic) { this->impl.render(this, atomic); } +}; + +void findMinVertAndNumVertices(uint16 *indices, uint32 numIndices, uint32 *minVert, int32 *numVertices); + +// everything xbox, d3d8 and d3d9 may want to use +enum { + VERT_BYTE2 = 1, + VERT_BYTE3, + VERT_SHORT2, + VERT_SHORT3, + VERT_NORMSHORT2, + VERT_NORMSHORT3, + VERT_FLOAT2, + VERT_FLOAT3, + VERT_FLOAT4, + VERT_ARGB, + VERT_RGBA, + VERT_COMPNORM +}; + +void instV4d(int type, uint8 *dst, V4d *src, uint32 numVertices, uint32 stride); +void instV3d(int type, uint8 *dst, V3d *src, uint32 numVertices, uint32 stride); +void uninstV3d(int type, V3d *dst, uint8 *src, uint32 numVertices, uint32 stride); +void instTexCoords(int type, uint8 *dst, TexCoords *src, uint32 numVertices, uint32 stride); +void uninstTexCoords(int type, TexCoords *dst, uint8 *src, uint32 numVertices, uint32 stride); +bool32 instColor(int type, uint8 *dst, RGBA *src, uint32 numVertices, uint32 stride); +void uninstColor(int type, RGBA *dst, uint8 *src, uint32 numVertices, uint32 stride); + +} diff --git a/src/rwplg.h b/src/rwplg.h new file mode 100644 index 0000000..69e0f5c --- /dev/null +++ b/src/rwplg.h @@ -0,0 +1,79 @@ +namespace rw { + +#define PLUGINOFFSET(type, base, offset) \ + ((type*)((char*)(base) + (offset))) + +typedef void *(*Constructor)(void *object, int32 offset, int32 size); +typedef void *(*Destructor)(void *object, int32 offset, int32 size); +typedef void *(*CopyConstructor)(void *dst, void *src, int32 offset, int32 size); +typedef Stream *(*StreamRead)(Stream *stream, int32 length, void *object, int32 offset, int32 size); +typedef Stream *(*StreamWrite)(Stream *stream, int32 length, void *object, int32 offset, int32 size); +typedef int32 (*StreamGetSize)(void *object, int32 offset, int32 size); +typedef void (*RightsCallback)(void *object, int32 offset, int32 size, uint32 data); + +struct PluginList +{ + int32 size; + int32 defaultSize; + LinkList plugins; + + PluginList(void) {} + PluginList(int32 defSize) + : size(defSize), defaultSize(defSize) + { plugins.init(); } + + static void open(void); + static void close(void); + + void construct(void *); + void destruct(void *); + void copy(void *dst, void *src); + bool streamRead(Stream *stream, void *); + void streamWrite(Stream *stream, void *); + int streamGetSize(void *); + void streamSkip(Stream *stream); + void assertRights(void *, uint32 pluginID, uint32 data); + + int32 registerPlugin(int32 size, uint32 id, + Constructor, Destructor, CopyConstructor); + int32 registerStream(uint32 id, StreamRead, StreamWrite, StreamGetSize); + int32 setStreamRightsCallback(uint32 id, RightsCallback cb); + int32 getPluginOffset(uint32 id); +}; + +struct Plugin +{ + int32 offset; + int32 size; + uint32 id; + Constructor constructor; + Destructor destructor; + CopyConstructor copy; + StreamRead read; + StreamWrite write; + StreamGetSize getSize; + RightsCallback rightsCallback; + PluginList *parentList; + LLLink inParentList; + LLLink inGlobalList; +}; + +#define PLUGINBASE \ + static PluginList s_plglist; \ + static int32 registerPlugin(int32 size, uint32 id, Constructor ctor, \ + Destructor dtor, CopyConstructor copy){ \ + return s_plglist.registerPlugin(size, id, ctor, dtor, copy); \ + } \ + static int32 registerPluginStream(uint32 id, StreamRead read, \ + StreamWrite write, StreamGetSize getSize){ \ + return s_plglist.registerStream(id, read, write, getSize); \ + } \ + static int32 setStreamRightsCallback(uint32 id, RightsCallback cb){ \ + return s_plglist.setStreamRightsCallback(id, cb); \ + } \ + static int32 getPluginOffset(uint32 id){ \ + return s_plglist.getPluginOffset(id); \ + } + + +} diff --git a/src/rwplugins.h b/src/rwplugins.h new file mode 100644 index 0000000..326d4af --- /dev/null +++ b/src/rwplugins.h @@ -0,0 +1,248 @@ +namespace rw { + +/* + * HAnim + */ + +struct HAnimKeyFrame +{ + HAnimKeyFrame *prev; + float32 time; + Quat q; + V3d t; +}; + +struct HAnimInterpFrame +{ + HAnimKeyFrame *keyFrame1; + HAnimKeyFrame *keyFrame2; + Quat q; + V3d t; +}; + +struct HAnimNodeInfo +{ + int32 id; + int32 index; + int32 flags; + Frame *frame; +}; + +struct HAnimHierarchy +{ + int32 flags; + int32 numNodes; + Matrix *matrices; + void *matricesUnaligned; + HAnimNodeInfo *nodeInfo; + Frame *parentFrame; + HAnimHierarchy *parentHierarchy; // mostly unused + AnimInterpolator *interpolator; + + static HAnimHierarchy *create(int32 numNodes, int32 *nodeFlags, + int32 *nodeIDs, int32 flags, int32 maxKeySize); + void destroy(void); + void attachByIndex(int32 id); + void attach(void); + int32 getIndex(int32 id); + int32 getIndex(Frame *f); + void updateMatrices(void); + + static HAnimHierarchy *get(Frame *f); + static HAnimHierarchy *get(Clump *c){ + return find(c->getFrame()); } + static HAnimHierarchy *find(Frame *f); + + enum Flags { + SUBHIERARCHY = 0x1, + NOMATRICES = 0x2, + + UPDATEMODELLINGMATRICES = 0x1000, + UPDATELTMS = 0x2000, + LOCALSPACEMATRICES = 0x4000 + }; + enum NodeFlag { + POP = 1, + PUSH + }; +}; + +struct HAnimData +{ + int32 id; + HAnimHierarchy *hierarchy; + + static HAnimData *get(Frame *f); +}; + +extern int32 hAnimOffset; +extern bool32 hAnimDoStream; +void registerHAnimPlugin(void); + + +/* + * MatFX + */ + +struct MatFX +{ + enum { + NOTHING = 0, + BUMPMAP, + ENVMAP, + BUMPENVMAP, // BUMP | ENV + DUAL, + UVTRANSFORM, + DUALUVTRANSFORM + }; + struct Bump { + Frame *frame; + Texture *bumpedTex; + Texture *tex; + float coefficient; + }; + struct Env { + Frame *frame; + Texture *tex; + float coefficient; + int32 fbAlpha; + }; + struct Dual { + Texture *tex; + int32 srcBlend; + int32 dstBlend; + }; + struct UVtransform { + Matrix *baseTransform; + Matrix *dualTransform; + }; + struct { + uint32 type; + union { + Bump bump; + Env env; + Dual dual; + UVtransform uvtransform; + }; + } fx[2]; + uint32 type; + + static void setEffects(Material *m, uint32 flags); + static uint32 getEffects(const Material *m); + static MatFX *get(const Material *m); + int32 getEffectIndex(uint32 type); + // Bump + void setBumpTexture(Texture *t); + void setBumpCoefficient(float32 coef); + Texture *getBumpTexture(void); + float32 getBumpCoefficient(void); + // Env + void setEnvTexture(Texture *t); + void setEnvFrame(Frame *f); + void setEnvCoefficient(float32 coef); + void setEnvFBAlpha(bool32 useFBAlpha); + Texture *getEnvTexture(void); + Frame *getEnvFrame(void); + float32 getEnvCoefficient(void); + bool32 getEnvFBAlpha(void); + // Dual + void setDualTexture(Texture *t); + void setDualSrcBlend(int32 blend); + void setDualDestBlend(int32 blend); + Texture *getDualTexture(void); + int32 getDualSrcBlend(void); + int32 getDualDestBlend(void); + // UV transform + void setUVTransformMatrices(Matrix *base, Matrix *dual); + void getUVTransformMatrices(Matrix **base, Matrix **dual); + + static void enableEffects(Atomic *atomic); + static void disableEffects(Atomic *atomic); + static bool32 getEffects(Atomic *atomic); + + static bool32 modulateEnvMap; +}; + +struct MatFXGlobals +{ + int32 atomicOffset; + int32 materialOffset; + ObjPipeline *pipelines[NUM_PLATFORMS]; + ObjPipeline *dummypipe; +}; +extern MatFXGlobals matFXGlobals; +void registerMatFXPlugin(void); + + +/* + * Skin + */ + +struct SkinGlobals +{ + int32 geoOffset; + int32 atomicOffset; + ObjPipeline *pipelines[NUM_PLATFORMS]; + ObjPipeline *dummypipe; +}; +extern SkinGlobals skinGlobals; + +struct Skin +{ + int32 numBones; + int32 numUsedBones; + int32 numWeights; + uint8 *usedBones; + float *inverseMatrices; + uint8 *indices; + float *weights; + + // split skin + + // points into rle for each mesh + struct RLEcount { + uint8 start; + uint8 size; + }; + // run length encoded used bones + struct RLE { + uint8 startbone; // into remapIndices + uint8 n; + }; + int32 boneLimit; + int32 numMeshes; + int32 rleSize; + int8 *remapIndices; + RLEcount *rleCount; + RLE *rle; + + uint8 *data; // only used by delete + void *platformData; // a place to store platform specific stuff + + void init(int32 numBones, int32 numUsedBones, int32 numVertices); + void findNumWeights(int32 numVertices); + void findUsedBones(int32 numVertices); + + static void setPipeline(Atomic *a, int32 type); + static Skin *get(const Geometry *geo){ + return *PLUGINOFFSET(Skin*, geo, skinGlobals.geoOffset); + } + static void set(Geometry *geo, Skin *skin){ + *PLUGINOFFSET(Skin*, geo, skinGlobals.geoOffset) = skin; + } + static void setHierarchy(Atomic *atomic, HAnimHierarchy *hier){ + *PLUGINOFFSET(HAnimHierarchy*, atomic, + skinGlobals.atomicOffset) = hier; + } + static HAnimHierarchy *getHierarchy(const Atomic *atomic){ + return *PLUGINOFFSET(HAnimHierarchy*, atomic, + skinGlobals.atomicOffset); + } +}; + +Stream *readSkinSplitData(Stream *stream, Skin *skin); +Stream *writeSkinSplitData(Stream *stream, Skin *skin); +int32 skinSplitDataSize(Skin *skin); +void registerSkinPlugin(void); + +} diff --git a/src/rwrender.h b/src/rwrender.h new file mode 100644 index 0000000..592ecc2 --- /dev/null +++ b/src/rwrender.h @@ -0,0 +1,105 @@ +namespace rw { + +// Render states + +enum RenderState +{ + TEXTURERASTER, + TEXTUREADDRESS, + TEXTUREADDRESSU, + TEXTUREADDRESSV, + TEXTUREFILTER, + VERTEXALPHA, + SRCBLEND, + DESTBLEND, + ZTESTENABLE, + ZWRITEENABLE, + FOGENABLE, + FOGCOLOR, + CULLMODE, + // TODO: + // fog type, density ? + // ? shademode + // ???? stencil + + // platform specific or opaque? + ALPHATESTFUNC, + ALPHATESTREF, + + // emulation of PS2 GS alpha test + // in the mode where it still writes color but nor depth + GSALPHATEST, + GSALPHATESTREF +}; + +enum AlphaTestFunc +{ + ALPHAALWAYS, + ALPHAGREATEREQUAL, + ALPHALESS +}; + +enum CullMode +{ + CULLNONE = 1, + CULLBACK, + CULLFRONT +}; + +enum BlendFunction +{ + BLENDZERO = 1, + BLENDONE, + BLENDSRCCOLOR, + BLENDINVSRCCOLOR, + BLENDSRCALPHA, + BLENDINVSRCALPHA, + BLENDDESTALPHA, + BLENDINVDESTALPHA, + BLENDDESTCOLOR, + BLENDINVDESTCOLOR, + BLENDSRCALPHASAT + // TODO: add more perhaps +}; + +void SetRenderState(int32 state, uint32 value); +void SetRenderStatePtr(int32 state, void *value); +uint32 GetRenderState(int32 state); +void *GetRenderStatePtr(int32 state); + +// Im2D + +namespace im2d { + +float32 GetNearZ(void); +float32 GetFarZ(void); +void RenderLine(void *verts, int32 numVerts, int32 vert1, int32 vert2); +void RenderTriangle(void *verts, int32 numVerts, int32 vert1, int32 vert2, int32 vert3); +void RenderIndexedPrimitive(PrimitiveType, void *verts, int32 numVerts, void *indices, int32 numIndices); +void RenderPrimitive(PrimitiveType type, void *verts, int32 numVerts); + +} + +// Im3D + +namespace im3d { + +enum TransformFlags +{ + VERTEXUV = 1, // has tex Coords + ALLOPAQUE = 2, // no vertex alpha + NOCLIP = 4, // don't frustum clip + VERTEXXYZ = 8, // has position + VERTEXRGBA = 16, // has color + EVERYTHING = VERTEXUV|VERTEXXYZ|VERTEXRGBA +}; + +void Transform(void *vertices, int32 numVertices, Matrix *world, uint32 flags); +void RenderPrimitive(PrimitiveType primType); +void RenderIndexedPrimitive(PrimitiveType primType, void *indices, int32 numIndices); +void End(void); + +} + +} + diff --git a/src/rwuserdata.h b/src/rwuserdata.h new file mode 100644 index 0000000..00d7355 --- /dev/null +++ b/src/rwuserdata.h @@ -0,0 +1,94 @@ +namespace rw { + +enum UserDataType +{ + USERDATANA = 0, + USERDATAINT = 1, + USERDATAFLOAT = 2, + USERDATASTRING = 3 +}; + +struct UserDataArray +{ + char *name; + uint32 datatype; + int32 numElements; + void *data; + + int32 getInt(int32 n) { return ((int32*)this->data)[n]; } + float32 getFloat(int32 n) { return ((float32*)this->data)[n]; } + char *getString(int32 n) { return ((char**)this->data)[n]; } + void setInt(int32 n, int32 i) { ((int32*)this->data)[n] = i; } + void setFloat(int32 n, float32 f) { ((float32*)this->data)[n] = f; } + void setString(int32 n, const char *s); + + static int32 geometryAdd(Geometry *g, const char *name, int32 datatype, int32 numElements); + static void geometryRemove(Geometry *g, int32 n); + static int32 geometryGetCount(Geometry *g); + static UserDataArray *geometryGet(Geometry *g, int32 n); + static int32 geometryFindIndex(Geometry *g, const char *name); + + static int32 frameAdd(Frame *f, const char *name, int32 datatype, int32 numElements); + static void frameRemove(Frame *f, int32 n); + static int32 frameGetCount(Frame *f); + static UserDataArray *frameGet(Frame *f, int32 n); + static int32 frameFindIndex(Frame *f, const char *name); + + static int32 cameraAdd(Camera *c, const char *name, int32 datatype, int32 numElements); + static void cameraRemove(Camera *c, int32 n); + static int32 cameraGetCount(Camera *c); + static UserDataArray *cameraGet(Camera *c, int32 n); + static int32 cameraFindIndex(Camera *c, const char *name); + + static int32 lightAdd(Light *l, const char *name, int32 datatype, int32 numElements); + static void lightRemove(Light *l, int32 n); + static int32 lightGetCount(Light *l); + static UserDataArray *lightGet(Light *l, int32 n); + static int32 lightFindIndex(Light *l, const char *name); + + static int32 materialAdd(Material *m, const char *name, int32 datatype, int32 numElements); + static void materialRemove(Material *m, int32 n); + static int32 materialGetCount(Material *m); + static UserDataArray *materialGet(Material *m, int32 n); + static int32 materialFindIndex(Material *m, const char *name); + + static int32 textureAdd(Texture *t, const char *name, int32 datatype, int32 numElements); + static void textureRemove(Texture *t, int32 n); + static int32 textureGetCount(Texture *t); + static UserDataArray *textureGet(Texture *t, int32 n); + static int32 textureFindIndex(Texture *t, const char *name); +}; + +struct UserDataExtension +{ + int32 numArrays; + UserDataArray *arrays; + + int32 add(const char *name, int32 datatype, int32 numElements); + void remove(int32 n); + int32 getCount(void) { return numArrays; } + UserDataArray *get(int32 n) { return n >= numArrays ? nil : &arrays[n]; } + int32 findIndex(const char *name); + + static UserDataExtension *get(Geometry *geo); + static UserDataExtension *get(Frame *frame); + static UserDataExtension *get(Camera *cam); + static UserDataExtension *get(Light *light); + static UserDataExtension *get(Material *mat); + static UserDataExtension *get(Texture *tex); +}; + +struct UserDataGlobals +{ + int32 geometryOffset; + int32 frameOffset; + int32 cameraOffset; + int32 lightOffset; + int32 materialOffset; + int32 textureOffset; +}; +extern UserDataGlobals userDataGlobals; + +void registerUserDataPlugin(void); + +} diff --git a/src/skin.cpp b/src/skin.cpp new file mode 100644 index 0000000..3d7eac4 --- /dev/null +++ b/src/skin.cpp @@ -0,0 +1,472 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwanim.h" +#include "rwengine.h" +#include "rwplugins.h" +#include "ps2/rwps2.h" +#include "ps2/rwps2plg.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwwdgl.h" +#include "gl/rwgl3.h" +#include "gl/rwgl3plg.h" + +#define PLUGIN_ID ID_SKIN + +namespace rw { + +SkinGlobals skinGlobals = { 0, 0, { nil } }; + +static void* +createSkin(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(Skin*, object, offset) = nil; + return object; +} + +static void* +destroySkin(void *object, int32 offset, int32) +{ + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin){ + rwFree(skin->data); + rwFree(skin->remapIndices); +// delete[] skin->platformData; + } + rwFree(skin); + return object; +} + +static void* +copySkin(void *dst, void *src, int32 offset, int32) +{ + Skin *srcskin = *PLUGINOFFSET(Skin*, src, offset); + if(srcskin == nil) + return dst; + Geometry *geometry = (Geometry*)src; + assert(geometry->instData == nil); + assert(((Geometry*)src)->numVertices == ((Geometry*)dst)->numVertices); + Skin *dstskin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, dst, offset) = dstskin; + dstskin->numBones = srcskin->numBones; + dstskin->numUsedBones = srcskin->numUsedBones; + dstskin->numWeights = srcskin->numWeights; + + assert(0 && "can't copy skin yet"); + dstskin->init(srcskin->numBones, srcskin->numUsedBones, + geometry->numVertices); + memcpy(dstskin->usedBones, srcskin->usedBones, srcskin->numUsedBones); + memcpy(dstskin->inverseMatrices, srcskin->inverseMatrices, + srcskin->numBones*64); + memcpy(dstskin->indices, srcskin->indices, geometry->numVertices*4); + memcpy(dstskin->weights, srcskin->weights, geometry->numVertices*16); + return dst; +} + +Stream* +readSkinSplitData(Stream *stream, Skin *skin) +{ + uint32 sz; + int8 *data; + + skin->boneLimit = stream->readI32(); + skin->numMeshes = stream->readI32(); + skin->rleSize = stream->readI32(); + if(skin->numMeshes){ + sz = skin->numBones + 2*(skin->numMeshes+skin->rleSize); + data = (int8*)rwMalloc(sz, MEMDUR_EVENT | ID_SKIN); + stream->read8(data, sz); + skin->remapIndices = data; + skin->rleCount = (Skin::RLEcount*)(data + skin->numBones); + skin->rle = (Skin::RLE*)(data + skin->numBones + 2*skin->numMeshes); + } + return stream; +} + +Stream* +writeSkinSplitData(Stream *stream, Skin *skin) +{ + stream->writeI32(skin->boneLimit); + stream->writeI32(skin->numMeshes); + stream->writeI32(skin->rleSize); + if(skin->numMeshes) + stream->write8(skin->remapIndices, + skin->numBones + 2*(skin->numMeshes+skin->rleSize)); + return stream; +} + +int32 +skinSplitDataSize(Skin *skin) +{ + if(skin->numMeshes == 0) + return 12; + return 12 + skin->numBones + 2*(skin->numMeshes+skin->rleSize); +} + +static Stream* +readSkin(Stream *stream, int32 len, void *object, int32 offset, int32) +{ + uint8 header[4]; + Geometry *geometry = (Geometry*)object; + + if(geometry->instData){ + // TODO: function pointers + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::readNativeSkin(stream, len, object, offset); + else if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::readNativeSkin(stream, len, object, offset); + else if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::readNativeSkin(stream, len, object, offset); + else{ + assert(0 && "unsupported native skin platform"); + return nil; + } + } + + stream->read8(header, 4); // numBones, numUsedBones, + // numWeights, unused + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, offset) = skin; + + // numUsedBones and numWeights appear in/after 34003 + // but not in/before 33002 (probably rw::version >= 0x34000) + bool oldFormat = header[1] == 0; + + // Use numBones for numUsedBones to allocate data, + // find out the correct value later + if(oldFormat) + skin->init(header[0], header[0], geometry->numVertices); + else + skin->init(header[0], header[1], geometry->numVertices); + skin->numWeights = header[2]; + + if(!oldFormat) + stream->read8(skin->usedBones, skin->numUsedBones); + if(skin->indices) + stream->read8(skin->indices, geometry->numVertices*4); + if(skin->weights) + stream->read32(skin->weights, geometry->numVertices*16); + for(int32 i = 0; i < skin->numBones; i++){ + if(oldFormat) + stream->seek(4); // skip 0xdeaddead + stream->read32(&skin->inverseMatrices[i*16], 64); + } + + if(oldFormat){ + skin->findNumWeights(geometry->numVertices); + skin->findUsedBones(geometry->numVertices); + } + + if(!oldFormat) + readSkinSplitData(stream, skin); + + return stream; +} + +static Stream* +writeSkin(Stream *stream, int32 len, void *object, int32 offset, int32) +{ + uint8 header[4]; + Geometry *geometry = (Geometry*)object; + + if(geometry->instData){ + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::writeNativeSkin(stream, len, object, offset); + else if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::writeNativeSkin(stream, len, object, offset); + else if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::writeNativeSkin(stream, len, object, offset); + else{ + assert(0 && "unsupported native skin platform"); + return nil; + } + } + + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + // not sure which version introduced the new format + bool oldFormat = version < 0x34000; + header[0] = skin->numBones; + if(oldFormat){ + header[1] = 0; + header[2] = 0; + }else{ + header[1] = skin->numUsedBones; + header[2] = skin->numWeights; + } + header[3] = 0; + stream->write8(header, 4); + if(!oldFormat) + stream->write8(skin->usedBones, skin->numUsedBones); + stream->write8(skin->indices, geometry->numVertices*4); + stream->write32(skin->weights, geometry->numVertices*16); + for(int32 i = 0; i < skin->numBones; i++){ + if(oldFormat) + stream->writeU32(0xdeaddead); + stream->write32(&skin->inverseMatrices[i*16], 64); + } + + if(!oldFormat) + writeSkinSplitData(stream, skin); + return stream; +} + +static int32 +getSizeSkin(void *object, int32 offset, int32) +{ + Geometry *geometry = (Geometry*)object; + + if(geometry->instData){ + if(geometry->instData->platform == PLATFORM_PS2) + return ps2::getSizeNativeSkin(object, offset); + if(geometry->instData->platform == PLATFORM_WDGL) + return wdgl::getSizeNativeSkin(object, offset); + if(geometry->instData->platform == PLATFORM_XBOX) + return xbox::getSizeNativeSkin(object, offset); + if(geometry->instData->platform == PLATFORM_D3D8) + return -1; + if(geometry->instData->platform == PLATFORM_D3D9) + return -1; + assert(0 && "unsupported native skin platform"); + } + + Skin *skin = *PLUGINOFFSET(Skin*, object, offset); + if(skin == nil) + return -1; + + int32 size = 4 + geometry->numVertices*(16+4) + + skin->numBones*64; + // not sure which version introduced the new format + if(version < 0x34000) + size += skin->numBones*4; + else + size += skin->numUsedBones + skinSplitDataSize(skin); + return size; +} + +static Stream* +readSkinLegacy(Stream *stream, int32 len, void *object, int32, int32) +{ + Atomic *atomic = (Atomic*)object; + Geometry *geometry = atomic->geometry; + + int32 numBones = stream->readI32(); + int32 numVertices = stream->readI32(); + assert(numVertices == geometry->numVertices); + + Skin *skin = rwNewT(Skin, 1, MEMDUR_EVENT | ID_SKIN); + *PLUGINOFFSET(Skin*, geometry, skinGlobals.geoOffset) = skin; + skin->init(numBones, numBones, numVertices); + skin->numWeights = 4; + + stream->read8(skin->indices, numVertices*4); + stream->read32(skin->weights, numVertices*16); + + HAnimHierarchy *hier = HAnimHierarchy::create(numBones, nil, nil, 0, 36); + + for(int i = 0; i < numBones; i++){ + hier->nodeInfo[i].id = stream->readI32(); + hier->nodeInfo[i].index = stream->readI32(); + hier->nodeInfo[i].flags = stream->readI32() & 3; +// printf("%d %d %d %d\n", i, hier->nodeInfo[i].id, hier->nodeInfo[i].index, hier->nodeInfo[i].flags); + stream->read32(&skin->inverseMatrices[i*16], 64); + + Matrix mat; + Matrix::invert(&mat, (Matrix*)&skin->inverseMatrices[i*16]); +// printf("[ [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" +// " [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" +// " [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" +// " [ %8.4f, %8.4f, %8.4f, %8.4f ] ]\n" +// " %08x == flags\n", +// mat.right.x, mat.up.x, mat.at.x, mat.pos.x, +// mat.right.y, mat.up.y, mat.at.y, mat.pos.y, +// mat.right.z, mat.up.z, mat.at.z, mat.pos.z, +// 0.0f, 0.0f, 0.0f, 1.0f, +// mat.flags); + } + Frame *frame = atomic->getFrame()->child; + assert(frame->next == nil); // in old files atomic is above hierarchy it seems + assert(frame->count() == numBones); // assuming one frame per node this should also be true + HAnimData::get(frame)->hierarchy = hier; + hier->parentFrame = frame; + + return stream; +} + +static void +skinRights(void *object, int32, int32, uint32) +{ + Skin::setPipeline((Atomic*)object, 1); +} + +static void* +createSkinAtm(void *object, int32 offset, int32) +{ + *PLUGINOFFSET(void*, object, offset) = nil; + return object; +} + +static void* +destroySkinAtm(void *object, int32 offset, int32) +{ + return object; +} + +static void* +copySkinAtm(void *dst, void *src, int32 offset, int32) +{ + *PLUGINOFFSET(void*, dst, offset) = *PLUGINOFFSET(void*, src, offset); + return dst; +} + +static void* +skinOpen(void *o, int32, int32) +{ + // init dummy pipelines + skinGlobals.dummypipe = ObjPipeline::create(); + skinGlobals.dummypipe->pluginID = ID_SKIN; + skinGlobals.dummypipe->pluginData = 1; + for(uint i = 0; i < nelem(skinGlobals.pipelines); i++) + skinGlobals.pipelines[i] = skinGlobals.dummypipe; + return o; +} + +static void* +skinClose(void *o, int32, int32) +{ + for(uint i = 0; i < nelem(skinGlobals.pipelines); i++) + if(skinGlobals.pipelines[i] == skinGlobals.dummypipe) + matFXGlobals.pipelines[i] = nil; + skinGlobals.dummypipe->destroy(); + skinGlobals.dummypipe = nil; + return o; +} + +void +registerSkinPlugin(void) +{ + Driver::registerPlugin(PLATFORM_NULL, 0, ID_SKIN, + skinOpen, skinClose); + ps2::initSkin(); + xbox::initSkin(); + d3d8::initSkin(); + d3d9::initSkin(); + wdgl::initSkin(); + gl3::initSkin(); + + int32 o; + o = Geometry::registerPlugin(sizeof(Skin*), ID_SKIN, + createSkin, destroySkin, copySkin); + Geometry::registerPluginStream(ID_SKIN, + readSkin, writeSkin, getSizeSkin); + skinGlobals.geoOffset = o; + o = Atomic::registerPlugin(sizeof(HAnimHierarchy*),ID_SKIN, + createSkinAtm, destroySkinAtm, copySkinAtm); + skinGlobals.atomicOffset = o; + Atomic::registerPluginStream(ID_SKIN, readSkinLegacy, nil, nil); + Atomic::setStreamRightsCallback(ID_SKIN, skinRights); +} + +void +Skin::init(int32 numBones, int32 numUsedBones, int32 numVertices) +{ + this->numBones = numBones; + this->numUsedBones = numUsedBones; + uint32 size = this->numUsedBones + + this->numBones*64 + + numVertices*(16+4) + 0xF; + this->data = rwNewT(uint8, size, MEMDUR_EVENT | ID_SKIN); + uint8 *p = this->data; + + this->usedBones = nil; + if(this->numUsedBones){ + this->usedBones = p; + p += this->numUsedBones; + } + + p = (uint8*)(((uintptr)p + 0xF) & ~0xF); + this->inverseMatrices = nil; + if(this->numBones){ + this->inverseMatrices = (float*)p; + p += 64*this->numBones; + } + + this->indices = nil; + if(numVertices){ + this->indices = p; + p += 4*numVertices; + } + + this->weights = nil; + if(numVertices) + this->weights = (float*)p; + + this->boneLimit = 0; + this->numMeshes = 0; + this->rleSize = 0; + this->remapIndices = nil; + this->rleCount = nil; + this->rle = nil; + + this->platformData = nil; +} + + + +//static_assert(sizeof(Skin::RLEcount) == 2, "RLEcount size"); +//static_assert(sizeof(Skin::RLE) == 2, "RLE size"); + +void +Skin::findNumWeights(int32 numVertices) +{ + this->numWeights = 1; + float *w = this->weights; + while(numVertices--){ + while(w[this->numWeights] != 0.0f){ + this->numWeights++; + if(this->numWeights == 4) + return; + } + w += 4; + } +} + +void +Skin::findUsedBones(int32 numVertices) +{ + uint8 usedTab[256]; + uint8 *indices = this->indices; + float *weights = this->weights; + memset(usedTab, 0, 256); + while(numVertices--){ + for(int32 i = 0; i < this->numWeights; i++){ + if(weights[i] == 0.0f) + continue; // TODO: this could probably be optimized + if(usedTab[indices[i]] == 0) + usedTab[indices[i]]++; + } + indices += 4; + weights += 4; + } + this->numUsedBones = 0; + for(int32 i = 0; i < 256; i++) + if(usedTab[i]) + this->usedBones[this->numUsedBones++] = i; +} + +void +Skin::setPipeline(Atomic *a, int32 type) +{ + (void)type; + a->pipeline = skinGlobals.pipelines[rw::platform]; +} + +} diff --git a/src/texture.cpp b/src/texture.cpp new file mode 100644 index 0000000..fcd9597 --- /dev/null +++ b/src/texture.cpp @@ -0,0 +1,520 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "ps2/rwps2.h" +#include "d3d/rwd3d.h" +#include "d3d/rwxbox.h" +#include "d3d/rwd3d8.h" +#include "d3d/rwd3d9.h" +#include "gl/rwgl3.h" + +#define PLUGIN_ID 0 + +namespace rw { + +int32 Texture::numAllocated; +int32 TexDictionary::numAllocated; + +PluginList TexDictionary::s_plglist(sizeof(TexDictionary)); +PluginList Texture::s_plglist(sizeof(Texture)); +PluginList Raster::s_plglist(sizeof(Raster)); + +struct TextureGlobals +{ + TexDictionary *initialTexDict; + TexDictionary *currentTexDict; + // load textures from files + bool32 loadTextures; + // create dummy textures to store just names + bool32 makeDummies; + bool32 mipmapping; + bool32 autoMipmapping; + LinkList texDicts; + + LinkList textures; +}; +int32 textureModuleOffset; + +#define TEXTUREGLOBAL(v) (PLUGINOFFSET(TextureGlobals, engine, textureModuleOffset)->v) + +static void* +textureOpen(void *object, int32 offset, int32 size) +{ + TexDictionary *texdict; + textureModuleOffset = offset; + TEXTUREGLOBAL(texDicts).init(); + TEXTUREGLOBAL(textures).init(); + texdict = TexDictionary::create(); + TEXTUREGLOBAL(initialTexDict) = texdict; + TexDictionary::setCurrent(texdict); + TEXTUREGLOBAL(loadTextures) = 1; + TEXTUREGLOBAL(makeDummies) = 0; + TEXTUREGLOBAL(mipmapping) = 0; + TEXTUREGLOBAL(autoMipmapping) = 0; + return object; +} +static void* +textureClose(void *object, int32 offset, int32 size) +{ + FORLIST(lnk, TEXTUREGLOBAL(texDicts)) + TexDictionary::fromLink(lnk)->destroy(); + TEXTUREGLOBAL(initialTexDict) = nil; + TEXTUREGLOBAL(currentTexDict) = nil; + + FORLIST(lnk, TEXTUREGLOBAL(textures)){ + Texture *tex = LLLinkGetData(lnk, Texture, inGlobalList); + printf("Tex still allocated: %d %s %s\n", tex->refCount, tex->name, tex->mask); + assert(tex->dict == nil); + tex->destroy(); + } + return object; +} + +void +Texture::registerModule(void) +{ + Engine::registerPlugin(sizeof(TextureGlobals), ID_TEXTUREMODULE, textureOpen, textureClose); +} + +void +Texture::setLoadTextures(bool32 b) +{ + TEXTUREGLOBAL(loadTextures) = b; +} + +void +Texture::setCreateDummies(bool32 b) +{ + TEXTUREGLOBAL(makeDummies) = b; +} + +void Texture::setMipmapping(bool32 b) { TEXTUREGLOBAL(mipmapping) = b; } +void Texture::setAutoMipmapping(bool32 b) { TEXTUREGLOBAL(autoMipmapping) = b; } +bool32 Texture::getMipmapping(void) { return TEXTUREGLOBAL(mipmapping); } +bool32 Texture::getAutoMipmapping(void) { return TEXTUREGLOBAL(autoMipmapping); } + +// +// TexDictionary +// + +TexDictionary* +TexDictionary::create(void) +{ + TexDictionary *dict = (TexDictionary*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_TEXDICTIONARY); + if(dict == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + dict->object.init(TexDictionary::ID, 0); + dict->textures.init(); + TEXTUREGLOBAL(texDicts).add(&dict->inGlobalList); + s_plglist.construct(dict); + return dict; +} + +void +TexDictionary::destroy(void) +{ + if(TEXTUREGLOBAL(currentTexDict) == this) + TEXTUREGLOBAL(currentTexDict) = nil; + FORLIST(lnk, this->textures){ + Texture *tex = Texture::fromDict(lnk); + this->remove(tex); + tex->destroy(); + } + s_plglist.destruct(this); + this->inGlobalList.remove(); + rwFree(this); + numAllocated--; +} + +void +TexDictionary::add(Texture *t) +{ + if(t->dict) + t->inDict.remove(); + t->dict = this; + this->textures.append(&t->inDict); +} + +void +TexDictionary::remove(Texture *t) +{ + assert(t->dict == this); + t->inDict.remove(); + t->dict = nil; +} + +void +TexDictionary::addFront(Texture *t) +{ + if(t->dict) + t->inDict.remove(); + t->dict = this; + this->textures.add(&t->inDict); +} + +Texture* +TexDictionary::find(const char *name) +{ + FORLIST(lnk, this->textures){ + Texture *tex = Texture::fromDict(lnk); + if(strncmp_ci(tex->name, name, 32) == 0) + return tex; + } + return nil; +} + +TexDictionary* +TexDictionary::streamRead(Stream *stream) +{ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + int32 numTex = stream->readI16(); + stream->readI16(); // device id (0 = unknown, 1 = d3d8, 2 = d3d9, + // 3 = gcn, 4 = null, 5 = opengl, + // 6 = ps2, 7 = softras, 8 = xbox, 9 = psp) + TexDictionary *txd = TexDictionary::create(); + if(txd == nil) + return nil; + Texture *tex; + for(int32 i = 0; i < numTex; i++){ + if(!findChunk(stream, ID_TEXTURENATIVE, nil, nil)){ + RWERROR((ERR_CHUNK, "TEXTURENATIVE")); + goto fail; + } + tex = Texture::streamReadNative(stream); + if(tex == nil) + goto fail; + Texture::s_plglist.streamRead(stream, tex); + txd->add(tex); + } + if(s_plglist.streamRead(stream, txd)) + return txd; +fail: + txd->destroy(); + return nil; +} + +void +TexDictionary::streamWrite(Stream *stream) +{ + writeChunkHeader(stream, ID_TEXDICTIONARY, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, 4); + int32 numTex = this->count(); + stream->writeI16(numTex); + stream->writeI16(0); + FORLIST(lnk, this->textures){ + Texture *tex = Texture::fromDict(lnk); + uint32 sz = tex->streamGetSizeNative(); + sz += 12 + Texture::s_plglist.streamGetSize(tex); + writeChunkHeader(stream, ID_TEXTURENATIVE, sz); + tex->streamWriteNative(stream); + Texture::s_plglist.streamWrite(stream, tex); + } + s_plglist.streamWrite(stream, this); +} + +uint32 +TexDictionary::streamGetSize(void) +{ + uint32 size = 12 + 4; + FORLIST(lnk, this->textures){ + Texture *tex = Texture::fromDict(lnk); + size += 12 + tex->streamGetSizeNative(); + size += 12 + Texture::s_plglist.streamGetSize(tex); + } + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +void +TexDictionary::setCurrent(TexDictionary *txd) +{ + PLUGINOFFSET(TextureGlobals, engine, textureModuleOffset)->currentTexDict = txd; +} + +TexDictionary* +TexDictionary::getCurrent(void) +{ + return PLUGINOFFSET(TextureGlobals, engine, textureModuleOffset)->currentTexDict; +} + +// +// Texture +// + +static Texture *defaultFindCB(const char *name); +static Texture *defaultReadCB(const char *name, const char *mask); + +Texture *(*Texture::findCB)(const char *name) = defaultFindCB; +Texture *(*Texture::readCB)(const char *name, const char *mask) = defaultReadCB; + +Texture* +Texture::create(Raster *raster) +{ + Texture *tex = (Texture*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_TEXTURE); + if(tex == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + tex->dict = nil; + tex->inDict.init(); + memset(tex->name, 0, 32); + memset(tex->mask, 0, 32); + tex->filterAddressing = (WRAP << 12) | (WRAP << 8) | NEAREST; + tex->raster = raster; + tex->refCount = 1; + TEXTUREGLOBAL(textures).add(&tex->inGlobalList); + s_plglist.construct(tex); + return tex; +} + +void +Texture::destroy(void) +{ + this->refCount--; + if(this->refCount <= 0){ + s_plglist.destruct(this); + if(this->dict) + this->inDict.remove(); + if(this->raster) + this->raster->destroy(); + this->inGlobalList.remove(); + rwFree(this); + numAllocated--; + } +} + +static Texture* +defaultFindCB(const char *name) +{ + if(TEXTUREGLOBAL(currentTexDict)) + return TEXTUREGLOBAL(currentTexDict)->find(name); + // TODO: RW searches *all* TXDs otherwise + return nil; +} + + +static Texture* +defaultReadCB(const char *name, const char *mask) +{ + Texture *tex; + Image *img; + + img = Image::readMasked(name, mask); + if(img){ + tex = Texture::create(Raster::createFromImage(img)); + strncpy(tex->name, name, 32); + if(mask) + strncpy(tex->mask, mask, 32); + img->destroy(); + return tex; + }else + return nil; +} + +Texture* +Texture::read(const char *name, const char *mask) +{ + (void)mask; + Raster *raster = nil; + Texture *tex; + + if(tex = Texture::findCB(name), tex){ + tex->addRef(); + return tex; + } + if(TEXTUREGLOBAL(loadTextures)){ + tex = Texture::readCB(name, mask); + if(tex == nil) + goto dummytex; + }else dummytex: if(TEXTUREGLOBAL(makeDummies)){ +//printf("missing texture %s %s\n", name ? name : "", mask ? mask : ""); + tex = Texture::create(nil); + if(tex == nil) + return nil; + strncpy(tex->name, name, 32); + if(mask) + strncpy(tex->mask, mask, 32); + raster = Raster::create(0, 0, 0, Raster::DONTALLOCATE); + tex->raster = raster; + } + if(tex && TEXTUREGLOBAL(currentTexDict)){ + if(tex->dict) + tex->inDict.remove(); + TEXTUREGLOBAL(currentTexDict)->add(tex); + } + return tex; +} + +Texture* +Texture::streamRead(Stream *stream) +{ + uint32 length; + char name[128], mask[128]; + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + uint32 filterAddressing = stream->readU32(); + // if V addressing is 0, copy U + if((filterAddressing & 0xF000) == 0) + filterAddressing |= (filterAddressing&0xF00) << 4; + + // if using mipmap filter mode, set automipmapping, + // if 0x10000 is set, set mipmapping + + if(!findChunk(stream, ID_STRING, &length, nil)){ + RWERROR((ERR_CHUNK, "STRING")); + return nil; + } + stream->read8(name, length); + + if(!findChunk(stream, ID_STRING, &length, nil)){ + RWERROR((ERR_CHUNK, "STRING")); + return nil; + } + stream->read8(mask, length); + + bool32 mipState = getMipmapping(); + bool32 autoMipState = getAutoMipmapping(); + int32 filter = filterAddressing&0xFF; + if(filter == MIPNEAREST || filter == MIPLINEAR || + filter == LINEARMIPNEAREST || filter == LINEARMIPLINEAR){ + setMipmapping(1); + setAutoMipmapping((filterAddressing&0x10000) == 0); + }else{ + setMipmapping(0); + setAutoMipmapping(0); + } + + Texture *tex = Texture::read(name, mask); + + setMipmapping(mipState); + setAutoMipmapping(autoMipState); + + if(tex == nil){ + s_plglist.streamSkip(stream); + return nil; + } + if(tex->refCount == 1) + tex->filterAddressing = filterAddressing&0xFFFF; + + if(s_plglist.streamRead(stream, tex)) + return tex; + + tex->destroy(); + return nil; +} + +bool +Texture::streamWrite(Stream *stream) +{ + int size; + char buf[36]; + writeChunkHeader(stream, ID_TEXTURE, this->streamGetSize()); + writeChunkHeader(stream, ID_STRUCT, 4); + uint32 filterAddressing = this->filterAddressing; + if(this->raster && (raster->format & Raster::AUTOMIPMAP) == 0) + filterAddressing |= 0x10000; + stream->writeU32(filterAddressing); + + memset(buf, 0, 36); + strncpy(buf, this->name, 32); + size = strlen(buf)+4 & ~3; + writeChunkHeader(stream, ID_STRING, size); + stream->write8(buf, size); + + memset(buf, 0, 36); + strncpy(buf, this->mask, 32); + size = strlen(buf)+4 & ~3; + writeChunkHeader(stream, ID_STRING, size); + stream->write8(buf, size); + + s_plglist.streamWrite(stream, this); + return true; +} + +uint32 +Texture::streamGetSize(void) +{ + uint32 size = 0; + size += 12 + 4; + size += 12 + 12; + size += strlen(this->name)+4 & ~3; + size += strlen(this->mask)+4 & ~3; + size += 12 + s_plglist.streamGetSize(this); + return size; +} + +Texture* +Texture::streamReadNative(Stream *stream) +{ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + uint32 platform = stream->readU32(); + stream->seek(-16); + if(platform == FOURCC_PS2) + return ps2::readNativeTexture(stream); + if(platform == PLATFORM_D3D8) + return d3d8::readNativeTexture(stream); + if(platform == PLATFORM_D3D9) + return d3d9::readNativeTexture(stream); + if(platform == PLATFORM_XBOX) + return xbox::readNativeTexture(stream); + if(platform == PLATFORM_GL3) + return gl3::readNativeTexture(stream); + assert(0 && "unsupported platform"); + return nil; +} + +void +Texture::streamWriteNative(Stream *stream) +{ + if(this->raster->platform == PLATFORM_PS2) + ps2::writeNativeTexture(this, stream); + else if(this->raster->platform == PLATFORM_D3D8) + d3d8::writeNativeTexture(this, stream); + else if(this->raster->platform == PLATFORM_D3D9) + d3d9::writeNativeTexture(this, stream); + else if(this->raster->platform == PLATFORM_XBOX) + xbox::writeNativeTexture(this, stream); + else if(this->raster->platform == PLATFORM_GL3) + gl3::writeNativeTexture(this, stream); + else + assert(0 && "unsupported platform"); +} + +uint32 +Texture::streamGetSizeNative(void) +{ + if(this->raster->platform == PLATFORM_PS2) + return ps2::getSizeNativeTexture(this); + if(this->raster->platform == PLATFORM_D3D8) + return d3d8::getSizeNativeTexture(this); + if(this->raster->platform == PLATFORM_D3D9) + return d3d9::getSizeNativeTexture(this); + if(this->raster->platform == PLATFORM_XBOX) + return xbox::getSizeNativeTexture(this); + if(this->raster->platform == PLATFORM_GL3) + return gl3::getSizeNativeTexture(this); + assert(0 && "unsupported platform"); + return 0; +} + +} diff --git a/src/tga.cpp b/src/tga.cpp new file mode 100644 index 0000000..cbe845f --- /dev/null +++ b/src/tga.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 0 + +namespace rw { + + +// NB: this has padding and cannot be streamed directly! +struct TGAHeader +{ + int8 IDlen; + int8 colorMapType; + int8 imageType; + int16 colorMapOrigin; + int16 colorMapLength; + int8 colorMapDepth; + int16 xOrigin, yOrigin; + int16 width, height; + uint8 depth; + uint8 descriptor; + + void read(Stream *stream); + void write(Stream *stream); +}; + +void +TGAHeader::read(Stream *stream) +{ + IDlen = stream->readI8(); + colorMapType = stream->readI8(); + imageType = stream->readI8(); + colorMapOrigin = stream->readI16(); + colorMapLength = stream->readI16(); + colorMapDepth = stream->readI8(); + xOrigin = stream->readI16(); + yOrigin = stream->readI16(); + width = stream->readI16(); + height = stream->readI16(); + depth = stream->readU8(); + descriptor = stream->readU8(); +} + +void +TGAHeader::write(Stream *stream) +{ + stream->writeI8(IDlen); + stream->writeI8(colorMapType); + stream->writeI8(imageType); + stream->writeI16(colorMapOrigin); + stream->writeI16(colorMapLength); + stream->writeI8(colorMapDepth); + stream->writeI16(xOrigin); + stream->writeI16(yOrigin); + stream->writeI16(width); + stream->writeI16(height); + stream->writeU8(depth); + stream->writeU8(descriptor); +} + +Image* +readTGA(const char *filename) +{ + TGAHeader header; + Image *image; + int depth = 0, palDepth = 0; + uint32 length; + uint8 *data = getFileContents(filename, &length); + assert(data != nil); + StreamMemory file; + file.open(data, length); + header.read(&file); + + assert(header.imageType == 1 || header.imageType == 2); + file.seek(header.IDlen); + if(header.colorMapType){ + assert(header.colorMapOrigin == 0); + depth = (header.colorMapLength <= 16) ? 4 : 8; + palDepth = header.colorMapDepth; + assert(palDepth == 24 || palDepth == 32); + }else{ + depth = header.depth; + assert(depth == 16 || depth == 24 || depth == 32); + } + + image = Image::create(header.width, header.height, depth); + image->allocate(); + uint8 *palette = header.colorMapType ? image->palette : nil; + uint8 (*color)[4] = nil; + if(palette){ + int maxlen = depth == 4 ? 16 : 256; + color = (uint8(*)[4])palette; + int i; + for(i = 0; i < header.colorMapLength; i++){ + color[i][2] = file.readU8(); + color[i][1] = file.readU8(); + color[i][0] = file.readU8(); + color[i][3] = 0xFF; + if(palDepth == 32) + color[i][3] = file.readU8(); + } + for(; i < maxlen; i++){ + color[i][0] = color[i][1] = color[i][2] = 0; + color[i][3] = 0xFF; + } + } + + uint8 *pixels = image->pixels; + if(!(header.descriptor & 0x20)) + pixels += (image->height-1)*image->stride; + for(int y = 0; y < image->height; y++){ + uint8 *line = pixels; + for(int x = 0; x < image->width; x++){ + switch(image->depth){ + case 4: + case 8: + line[0] = file.readU8(); + break; + case 16: + line[0] = file.readU8(); + line[1] = file.readU8(); + break; + case 24: + line[2] = file.readU8(); + line[1] = file.readU8(); + line[0] = file.readU8(); + break; + case 32: + line[2] = file.readU8(); + line[1] = file.readU8(); + line[0] = file.readU8(); + line[3] = file.readU8(); + break; + } + line += image->bpp; + } + pixels += (header.descriptor&0x20) ? + image->stride : -image->stride; + } + + file.close(); + rwFree(data); + return image; +} + +void +writeTGA(Image *image, const char *filename) +{ + TGAHeader header; + StreamFile file; + if(!file.open(filename, "wb")){ + RWERROR((ERR_FILE, filename)); + return; + } + header.IDlen = 0; + header.imageType = image->palette != nil ? 1 : 2; + header.colorMapType = image->palette != nil; + header.colorMapOrigin = 0; + header.colorMapLength = image->depth == 4 ? 16 : + image->depth == 8 ? 256 : 0; + header.colorMapDepth = image->palette ? 32 : 0; + header.xOrigin = 0; + header.yOrigin = 0; + header.width = image->width; + header.height = image->height; + header.depth = image->depth == 4 ? 8 : image->depth; + header.descriptor = 0x20 | (image->depth == 32 ? 8 : 0); + header.write(&file); + + uint8 *palette = header.colorMapType ? image->palette : nil; + uint8 (*color)[4] = (uint8(*)[4])palette;; + if(palette) + for(int i = 0; i < header.colorMapLength; i++){ + file.writeU8(color[i][2]); + file.writeU8(color[i][1]); + file.writeU8(color[i][0]); + file.writeU8(color[i][3]); + } + + uint8 *line = image->pixels; + uint8 *p; + for(int y = 0; y < image->height; y++){ + p = line; + for(int x = 0; x < image->width; x++){ + switch(image->depth){ + case 4: + case 8: + file.writeU8(p[0]); + break; + case 16: + file.writeU8(p[0]); + file.writeU8(p[1]); + break; + case 24: + file.writeU8(p[2]); + file.writeU8(p[1]); + file.writeU8(p[0]); + break; + case 32: + file.writeU8(p[2]); + file.writeU8(p[1]); + file.writeU8(p[0]); + file.writeU8(p[3]); + break; + } + p += image->bpp; + } + line += image->stride; + } + file.close(); +} + +} diff --git a/src/tristrip.cpp b/src/tristrip.cpp new file mode 100644 index 0000000..e71b844 --- /dev/null +++ b/src/tristrip.cpp @@ -0,0 +1,682 @@ +#include +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID 2 + +namespace rw { + +struct GraphEdge +{ + int32 node; /* index of the connected node */ + uint32 isConnected : 1; /* is connected to other node */ + uint32 otherEdge : 2; /* edge number on connected node */ + uint32 isStrip : 1; /* is strip edge */ +}; + +struct StripNode +{ + uint16 v[3]; /* vertex indices */ + uint8 parent : 2; /* tunnel parent node (edge index) */ + uint8 visited : 1; /* visited in breadth first search */ + uint8 stripVisited : 1; /* strip starting at this node was visited during search */ + uint8 isEnd : 1; /* is in end list */ + GraphEdge e[3]; + int32 stripId; /* index of start node */ + LLLink inlist; +}; + +struct StripMesh +{ + int32 numNodes; + StripNode *nodes; + LinkList loneNodes; /* nodes not connected to any others */ + LinkList endNodes; /* strip start/end nodes */ +}; + +//#define trace(...) printf(__VA_ARGS__) +#define trace(...) + +static void +printNode(StripMesh *sm, StripNode *n) +{ + trace("%3ld: %3d %3d.%d %3d.%d %3d.%d || %3d %3d %3d\n", + n - sm->nodes, + n->stripId, + n->e[0].node, + n->e[0].isStrip, + n->e[1].node, + n->e[1].isStrip, + n->e[2].node, + n->e[2].isStrip, + n->v[0], + n->v[1], + n->v[2]); +} + +static void +printLone(StripMesh *sm) +{ + FORLIST(lnk, sm->loneNodes) + printNode(sm, LLLinkGetData(lnk, StripNode, inlist)); +} + +static void +printEnds(StripMesh *sm) +{ + FORLIST(lnk, sm->endNodes) + printNode(sm, LLLinkGetData(lnk, StripNode, inlist)); +} + +static void +printSmesh(StripMesh *sm) +{ + for(int32 i = 0; i < sm->numNodes; i++) + printNode(sm, &sm->nodes[i]); +} + +static void +collectFaces(Geometry *geo, StripMesh *sm, uint16 m) +{ + StripNode *n; + Triangle *t; + sm->numNodes = 0; + for(int32 i = 0; i < geo->numTriangles; i++){ + t = &geo->triangles[i]; + if(t->matId == m){ + n = &sm->nodes[sm->numNodes++]; + n->v[0] = t->v[0]; + n->v[1] = t->v[1]; + n->v[2] = t->v[2]; + assert(t->v[0] < geo->numVertices); + assert(t->v[1] < geo->numVertices); + assert(t->v[2] < geo->numVertices); + n->e[0].node = 0; + n->e[1].node = 0; + n->e[2].node = 0; + n->e[0].isConnected = 0; + n->e[1].isConnected = 0; + n->e[2].isConnected = 0; + n->e[0].isStrip = 0; + n->e[1].isStrip = 0; + n->e[2].isStrip = 0; + n->parent = 0; + n->visited = 0; + n->stripVisited = 0; + n->isEnd = 0; + n->stripId = -1; + n->inlist.init(); + } + } +} + +/* Find Triangle that has edge e that is not connected yet. */ +static GraphEdge +findEdge(StripMesh *sm, int32 e[2]) +{ + StripNode *n; + GraphEdge ge = { 0, 0, 0, 0 }; + for(int32 i = 0; i < sm->numNodes; i++){ + n = &sm->nodes[i]; + for(int32 j = 0; j < 3; j++){ + if(n->e[j].isConnected) + continue; + if(e[0] == n->v[j] && + e[1] == n->v[(j+1) % 3]){ + ge.node = i; + // signal success + ge.isConnected = 1; + ge.otherEdge = j; + return ge; + } + } + } + return ge; +} + +/* Connect nodes sharing an edge, preserving winding */ +static void +connectNodesPreserve(StripMesh *sm) +{ + StripNode *n, *nn; + int32 e[2]; + GraphEdge ge; + for(int32 i = 0; i < sm->numNodes; i++){ + n = &sm->nodes[i]; + for(int32 j = 0; j < 3; j++){ + if(n->e[j].isConnected) + continue; + + /* flip edge and search for node */ + e[1] = n->v[j]; + e[0] = n->v[(j+1) % 3]; + ge = findEdge(sm, e); + if(ge.isConnected){ + /* found node, now connect */ + n->e[j].node = ge.node; + n->e[j].isConnected = 1; + n->e[j].otherEdge = ge.otherEdge; + n->e[j].isStrip = 0; + nn = &sm->nodes[ge.node]; + nn->e[ge.otherEdge].node = i; + nn->e[ge.otherEdge].isConnected = 1; + nn->e[ge.otherEdge].otherEdge = j; + nn->e[ge.otherEdge].isStrip = 0; + } + } + } +} + +static int32 +numConnections(StripNode *n) +{ + return n->e[0].isConnected + + n->e[1].isConnected + + n->e[2].isConnected; +} + +static int32 +numStripEdges(StripNode *n) +{ + return n->e[0].isStrip + + n->e[1].isStrip + + n->e[2].isStrip; +} + +#define IsEnd(n) (numConnections(n) > 0 && numStripEdges(n) < 2) + +/* Complement the strip-ness of an edge */ +static void +complementEdge(StripMesh *sm, GraphEdge *e) +{ + e->isStrip = !e->isStrip; + e = &sm->nodes[e->node].e[e->otherEdge]; + e->isStrip = !e->isStrip; +} + +/* While possible extend a strip from a starting node until + * we find a node already in a strip. N.B. this function + * makes no attempts to connect to an already existing strip. + * It also doesn't try to alternate between left and right. */ +static void +extendStrip(StripMesh *sm, StripNode *start) +{ + StripNode *n, *nn; + n = start; + if(numConnections(n) == 0){ + sm->loneNodes.append(&n->inlist); + return; + } + sm->endNodes.append(&n->inlist); + n->isEnd = 1; +loop: + /* Find the next node to connect to on any of the three edges */ + for(int32 i = 0; i < 3; i++){ + if(!n->e[i].isConnected) + continue; + nn = &sm->nodes[n->e[i].node]; + if(nn->stripId >= 0) + continue; + + /* found one */ + nn->stripId = n->stripId; + /* We know it's not a strip edge yet, + * so complementing it will make it one. */ + complementEdge(sm, &n->e[i]); + n = nn; + goto loop; + } + if(n != start){ + sm->endNodes.append(&n->inlist); + n->isEnd = 1; + } +} + +static void +buildStrips(StripMesh *sm) +{ + StripNode *n; + for(int32 i = 0; i < sm->numNodes; i++){ + n = &sm->nodes[i]; + if(n->stripId >= 0) + continue; + n->stripId = i; + extendStrip(sm, n); + } +} + +static StripNode* +findTunnel(StripMesh *sm, StripNode *n) +{ + LinkList searchNodes; + StripNode *nn; + int edgetype; + int isEnd; + + searchNodes.init(); + edgetype = 0; + for(;;){ + for(int32 i = 0; i < 3; i++){ + /* Find a node connected by the right edgetype */ + if(!n->e[i].isConnected || + n->e[i].isStrip != edgetype) + continue; + nn = &sm->nodes[n->e[i].node]; + + /* If the node has been visited already, + * there's a shorter path. */ + if(nn->visited) + continue; + + /* Don't allow non-strip edge between nodes of the same + * strip to prevent loops. + * Actually these edges are allowed under certain + * circumstances, but they require complex checks. */ + if(edgetype == 0 && + n->stripId == nn->stripId) + continue; + + isEnd = IsEnd(nn); + + /* Can't add end nodes to two lists, so skip. */ + if(isEnd && edgetype == 1) + continue; + + nn->parent = n->e[i].otherEdge; + nn->visited = 1; + sm->nodes[nn->stripId].stripVisited = 1; + + /* Search complete. */ + if(isEnd && edgetype == 0) + return nn; + + /* Found a valid node. */ + searchNodes.append(&nn->inlist); + } + if(searchNodes.isEmpty()) + return nil; + n = LLLinkGetData(searchNodes.link.next, StripNode, inlist); + n->inlist.remove(); + edgetype = !edgetype; + } +} + +static void +resetGraph(StripMesh *sm) +{ + StripNode *n; + for(int32 i = 0; i < sm->numNodes; i++){ + n = &sm->nodes[i]; + n->visited = 0; + n->stripVisited = 0; + } +} + +static StripNode* +walkStrip(StripMesh *sm, StripNode *start) +{ + StripNode *n, *nn; + int32 last; + +//trace("stripend: "); +//printNode(sm, start); + + n = start; + last = -1; + for(;;n = nn){ + n->visited = 0; + n->stripVisited = 0; + if(n->isEnd) + n->inlist.remove(); + n->isEnd = 0; + + if(IsEnd(n) && n != start) + return n; + + /* find next node */ + nn = nil; + for(int32 i = 0; i < 3; i++){ + if(!n->e[i].isStrip || i == last) + continue; + nn = &sm->nodes[n->e[i].node]; + last = n->e[i].otherEdge; + nn->stripId = n->stripId; + break; + } +//trace(" next: "); +//printNode(sm, nn); + if(nn == nil) + return nil; + } +} + +static void +applyTunnel(StripMesh *sm, StripNode *end, StripNode *start) +{ + LinkList tmplist; + StripNode *n, *nn; + + for(n = end; n != start; n = &sm->nodes[n->e[n->parent].node]){ +//trace(" "); +//printNode(sm, n); + complementEdge(sm, &n->e[n->parent]); + } +//trace(" "); +//printNode(sm, start); + +//printSmesh(sm); +//trace("-------\n"); + tmplist.init(); + while(!sm->endNodes.isEmpty()){ + n = LLLinkGetData(sm->endNodes.link.next, StripNode, inlist); + /* take out of end list */ + n->inlist.remove(); + n->isEnd = 0; + /* no longer an end node */ + if(!IsEnd(n)) + continue; + // TODO: only walk strip if it was touched + /* set new id, walk strip and find other end */ + n->stripId = n - sm->nodes; + nn = walkStrip(sm, n); + tmplist.append(&n->inlist); + n->isEnd = 1; + if(nn && n != nn){ + tmplist.append(&nn->inlist); + nn->isEnd = 1; + } + } + /* Move new end nodes to the real list. */ + sm->endNodes = tmplist; + sm->endNodes.link.next->prev = &sm->endNodes.link; + sm->endNodes.link.prev->next = &sm->endNodes.link; +} + +static void +tunnel(StripMesh *sm) +{ + StripNode *n, *nn; + +again: + FORLIST(lnk, sm->endNodes){ + n = LLLinkGetData(lnk, StripNode, inlist); +// trace("searching %p %d\n", n, numStripEdges(n)); + nn = findTunnel(sm, n); +// trace(" %p %p\n", n, nn); + + if(nn){ + applyTunnel(sm, nn, n); + resetGraph(sm); + /* applyTunnel changes sm->endNodes, so we have to + * jump out of the loop. */ + goto again; + } + resetGraph(sm); + } + trace("tunneling done!\n"); +} + +/* Get next edge in strip. + * Last is the edge index whence we came lest we go back. */ +static int +getNextEdge(StripNode *n, int32 last) +{ + int32 i; + for(i = 0; i < 3; i++) + if(n->e[i].isStrip && i != last) + return i; + return -1; +} + +#define NEXT(x) (((x)+1) % 3) +#define PREV(x) (((x)+2) % 3) +#define RIGHT(x) NEXT(x) +#define LEFT(x) PREV(x) + +/* Generate mesh indices for all strips in a StripMesh */ +static void +makeMesh(StripMesh *sm, Mesh *m) +{ + int32 i, j; + int32 rightturn, lastrightturn; + int32 seam; + int32 even; + StripNode *n; + + /* three indices + two for stitch per triangle must be enough */ + m->indices = rwNewT(uint16, sm->numNodes*5, MEMDUR_FUNCTION | ID_GEOMETRY); + memset(m->indices, 0xFF, sm->numNodes*5*sizeof(uint16)); + + even = 1; + FORLIST(lnk, sm->endNodes){ + n = LLLinkGetData(lnk, StripNode, inlist); + /* only interested in start nodes, not the ends */ + if(n->stripId != (n - sm->nodes)) + continue; + + /* i is the edge we enter this triangle from. + * j is the edge we exit. */ + j = getNextEdge(n, -1); + /* starting triangle must have connection */ + if(j < 0) + continue; + /* Space to stitch together strips */ + seam = m->numIndices; + if(seam) + m->numIndices += 2; + /* Start ccw for even tris */ + if(even){ + /* Start with a right turn */ + i = LEFT(j); + m->indices[m->numIndices++] = n->v[i]; + m->indices[m->numIndices++] = n->v[NEXT(i)]; + }else{ + /* Start with a left turn */ + i = RIGHT(j); + m->indices[m->numIndices++] = n->v[NEXT(i)]; + m->indices[m->numIndices++] = n->v[i]; + } +trace("\nstart %d %d\n", numStripEdges(n), m->numIndices-2); + lastrightturn = -1; + + while(j >= 0){ + rightturn = RIGHT(i) == j; + if(rightturn == lastrightturn){ + // insert a swap if we're not alternating + m->indices[m->numIndices] = m->indices[m->numIndices-2]; +trace("SWAP\n"); + m->numIndices++; + even = !even; + } +trace("%d:%d%c %d %d %d\n", n-sm->nodes, m->numIndices, even ? ' ' : '.', n->v[0], n->v[1], n->v[2]); + lastrightturn = rightturn; + if(rightturn) + m->indices[m->numIndices++] = n->v[NEXT(j)]; + else + m->indices[m->numIndices++] = n->v[j]; + even = !even; + + /* go to next triangle */ + i = n->e[j].otherEdge; + n = &sm->nodes[n->e[j].node]; + j = getNextEdge(n, i); + } + + /* finish strip */ +trace("%d:%d%c %d %d %d\nend\n", n-sm->nodes, m->numIndices, even ? ' ' : '.', n->v[0], n->v[1], n->v[2]); + m->indices[m->numIndices++] = n->v[LEFT(i)]; + even = !even; + if(seam){ + m->indices[seam] = m->indices[seam-1]; + m->indices[seam+1] = m->indices[seam+2]; +trace("STITCH %d: %d %d\n", seam, m->indices[seam], m->indices[seam+1]); + } + } + + /* Add all unconnected and lonely triangles */ + FORLIST(lnk, sm->endNodes){ + n = LLLinkGetData(lnk, StripNode, inlist); + if(numStripEdges(n) != 0) + continue; + if(m->numIndices != 0){ + m->indices[m->numIndices] = m->indices[m->numIndices-1]; + m->numIndices++; + m->indices[m->numIndices++] = n->v[!even]; + } + m->indices[m->numIndices++] = n->v[!even]; + m->indices[m->numIndices++] = n->v[even]; + m->indices[m->numIndices++] = n->v[2]; + even = !even; + } + FORLIST(lnk, sm->loneNodes){ + n = LLLinkGetData(lnk, StripNode, inlist); + if(m->numIndices != 0){ + m->indices[m->numIndices] = m->indices[m->numIndices-1]; + m->numIndices++; + m->indices[m->numIndices++] = n->v[!even]; + } + m->indices[m->numIndices++] = n->v[!even]; + m->indices[m->numIndices++] = n->v[even]; + m->indices[m->numIndices++] = n->v[2]; + even = !even; + } +} + +static void verifyMesh(Geometry *geo); + +/* + * For each material: + * 1. build dual graph (collectFaces, connectNodes) + * 2. make some simple strip (buildStrips) + * 3. apply tunnel operator (tunnel) + */ +void +Geometry::buildTristrips(void) +{ + int32 i; + uint16 *indices; + MeshHeader *header; + Mesh *ms, *md; + StripMesh smesh; + +// trace("%ld\n", sizeof(StripNode)); + + this->allocateMeshes(matList.numMaterials, 0, 1); + + smesh.nodes = rwNewT(StripNode, this->numTriangles, MEMDUR_FUNCTION | ID_GEOMETRY); + ms = this->meshHeader->getMeshes(); + for(int32 i = 0; i < this->matList.numMaterials; i++){ + smesh.loneNodes.init(); + smesh.endNodes.init(); + collectFaces(this, &smesh, i); + connectNodesPreserve(&smesh); + buildStrips(&smesh); +printSmesh(&smesh); +//trace("-------\n"); +//printLone(&smesh); +//trace("-------\n"); +//printEnds(&smesh); +//trace("-------\n"); + // TODO: make this work +// tunnel(&smesh); +//trace("-------\n"); +//printEnds(&smesh); + + ms[i].material = this->matList.materials[i]; + makeMesh(&smesh, &ms[i]); + this->meshHeader->totalIndices += ms[i].numIndices; + } + rwFree(smesh.nodes); + + /* Now re-allocate and copy data */ + header = this->meshHeader; + this->meshHeader = nil; + this->allocateMeshes(header->numMeshes, header->totalIndices, 0); + this->meshHeader->flags = MeshHeader::TRISTRIP; + md = this->meshHeader->getMeshes(); + indices = md->indices; + for(i = 0; i < header->numMeshes; i++){ + md[i].material = ms[i].material; + md[i].numIndices = ms[i].numIndices; + md[i].indices = indices; + indices += md[i].numIndices; + memcpy(md[i].indices, ms[i].indices, md[i].numIndices*sizeof(uint16)); + rwFree(ms[i].indices); + } + rwFree(header); + + verifyMesh(this); +} + +/* Check that tristripped mesh and geometry triangles are actually the same. */ +static void +verifyMesh(Geometry *geo) +{ + int32 i, k; + uint32 j; + int32 x; + int32 a, b, c, m; + Mesh *mesh; + Triangle *t; + uint8 *seen; + + seen = rwNewT(uint8, geo->numTriangles, MEMDUR_FUNCTION | ID_GEOMETRY); + memset(seen, 0, geo->numTriangles); + + mesh = geo->meshHeader->getMeshes(); + for(i = 0; i < geo->meshHeader->numMeshes; i++){ + m = geo->matList.findIndex(mesh->material); + x = 0; + for(j = 0; j < mesh->numIndices-2; j++){ + a = mesh->indices[j+x]; + x = !x; + b = mesh->indices[j+x]; + c = mesh->indices[j+2]; + if(a >= geo->numVertices || + b >= geo->numVertices || + c >= geo->numVertices){ + fprintf(stderr, "triangle %d %d %d out of range (%d)\n", a, b, c, geo->numVertices); + goto loss; + } + if(a == b || a == c || b == c) + continue; +trace("%d %d %d\n", a, b, c); + + /* now that we have a triangle, try to find it */ + for(k = 0; k < geo->numTriangles; k++){ + t = &geo->triangles[k]; + if(seen[k] || t->matId != m) continue; + if(t->v[0] == a && t->v[1] == b && t->v[2] == c || + t->v[1] == a && t->v[2] == b && t->v[0] == c || + t->v[2] == a && t->v[0] == b && t->v[1] == c){ + seen[k] = 1; + goto found; + } + } + goto loss; + found: ; + } + mesh++; + } + + /* Also check that all triangles are in the mesh */ + for(i = 0; i < geo->numTriangles; i++) + if(!seen[i]){ + loss: + fprintf(stderr, "TRISTRIP verify failed\n"); + exit(1); + } + + rwFree(seen); +} + +} diff --git a/src/userdata.cpp b/src/userdata.cpp new file mode 100644 index 0000000..e17a3d5 --- /dev/null +++ b/src/userdata.cpp @@ -0,0 +1,364 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwuserdata.h" + +#define PLUGIN_ID ID_USERDATA + +namespace rw { + +UserDataGlobals userDataGlobals; + +#define udMalloc(sz) rwMalloc(sz, MEMDUR_EVENT | ID_USERDATA) + +void +UserDataArray::setString(int32 n, const char *s) +{ + int32 len; + char **sp = &((char**)this->data)[n]; + rwFree(*sp); + len = (int32)strlen(s)+1; + *sp = (char*)udMalloc(len); + if(*sp) + strncpy(*sp, s, len); +} + +static void* +createUserData(void *object, int32 offset, int32) +{ + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + ext->numArrays = 0; + ext->arrays = nil; + return object; +} + +static void* +destroyUserData(void *object, int32 offset, int32) +{ + int32 i, j; + char **strar; + UserDataArray *a; + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + a = ext->arrays; + for(i = 0; i < ext->numArrays; i++){ + rwFree(a->name); + switch(a->datatype){ + case USERDATASTRING: + strar = (char**)a->data; + for(j = 0; j < a->numElements; j++) + rwFree(strar[j]); + /* fallthrough */ + case USERDATAINT: + case USERDATAFLOAT: + rwFree(a->data); + break; + } + a++; + } + rwFree(ext->arrays); + ext->numArrays = 0; + ext->arrays = nil; + return object; +} + +static void* +copyUserData(void *dst, void *src, int32 offset, int32) +{ + int32 i, j; + char **srcstrar, **dststrar; + UserDataArray *srca, *dsta; + UserDataExtension *srcext = PLUGINOFFSET(UserDataExtension, src, offset); + UserDataExtension *dstext = PLUGINOFFSET(UserDataExtension, dst, offset); + dstext->numArrays = srcext->numArrays; + dstext->arrays = (UserDataArray*)udMalloc(dstext->numArrays*sizeof(UserDataArray)); + srca = srcext->arrays; + dsta = srcext->arrays; + for(i = 0; i < srcext->numArrays; i++){ + int32 len = (int32)strlen(srca->name)+1; + dsta->name = (char*)udMalloc(len); + strncpy(dsta->name, srca->name, len); + dsta->datatype = srca->datatype; + dsta->numElements = srca->numElements; + switch(srca->datatype){ + case USERDATAINT: + dsta->data = (int32*)udMalloc(sizeof(int32)*dsta->numElements); + memcpy(dsta->data, srca->data, sizeof(int32)*dsta->numElements); + break; + case USERDATAFLOAT: + dsta->data = (float32*)udMalloc(sizeof(float32)*dsta->numElements); + memcpy(dsta->data, srca->data, sizeof(float32)*dsta->numElements); + break; + case USERDATASTRING: + dststrar = (char**)udMalloc(sizeof(char*)*dsta->numElements); + dsta->data = dststrar; + srcstrar = (char**)srca->data; + for(j = 0; j < dsta->numElements; j++){ + len = (int32)strlen(srcstrar[j])+1; + dststrar[j] = (char*)udMalloc(len); + strncpy(dststrar[j], srcstrar[j], len); + } + break; + } + srca++; + dsta++; + } + return dst; +} + +static Stream* +readUserData(Stream *stream, int32, void *object, int32 offset, int32) +{ + int32 i, j; + char **strar; + UserDataArray *a; + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + ext->numArrays = stream->readI32(); + ext->arrays = (UserDataArray*)udMalloc(ext->numArrays*sizeof(UserDataArray)); + a = ext->arrays; + for(i = 0; i < ext->numArrays; i++){ + int32 len = stream->readI32(); + a->name = (char*)udMalloc(len); + stream->read8(a->name, len); + a->datatype = stream->readU32(); + a->numElements = stream->readI32(); + switch(a->datatype){ + case USERDATAINT: + a->data = (int32*)udMalloc(sizeof(int32)*a->numElements); + stream->read32(a->data, sizeof(int32)*a->numElements); + break; + case USERDATAFLOAT: + a->data = (float32*)udMalloc(sizeof(float32)*a->numElements); + stream->read32(a->data, sizeof(float32)*a->numElements); + break; + case USERDATASTRING: + strar = (char**)udMalloc(sizeof(char*)*a->numElements); + a->data = strar; + for(j = 0; j < a->numElements; j++){ + len = stream->readI32(); + strar[j] = (char*)udMalloc(len); + stream->read8(strar[j], len); + } + break; + } + a++; + } + return stream; +} + +static Stream* +writeUserData(Stream *stream, int32, void *object, int32 offset, int32) +{ + int32 len; + int32 i, j; + char **strar; + UserDataArray *a; + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + stream->writeI32(ext->numArrays); + a = ext->arrays; + for(i = 0; i < ext->numArrays; i++){ + len = (int32)strlen(a->name)+1; + stream->writeI32(len); + stream->write8(a->name, len); + stream->writeU32(a->datatype); + stream->writeI32(a->numElements); + switch(a->datatype){ + case USERDATAINT: + stream->write32(a->data, sizeof(int32)*a->numElements); + break; + case USERDATAFLOAT: + stream->write32(a->data, sizeof(float32)*a->numElements); + break; + case USERDATASTRING: + strar = (char**)a->data; + for(j = 0; j < a->numElements; j++){ + len = (int32)strlen(strar[j])+1; + stream->writeI32(len); + stream->write8(strar[j], len); + } + break; + } + a++; + } + return stream; +} + +static int32 +getSizeUserData(void *object, int32 offset, int32) +{ + int32 len; + int32 i, j; + char **strar; + int32 size; + UserDataArray *a; + UserDataExtension *ext = PLUGINOFFSET(UserDataExtension, object, offset); + if(ext->numArrays == 0) + return 0; + size = 4; // numArrays + a = ext->arrays; + for(i = 0; i < ext->numArrays; i++){ + len = (int32)strlen(a->name)+1; + size += 4 + len + 4 + 4; // name len, name, type, numElements + switch(a->datatype){ + case USERDATAINT: + size += sizeof(int32)*a->numElements; + break; + case USERDATAFLOAT: + size += sizeof(float32)*a->numElements; + break; + case USERDATASTRING: + strar = (char**)a->data; + for(j = 0; j < a->numElements; j++){ + len = (int32)strlen(strar[j])+1; + size += 4 + len; // len and string + } + break; + } + a++; + } + return size; +} + +int32 +UserDataExtension::add(const char *name, int32 datatype, int32 numElements) +{ + int32 i; + int32 len; + int32 typesz; + UserDataArray *a; + // try to find empty slot + for(i = 0; i < this->numArrays; i++) + if(this->arrays[i].datatype == USERDATANA) + goto alloc; + // have to realloc + a = (UserDataArray*)udMalloc((this->numArrays+1)*sizeof(UserDataArray)); + if(a == nil) + return -1; + memcpy(a, this->arrays, this->numArrays*sizeof(UserDataArray)); + rwFree(this->arrays); + this->arrays = a; + i = this->numArrays++; +alloc: + a = &this->arrays[i]; + len = (int32)strlen(name)+1; + a->name = (char*)udMalloc(len+1); + assert(a->name); + strncpy(a->name, name, len); + a->datatype = datatype; + a->numElements = numElements; + typesz = datatype == USERDATAINT ? sizeof(int32) : + datatype == USERDATAFLOAT ? sizeof(float32) : + datatype == USERDATASTRING ? sizeof(char*) : 0; + a->data = udMalloc(typesz*numElements); + assert(a->data); + memset(a->data, 0, typesz*numElements); + return i; +} + +void +UserDataExtension::remove(int32 n) +{ + int32 i; + UserDataArray *a = &this->arrays[n]; + if(a->name){ + rwFree(a->name); + a->name = nil; + } + if(a->datatype == USERDATASTRING) + for(i = 0; i < a->numElements; i++) + rwFree(((char**)a->data)[i]); + if(a->data){ + rwFree(a->data); + a->data = nil; + } + a->datatype = USERDATANA; + a->numElements = 0; +} + +int32 +UserDataExtension::findIndex(const char *name) { + for(int32 i = 0; i < this->numArrays; i++) + if(strcmp(this->arrays[i].name, name) == 0) + return i; + return -1; +} + +#define ACCESSOR(TYPE, NAME) \ +int32 \ +UserDataArray::NAME##Add(TYPE *t, const char *name, int32 datatype, int32 numElements) \ +{ \ + return PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->add(name, datatype, numElements); \ +} \ +void \ +UserDataArray::NAME##Remove(TYPE *t, int32 n) \ +{ \ + PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->remove(n); \ +} \ +int32 \ +UserDataArray::NAME##GetCount(TYPE *t) \ +{ \ + return PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->getCount(); \ +} \ +UserDataArray* \ +UserDataArray::NAME##Get(TYPE *t, int32 n) \ +{ \ + return PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->get(n); \ +} \ +int32 \ +UserDataArray::NAME##FindIndex(TYPE *t, const char *name) \ +{ \ + return PLUGINOFFSET(UserDataExtension, t, userDataGlobals.NAME##Offset)->findIndex(name); \ +} + +ACCESSOR(Geometry, geometry) +ACCESSOR(Frame, frame) +ACCESSOR(Camera, camera) +ACCESSOR(Light, light) +ACCESSOR(Material, material) +ACCESSOR(Texture, texture) + +UserDataExtension *UserDataExtension::get(Geometry *geo) { return PLUGINOFFSET(UserDataExtension, geo, userDataGlobals.geometryOffset); } +UserDataExtension *UserDataExtension::get(Frame *frame) { return PLUGINOFFSET(UserDataExtension, frame, userDataGlobals.frameOffset); } +UserDataExtension *UserDataExtension::get(Camera *cam) { return PLUGINOFFSET(UserDataExtension, cam, userDataGlobals.cameraOffset); } +UserDataExtension *UserDataExtension::get(Light *light) { return PLUGINOFFSET(UserDataExtension, light, userDataGlobals.lightOffset); } +UserDataExtension *UserDataExtension::get(Material *mat) { return PLUGINOFFSET(UserDataExtension, mat, userDataGlobals.materialOffset); } +UserDataExtension *UserDataExtension::get(Texture *tex) { return PLUGINOFFSET(UserDataExtension, tex, userDataGlobals.textureOffset); } + +void +registerUserDataPlugin(void) +{ + // TODO: World Sector + + userDataGlobals.geometryOffset = Geometry::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Geometry::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.frameOffset = Frame::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Frame::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.cameraOffset = Camera::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Camera::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.lightOffset = Light::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Light::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.materialOffset = Material::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Material::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); + + userDataGlobals.textureOffset = Texture::registerPlugin(sizeof(UserDataExtension), + ID_USERDATA, createUserData, destroyUserData, copyUserData); + Texture::registerPluginStream(ID_USERDATA, readUserData, writeUserData, getSizeUserData); +} + +} diff --git a/src/uvanim.cpp b/src/uvanim.cpp new file mode 100644 index 0000000..f197b28 --- /dev/null +++ b/src/uvanim.cpp @@ -0,0 +1,502 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" +#include "rwanim.h" +#include "rwplugins.h" + +#define PLUGIN_ID ID_UVANIMATION + +namespace rw { + +// +// UVAnim +// + +void +UVAnimCustomData::destroy(Animation *anim) +{ + this->refCount--; + if(this->refCount <= 0) + anim->destroy(); +} + +UVAnimDictionary *currentUVAnimDictionary; + +UVAnimDictionary* +UVAnimDictionary::create(void) +{ + UVAnimDictionary *dict = (UVAnimDictionary*)rwMalloc(sizeof(UVAnimDictionary), MEMDUR_EVENT | ID_UVANIMATION); + if(dict == nil){ + RWERROR((ERR_ALLOC, sizeof(UVAnimDictionary))); + return nil; + } + dict->animations.init(); + return dict; +} + +void +UVAnimDictionary::destroy(void) +{ + FORLIST(lnk, this->animations){ + UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk); + UVAnimCustomData *cust = UVAnimCustomData::get(de->anim); + cust->destroy(de->anim); + rwFree(de); + } + rwFree(this); +} + +void +UVAnimDictionary::add(Animation *anim) +{ + UVAnimDictEntry *de = rwNewT(UVAnimDictEntry, 1, MEMDUR_EVENT | ID_UVANIMDICT); + de->anim = anim; + this->animations.append(&de->inDict); +} + +UVAnimDictionary* +UVAnimDictionary::streamRead(Stream *stream) +{ + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + UVAnimDictionary *dict = UVAnimDictionary::create(); + if(dict == nil) + return nil; + int32 numAnims = stream->readI32(); + Animation *anim; + for(int32 i = 0; i < numAnims; i++){ + if(!findChunk(stream, ID_ANIMANIMATION, nil, nil)){ + RWERROR((ERR_CHUNK, "ANIMANIMATION")); + goto fail; + } + anim = Animation::streamRead(stream); + if(anim == nil) + goto fail; + dict->add(anim); + } + return dict; +fail: + dict->destroy(); + return nil; +} + +bool +UVAnimDictionary::streamWrite(Stream *stream) +{ + uint32 size = this->streamGetSize(); + writeChunkHeader(stream, ID_UVANIMDICT, size); + writeChunkHeader(stream, ID_STRUCT, 4); + int32 numAnims = this->count(); + stream->writeI32(numAnims); + FORLIST(lnk, this->animations){ + UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk); + de->anim->streamWrite(stream); + } + return true; +} + +uint32 +UVAnimDictionary::streamGetSize(void) +{ + uint32 size = 12 + 4; + FORLIST(lnk, this->animations){ + UVAnimDictEntry *de = UVAnimDictEntry::fromDict(lnk); + size += 12 + de->anim->streamGetSize(); + } + return size; +} + +Animation* +UVAnimDictionary::find(const char *name) +{ + FORLIST(lnk, this->animations){ + Animation *anim = UVAnimDictEntry::fromDict(lnk)->anim; + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + if(strncmp_ci(custom->name, name, 32) == 0) + return anim; + } + return nil; +} + +static void +uvAnimStreamRead(Stream *stream, Animation *anim) +{ + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes; + stream->readI32(); + stream->read8(custom->name, 32); + stream->read32(custom->nodeToUVChannel, 8*4); + custom->refCount = 1; + + for(int32 i = 0; i < anim->numFrames; i++){ + frames[i].time = stream->readF32(); + stream->read32(frames[i].uv, 6*4); + int32 prev = stream->readI32(); + frames[i].prev = &frames[prev]; + } +} + +static void +uvAnimStreamWrite(Stream *stream, Animation *anim) +{ + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes; + stream->writeI32(0); + stream->write8(custom->name, 32); + stream->write32(custom->nodeToUVChannel, 8*4); + + for(int32 i = 0; i < anim->numFrames; i++){ + stream->writeF32(frames[i].time); + stream->write32(frames[i].uv, 6*4); + stream->writeI32(frames[i].prev - frames); + } +} + +static uint32 +uvAnimStreamGetSize(Animation *anim) +{ + return 4 + 32 + 8*4 + anim->numFrames*(4 + 6*4 + 4); +} + +static void +uvAnimLinearApplyCB(void *result, void *frame) +{ + Matrix *m = (Matrix*)result; + UVAnimInterpFrame *f = (UVAnimInterpFrame*)frame; + m->right.x = f->uv[0]; + m->right.y = f->uv[1]; + m->right.z = 0.0f; + m->up.x = f->uv[2]; + m->up.y = f->uv[3]; + m->up.z = 0.0f; + m->at.x = 0.0f; + m->at.y = 0.0f; + m->at.z = 0.0f; + m->pos.x = f->uv[4]; + m->pos.y = f->uv[5]; + m->pos.z = 0.0f; + m->update(); +} + +static void +uvAnimLinearInterpCB(void *out, void *in1, void *in2, float32 t, void *custom) +{ + UVAnimInterpFrame *intf = (UVAnimInterpFrame*)out; + UVAnimKeyFrame *kf1 = (UVAnimKeyFrame*)in1; + UVAnimKeyFrame *kf2 = (UVAnimKeyFrame*)in2; + float32 f = (t - kf1->time) / (kf2->time - kf1->time); + intf->uv[0] = (kf2->uv[0] - kf1->uv[0])*f + kf1->uv[0]; + intf->uv[1] = (kf2->uv[1] - kf1->uv[1])*f + kf1->uv[1]; + intf->uv[2] = (kf2->uv[2] - kf1->uv[2])*f + kf1->uv[2]; + intf->uv[3] = (kf2->uv[3] - kf1->uv[3])*f + kf1->uv[3]; + intf->uv[4] = (kf2->uv[4] - kf1->uv[4])*f + kf1->uv[4]; + intf->uv[5] = (kf2->uv[5] - kf1->uv[5])*f + kf1->uv[5]; +} + +static void +uvAnimParamApplyCB(void *result, void *frame) +{ + Matrix *m = (Matrix*)result; + UVAnimInterpFrame *f = (UVAnimInterpFrame*)frame; + UVAnimParamData *p = (UVAnimParamData*)f->uv; + + m->right.x = p->s0; + m->right.y = p->skew; + m->right.z = 0.0f; + m->up.x = 0.0f; + m->up.y = p->s1; + m->up.z = 0.0f; + m->at.x = 0.0f; + m->at.y = 0.0f; + m->at.z = 0.0f; + m->pos.x = p->x; + m->pos.y = p->y; + m->pos.z = 0.0f; + m->update(); + static V3d xlat1 = { -0.5f, -0.5f, 0.0f }; + static V3d xlat2 = { 0.5f, 0.5f, 0.0f }; + static V3d axis = { 0.0f, 0.0f, 1.0f }; + m->translate(&xlat1, COMBINEPOSTCONCAT); + m->rotate(&axis, p->theta*180.0f/(float)M_PI, COMBINEPOSTCONCAT); + m->translate(&xlat2, COMBINEPOSTCONCAT); +} + +static void +uvAnimParamInterpCB(void *out, void *in1, void *in2, float32 t, void *custom) +{ + UVAnimInterpFrame *intf = (UVAnimInterpFrame*)out; + UVAnimKeyFrame *kf1 = (UVAnimKeyFrame*)in1; + UVAnimKeyFrame *kf2 = (UVAnimKeyFrame*)in2; + float32 f = (t - kf1->time) / (kf2->time - kf1->time); + + float32 a = kf2->uv[0] - kf1->uv[0]; + while(a < (float)M_PI) a += 2 * (float)M_PI; + while(a > (float)M_PI) a -= 2 * (float)M_PI; + intf->uv[0] = a*f + kf1->uv[0]; + intf->uv[1] = (kf2->uv[1] - kf1->uv[1])*f + kf1->uv[1]; + intf->uv[2] = (kf2->uv[2] - kf1->uv[2])*f + kf1->uv[2]; + intf->uv[3] = (kf2->uv[3] - kf1->uv[3])*f + kf1->uv[3]; + intf->uv[4] = (kf2->uv[4] - kf1->uv[4])*f + kf1->uv[4]; + intf->uv[5] = (kf2->uv[5] - kf1->uv[5])*f + kf1->uv[5]; +} + + +static void +registerUVAnimInterpolator(void) +{ + // TODO: implement this fully + + // Linear + AnimInterpolatorInfo *info = rwNewT(AnimInterpolatorInfo, 1, MEMDUR_GLOBAL | ID_UVANIMATION); + info->id = 0x1C0; + info->interpKeyFrameSize = sizeof(UVAnimInterpFrame); + info->animKeyFrameSize = sizeof(UVAnimKeyFrame); + info->customDataSize = sizeof(UVAnimCustomData); + info->applyCB = uvAnimLinearApplyCB; + info->blendCB = nil; + info->interpCB = uvAnimLinearInterpCB; + info->addCB = nil; + info->mulRecipCB = nil; + info->streamRead = uvAnimStreamRead; + info->streamWrite = uvAnimStreamWrite; + info->streamGetSize = uvAnimStreamGetSize; + AnimInterpolatorInfo::registerInterp(info); + + // Param + info = rwNewT(AnimInterpolatorInfo, 1, MEMDUR_GLOBAL | ID_UVANIMATION); + info->id = 0x1C1; + info->interpKeyFrameSize = sizeof(UVAnimInterpFrame); + info->animKeyFrameSize = sizeof(UVAnimKeyFrame); + info->customDataSize = sizeof(UVAnimCustomData); + info->applyCB = uvAnimParamApplyCB; + info->blendCB = nil; + info->interpCB = uvAnimParamInterpCB; + info->addCB = nil; + info->mulRecipCB = nil; + info->streamRead = uvAnimStreamRead; + info->streamWrite = uvAnimStreamWrite; + info->streamGetSize = uvAnimStreamGetSize; + AnimInterpolatorInfo::registerInterp(info); +} + +int32 uvAnimOffset; + +static void* +createUVAnim(void *object, int32 offset, int32) +{ + UVAnim *uvanim; + uvanim = PLUGINOFFSET(UVAnim, object, offset); + memset(uvanim, 0, sizeof(*uvanim)); + return object; +} + +static void* +destroyUVAnim(void *object, int32 offset, int32) +{ + UVAnim *uvanim; + uvanim = PLUGINOFFSET(UVAnim, object, offset); + for(int32 i = 0; i < 8; i++){ + AnimInterpolator *ip = uvanim->interp[i]; + if(ip){ + UVAnimCustomData *custom = + UVAnimCustomData::get(ip->currentAnim); + custom->destroy(ip->currentAnim); + rwFree(ip); + } + } + return object; +} + +static void* +copyUVAnim(void *dst, void *src, int32 offset, int32) +{ + UVAnim *srcuvanim, *dstuvanim; + dstuvanim = PLUGINOFFSET(UVAnim, dst, offset); + srcuvanim = PLUGINOFFSET(UVAnim, src, offset); + for(int32 i = 0; i < 8; i++){ + AnimInterpolator *srcip = srcuvanim->interp[i]; + AnimInterpolator *dstip; + if(srcip){ + Animation *anim = srcip->currentAnim; + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + dstip = AnimInterpolator::create(anim->getNumNodes(), + anim->interpInfo->interpKeyFrameSize); + dstip->setCurrentAnim(anim); + custom->refCount++; + dstuvanim->interp[i] = dstip; + } + } + return dst; +} + +Animation* +makeDummyAnimation(const char *name) +{ + AnimInterpolatorInfo *interpInfo = AnimInterpolatorInfo::find(0x1C0); + Animation *anim = Animation::create(interpInfo, 2, 0, 1.0f); + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + strncpy(custom->name, name, 32); + memset(custom->nodeToUVChannel, 0, sizeof(custom->nodeToUVChannel)); + custom->refCount = 1; + UVAnimKeyFrame *frames = (UVAnimKeyFrame*)anim->keyframes; + frames[0].time = 0.0; + frames[0].prev = nil; + frames[1].time = 1.0; + frames[1].prev = &frames[0]; + return anim; +} + +static Stream* +readUVAnim(Stream *stream, int32, void *object, int32 offset, int32) +{ + UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset); + if(!findChunk(stream, ID_STRUCT, nil, nil)){ + RWERROR((ERR_CHUNK, "STRUCT")); + return nil; + } + char name[32]; + uint32 mask = stream->readI32(); + uint32 bit = 1; + for(int32 i = 0; i < 8; i++){ + if(mask & bit){ + stream->read8(name, 32); + Animation *anim = nil; + if(currentUVAnimDictionary) + anim = currentUVAnimDictionary->find(name); + if(anim == nil){ + anim = makeDummyAnimation(name); + if(currentUVAnimDictionary) + currentUVAnimDictionary->add(anim); + } + UVAnimCustomData *custom = UVAnimCustomData::get(anim); + AnimInterpolator *interp; + interp = AnimInterpolator::create(anim->getNumNodes(), + anim->interpInfo->interpKeyFrameSize); + interp->setCurrentAnim(anim); + custom->refCount++; + uvanim->interp[i] = interp; + } + bit <<= 1; + } + // TEMP + if(uvanim->uv[0] == nil) + uvanim->uv[0] = Matrix::create(); + if(uvanim->uv[1] == nil) + uvanim->uv[1] = Matrix::create(); + return stream; +} + +static Stream* +writeUVAnim(Stream *stream, int32 size, void *object, int32 offset, int32) +{ + UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset); + writeChunkHeader(stream, ID_STRUCT, size-12); + uint32 mask = 0; + uint32 bit = 1; + for(int32 i = 0; i < 8; i++){ + if(uvanim->interp[i]) + mask |= bit; + bit <<= 1; + } + stream->writeI32(mask); + for(int32 i = 0; i < 8; i++){ + if(uvanim->interp[i]){ + UVAnimCustomData *custom = + UVAnimCustomData::get(uvanim->interp[i]->currentAnim); + stream->write8(custom->name, 32); + } + } + return stream; +} + +static int32 +getSizeUVAnim(void *object, int32 offset, int32) +{ + UVAnim *uvanim = PLUGINOFFSET(UVAnim, object, offset); + int32 size = 0; + for(int32 i = 0; i < 8; i++) + if(uvanim->interp[i]) + size += 32; + return size ? size + 12 + 4 : 0; +} + +static void* +uvanimOpen(void *object, int32 offset, int32 size) +{ + registerUVAnimInterpolator(); + return object; +} +static void *uvanimClose(void *object, int32 offset, int32 size) { return object; } + +void +registerUVAnimPlugin(void) +{ + Engine::registerPlugin(0, ID_UVANIMATION, uvanimOpen, uvanimClose); + uvAnimOffset = Material::registerPlugin(sizeof(UVAnim), ID_UVANIMATION, + createUVAnim, destroyUVAnim, copyUVAnim); + Material::registerPluginStream(ID_UVANIMATION, + readUVAnim, writeUVAnim, getSizeUVAnim); +} + +bool32 +UVAnim::exists(Material *mat) +{ + int32 i; + UVAnim *uvanim = PLUGINOFFSET(UVAnim, mat, uvAnimOffset); + for(i = 0; i < 8; i++) + if(uvanim->interp[i]) + return 1; + return 0; +} + +void +UVAnim::addTime(Material *mat, float32 t) +{ + int32 i; + UVAnim *uvanim = PLUGINOFFSET(UVAnim, mat, uvAnimOffset); + for(i = 0; i < 8; i++) + if(uvanim->interp[i]) + uvanim->interp[i]->addTime(t); +} + +void +UVAnim::applyUpdate(Material *mat) +{ + int32 i, j; + int32 uv; + Matrix m; + UVAnim *uvanim = PLUGINOFFSET(UVAnim, mat, uvAnimOffset); + for(i = 0; i < 2; i++) + if(uvanim->uv[i]) + uvanim->uv[i]->setIdentity(); + m.setIdentity(); + + for(i = 0; i < 8; i++){ + AnimInterpolator *ip = uvanim->interp[i]; + if(ip == nil) + continue; + UVAnimCustomData *custom = UVAnimCustomData::get(ip->currentAnim); + for(j = 0; j < ip->numNodes; j++){ + InterpFrameHeader *f = ip->getInterpFrame(j); + uv = custom->nodeToUVChannel[j]; + if(uv < 2 && uvanim->uv[uv]){ + ip->applyCB(&m, f); + uvanim->uv[uv]->transform(&m, COMBINEPRECONCAT); + } + } + } + MatFX *mfx = MatFX::get(mat); + if(mfx) mfx->setUVTransformMatrices(uvanim->uv[0], uvanim->uv[1]); +} + +} diff --git a/src/vgafont.inc b/src/vgafont.inc new file mode 100644 index 0000000..110d824 --- /dev/null +++ b/src/vgafont.inc @@ -0,0 +1,256 @@ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, +1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/src/world.cpp b/src/world.cpp new file mode 100644 index 0000000..447eebc --- /dev/null +++ b/src/world.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +#include "rwbase.h" +#include "rwerror.h" +#include "rwplg.h" +#include "rwpipeline.h" +#include "rwobjects.h" +#include "rwengine.h" + +#define PLUGIN_ID ID_WORLD + +namespace rw { + +int32 World::numAllocated = 0; + +PluginList World::s_plglist(sizeof(World)); + +World* +World::create(void) +{ + World *world = (World*)rwMalloc(s_plglist.size, MEMDUR_EVENT | ID_WORLD); + if(world == nil){ + RWERROR((ERR_ALLOC, s_plglist.size)); + return nil; + } + numAllocated++; + world->object.init(World::ID, 0); + world->localLights.init(); + world->globalLights.init(); + world->clumps.init(); + s_plglist.construct(world); + return world; +} + +void +World::destroy(void) +{ + s_plglist.destruct(this); + rwFree(this); + numAllocated--; +} + +void +World::addLight(Light *light) +{ + assert(light->world == nil); + light->world = this; + if(light->getType() < Light::POINT){ + this->globalLights.append(&light->inWorld); + }else{ + this->localLights.append(&light->inWorld); + if(light->getFrame()) + light->getFrame()->updateObjects(); + } +} + +void +World::removeLight(Light *light) +{ + assert(light->world == this); + light->inWorld.remove(); + light->world = nil; +} + +void +World::addCamera(Camera *cam) +{ + assert(cam->world == nil); + cam->world = this; + if(cam->getFrame()) + cam->getFrame()->updateObjects(); +} + +void +World::removeCamera(Camera *cam) +{ + assert(cam->world == this); + cam->world = nil; +} + +void +World::addAtomic(Atomic *atomic) +{ + assert(atomic->world == nil); + atomic->world = this; + if(atomic->getFrame()) + atomic->getFrame()->updateObjects(); +} + +void +World::removeAtomic(Atomic *atomic) +{ + assert(atomic->world == this); + atomic->world = nil; +} + +void +World::addClump(Clump *clump) +{ + assert(clump->world == nil); + clump->world = this; + this->clumps.add(&clump->inWorld); + FORLIST(lnk, clump->atomics) + this->addAtomic(Atomic::fromClump(lnk)); + FORLIST(lnk, clump->lights) + this->addLight(Light::fromClump(lnk)); + FORLIST(lnk, clump->cameras) + this->addCamera(Camera::fromClump(lnk)); + + if(clump->getFrame()){ + clump->getFrame()->matrix.optimize(); + clump->getFrame()->updateObjects(); + } +} + +void +World::removeClump(Clump *clump) +{ + assert(clump->world == this); + clump->inWorld.remove(); + FORLIST(lnk, clump->atomics) + this->removeAtomic(Atomic::fromClump(lnk)); + FORLIST(lnk, clump->lights) + this->removeLight(Light::fromClump(lnk)); + FORLIST(lnk, clump->cameras) + this->removeCamera(Camera::fromClump(lnk)); + clump->world = nil; +} + +void +World::render(void) +{ + // this is very wrong, we really want world sectors + FORLIST(lnk, this->clumps) + Clump::fromWorld(lnk)->render(); +} + +// Find lights that illuminate an atomic +void +World::enumerateLights(Atomic *atomic, WorldLights *lightData) +{ + int32 maxDirectionals, maxLocals; + + maxDirectionals = lightData->numDirectionals; + maxLocals = lightData->numLocals; + + lightData->numDirectionals = 0; + lightData->numLocals = 0; + lightData->numAmbients = 0; + lightData->ambient.red = 0.0f; + lightData->ambient.green = 0.0f; + lightData->ambient.blue = 0.0f; + lightData->ambient.alpha = 1.0f; + + bool32 normals = atomic->geometry->flags & Geometry::NORMALS; + + FORLIST(lnk, this->globalLights){ + Light *l = Light::fromWorld(lnk); + if((l->getFlags() & Light::LIGHTATOMICS) == 0) + continue; + if(l->getType() == Light::AMBIENT){ + lightData->ambient.red += l->color.red; + lightData->ambient.green += l->color.green; + lightData->ambient.blue += l->color.blue; + lightData->numAmbients++; + }else if(normals && l->getType() == Light::DIRECTIONAL){ + if(lightData->numDirectionals < maxDirectionals) + lightData->directionals[lightData->numDirectionals++] = l; + } + } + + if(atomic->world != this) + return; + + if(!normals) + return; + + // TODO: for this we would use an atomic's world sectors, but we don't have those yet + FORLIST(lnk, this->localLights){ + if(lightData->numLocals >= maxLocals) + return; + + Light *l = Light::fromWorld(lnk); + if((l->getFlags() & Light::LIGHTATOMICS) == 0) + continue; + + // check if spheres are intersecting + Sphere *atomsphere = atomic->getWorldBoundingSphere(); + V3d dist = sub(l->getFrame()->getLTM()->pos, atomsphere->center); + if(length(dist) < atomsphere->radius + l->radius) + lightData->locals[lightData->numLocals++] = l; + } +} + +} diff --git a/tools/dumprwtree/dumprwtree.cpp b/tools/dumprwtree/dumprwtree.cpp new file mode 100644 index 0000000..ca86d3c --- /dev/null +++ b/tools/dumprwtree/dumprwtree.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include + +#include + +using namespace std; +using namespace rw; + +const char *chunks[] = { "None", "Struct", "String", "Extension", "Unknown", + "Camera", "Texture", "Material", "Material List", "Atomic Section", + "Plane Section", "World", "Spline", "Matrix", "Frame List", + "Geometry", "Clump", "Unknown", "Light", "Unicode String", "Atomic", + "Texture Native", "Texture Dictionary", "Animation Database", + "Image", "Skin Animation", "Geometry List", "Anim Animation", + "Team", "Crowd", "Delta Morph Animation", "Right To Render", + "MultiTexture Effect Native", "MultiTexture Effect Dictionary", + "Team Dictionary", "Platform Independet Texture Dictionary", + "Table of Contents", "Particle Standard Global Data", "AltPipe", + "Platform Independet Peds", "Patch Mesh", "Chunk Group Start", + "Chunk Group End", "UV Animation Dictionary", "Coll Tree" +}; + +/* From 0x0101 through 0x0135 */ +const char *toolkitchunks0[] = { "Metrics PLG", "Spline PLG", "Stereo PLG", + "VRML PLG", "Morph PLG", "PVS PLG", "Memory Leak PLG", "Animation PLG", + "Gloss PLG", "Logo PLG", "Memory Info PLG", "Random PLG", + "PNG Image PLG", "Bone PLG", "VRML Anim PLG", "Sky Mipmap Val", + "MRM PLG", "LOD Atomic PLG", "ME PLG", "Lightmap PLG", + "Refine PLG", "Skin PLG", "Label PLG", "Particles PLG", "GeomTX PLG", + "Synth Core PLG", "STQPP PLG", + "Part PP PLG", "Collision PLG", "HAnim PLG", "User Data PLG", + "Material Effects PLG", "Particle System PLG", "Delta Morph PLG", + "Patch PLG", "Team PLG", "Crowd PP PLG", "Mip Split PLG", + "Anisotrophy PLG", "Not used", "GCN Material PLG", "Geometric PVS PLG", + "XBOX Material PLG", "Multi Texture PLG", "Chain PLG", "Toon PLG", + "PTank PLG", "Particle Standard PLG", "PDS PLG", "PrtAdv PLG", + "Normal Map PLG", "ADC PLG", "UV Animation PLG" +}; + +/* From 0x0180 through 0x01c1 */ +const char *toolkitchunks1[] = { + "Character Set PLG", "NOHS World PLG", "Import Util PLG", + "Slerp PLG", "Optim PLG", "TL World PLG", "Database PLG", + "Raytrace PLG", "Ray PLG", "Library PLG", + "Not used", "Not used", "Not used", "Not used", "Not used", "Not used", + "2D PLG", "Tile Render PLG", "JPEG Image PLG", "TGA Image PLG", + "GIF Image PLG", "Quat PLG", "Spline PVS PLG", "Mipmap PLG", + "MipmapK PLG", "2D Font", "Intersection PLG", "TIFF Image PLG", + "Pick PLG", "BMP Image PLG", "RAS Image PLG", "Skin FX PLG", + "VCAT PLG", "2D Path", "2D Brush", "2D Object", "2D Shape", "2D Scene", + "2D Pick Region", "2D Object String", "2D Animation PLG", + "2D Animation", + "Not used", "Not used", "Not used", "Not used", "Not used", "Not used", + "2D Keyframe", "2D Maestro", "Barycentric", + "Platform Independent Texture Dictionary TK", "TOC TK", "TPL TK", + "AltPipe TK", "Animation TK", "Skin Split Tookit", "Compressed Key TK", + "Geometry Conditioning PLG", "Wing PLG", "Generic Pipeline TK", + "Lightmap Conversion TK", "Filesystem PLG", "Dictionary TK", + "UV Animation Linear", "UV Animation Parameter" +}; + +const char *RSchunks[] = { "Unused 1", "Unused 2", "Extra Normals", + "Pipeline Set", "Unused 5", "Unused 6", "Specular Material", + "Unused 8", "2dfx", "Extra Colors", "Collision Model", + "Unused 12", "Environment Material", "Breakable", "Node Name", + "Unused 16" +}; + +const char* +getChunkName(uint32 id) +{ + switch(id){ + case 0x50E: + return "Bin Mesh PLG"; + case 0x510: + return "Native Data PLG"; + case 0x511: + return "Vertex Format PLG"; + case 0xF21E: + return "ZModeler Lock"; + } + + if(id <= 45) + return chunks[id]; + else if(id <= 0x0253F2FF && id >= 0x0253F2F0) + return RSchunks[id-0x0253F2F0]; + else if(id <= 0x0135 && id >= 0x0101) + return toolkitchunks0[id-0x0101]; + else if(id <= 0x01C0 && id >= 0x0181) + return toolkitchunks1[id-0x0181]; + else + return "Unknown"; +} + +void +readchunk(StreamFile *s, ChunkHeaderInfo *h, int level) +{ + for(int i = 0; i < level; i++) + printf(" "); + const char *name = getChunkName(h->type); + printf("%s (%x bytes @ 0x%x/0x%x) - [0x%x]\n", + name, h->length, s->tell()-12, s->tell(), h->type); + + uint32 end = s->tell() + h->length; + while(s->tell() < end){ + ChunkHeaderInfo nh; + readChunkHeaderInfo(s, &nh); + if(nh.version == h->version && nh.build == h->build){ + readchunk(s, &nh, level+1); + if(h->type == 0x510) + s->seek(end, 0); + }else{ + s->seek(h->length-12); + break; + } + } +} + +int +main(int argc, char *argv[]) +{ + if(argc < 2){ + fprintf(stderr, "usage: %s rwStreamFile\n", argv[0]); + return 0; + } + StreamFile s; + s.open(argv[1], "rb"); + + ChunkHeaderInfo header, last; + while(readChunkHeaderInfo(&s, &header)){ + if(header.type == 0) + break; + last = header; + if(argc == 2) + readchunk(&s, &header, 0); + } + + printf("%x %x %x\n", last.version, last.build, + libraryIDPack(last.version, last.build)); + + s.close(); + return 0; +} diff --git a/tools/imguitest/main.cpp b/tools/imguitest/main.cpp new file mode 100644 index 0000000..6806d2d --- /dev/null +++ b/tools/imguitest/main.cpp @@ -0,0 +1,173 @@ +#include +#include +#include + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +struct SceneGlobals { + rw::World *world; + rw::Camera *camera; +} Scene; +rw::EngineOpenParams engineOpenParams; + +void +Init(void) +{ + sk::globals.windowtitle = "ImGui test"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +bool +InitRW(void) +{ +// rw::platform = rw::PLATFORM_D3D8; + if(!sk::InitRW()) + return false; + + Scene.world = rw::World::create(); + + rw::Light *ambient = rw::Light::create(rw::Light::AMBIENT); + ambient->setColor(0.2f, 0.2f, 0.2f); + Scene.world->addLight(ambient); + + rw::V3d xaxis = { 1.0f, 0.0f, 0.0f }; + rw::Light *direct = rw::Light::create(rw::Light::DIRECTIONAL); + direct->setColor(0.8f, 0.8f, 0.8f); + direct->setFrame(rw::Frame::create()); + direct->getFrame()->rotate(&xaxis, 180.0f, rw::COMBINEREPLACE); + Scene.world->addLight(direct); + + Scene.camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + Scene.world->addCamera(Scene.camera); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + return true; +} + +void +Draw(float timeDelta) +{ + static bool show_demo_window = true; + static bool show_another_window = false; + static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + rw::RGBA clearcol = rw::makeRGBA(clear_color.x*255, clear_color.y*255, clear_color.z*255, clear_color.w*255); + Scene.camera->clear(&clearcol, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + Scene.camera->beginUpdate(); + + ImGui_ImplRW_NewFrame(timeDelta); + + // 1. Show a simple window. + // Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets automatically appears in a window called "Debug". + { + static float f = 0.0f; + ImGui::Text("Hello, world!"); // Some text (you can use a format string too) + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float as a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats as a color + if(ImGui::Button("Demo Window")) // Use buttons to toggle our bools. We could use Checkbox() as well. + show_demo_window ^= 1; + if(ImGui::Button("Another Window")) + show_another_window ^= 1; + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + } + + // 2. Show another simple window. In most cases you will use an explicit Begin/End pair to name the window. + if(show_another_window){ + ImGui::Begin("Another Window", &show_another_window); + ImGui::Text("Hello from another window!"); + ImGui::End(); + } + + // 3. Show the ImGui demo window. Most of the sample code is in ImGui::ShowDemoWindow(). + if(show_demo_window){ + ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver); // Normally user code doesn't need/want to call this because positions are saved in .ini file anyway. Here we just want to make the demo initial state a bit more friendly! + ImGui::ShowDemoWindow(&show_demo_window); + } + + + ImGui::EndFrame(); + ImGui::Render(); + + Scene.camera->endUpdate(); + Scene.camera->showRaster(0); +} + + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + + ImGuiEventHandler(e, param); + + switch(e){ + case INITIALIZE: + Init(); + return EVENTPROCESSED; + case RWINITIALIZE: + return ::InitRW() ? EVENTPROCESSED : EVENTERROR; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + // TODO: set aspect ratio + if(Scene.camera) + sk::CameraSize(Scene.camera, r); + break; + case IDLE: + Draw(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/tools/lights/checker.dff b/tools/lights/checker.dff new file mode 100644 index 0000000..f955e8c Binary files /dev/null and b/tools/lights/checker.dff differ diff --git a/tools/lights/main.cpp b/tools/lights/main.cpp new file mode 100644 index 0000000..5d2bab1 --- /dev/null +++ b/tools/lights/main.cpp @@ -0,0 +1,365 @@ +#include +#include +#include + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +struct SceneGlobals { + rw::World *world; + rw::Camera *camera; +} Scene; +rw::EngineOpenParams engineOpenParams; +float FOV = 70.0f; + +rw::V3d Xaxis = { 1.0f, 0.0, 0.0f }; +rw::V3d Yaxis = { 0.0f, 1.0, 0.0f }; +rw::V3d Zaxis = { 0.0f, 0.0, 1.0f }; + +rw::Light *BaseAmbientLight; +bool BaseAmbientLightOn; + +rw::Light *CurrentLight; +rw::Light *AmbientLight; +rw::Light *PointLight; +rw::Light *DirectLight; +rw::Light *SpotLight; +rw::Light *SpotSoftLight; + +float LightRadius = 100.0f; +float LightConeAngle = 45.0f; +rw::V3d LightPos = {0.0f, 0.0f, 75.0f}; + +void +Init(void) +{ + sk::globals.windowtitle = "Light test"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +rw::Light* +CreateBaseAmbientLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::AMBIENT); + assert(light); + light->setColor(0.5f, 0.5f, 0.5f); + return light; +} + +rw::Light* +CreateAmbientLight(void) +{ + return rw::Light::create(rw::Light::AMBIENT); +} + +rw::Light* +CreateDirectLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::DIRECTIONAL); + assert(light); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +rw::Light* +CreatePointLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::POINT); + assert(light); + light->radius = LightRadius; + rw::Frame *frame = rw::Frame::create(); + assert(frame); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEREPLACE); + light->setFrame(frame); + return light; +} + +rw::Light* +CreateSpotLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::SPOT); + assert(light); + light->radius = LightRadius; + light->setAngle(LightConeAngle/180.0f*M_PI); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +rw::Light* +CreateSpotSoftLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::SOFTSPOT); + assert(light); + light->radius = LightRadius; + light->setAngle(LightConeAngle/180.0f*M_PI); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +bool +CreateTestScene(rw::World *world) +{ + rw::Clump *clump; + rw::StreamFile in; + const char *filename = "checker.dff"; + if(in.open(filename, "rb") == NULL){ + printf("couldn't open file\n"); + return false; + } + if(!rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL)) + return false; + clump = rw::Clump::streamRead(&in); + in.close(); + if(clump == nil) + return false; + + rw::Clump *clone; + rw::Frame *clumpFrame; + rw::V3d pos; + float zOffset = 75.0f; + + // Bottom panel + clumpFrame = clump->getFrame(); + clumpFrame->rotate(&Xaxis, 90.0f, rw::COMBINEREPLACE); + + pos.x = 0.0f; + pos.y = -25.0f; + pos.z = zOffset; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // only need to add once + world->addClump(clump); + + // Top panel + clone = clump->clone(); + clumpFrame = clone->getFrame(); + clumpFrame->rotate(&Xaxis, -90.0f, rw::COMBINEREPLACE); + + pos.x = 0.0f; + pos.y = 25.0f; + pos.z = zOffset; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // Left panel + clone = clump->clone(); + clumpFrame = clone->getFrame(); + clumpFrame->rotate(&Xaxis, 0.0f, rw::COMBINEREPLACE); + clumpFrame->rotate(&Yaxis, 90.0f, rw::COMBINEPOSTCONCAT); + + pos.x = 25.0f; + pos.y = 0.0f; + pos.z = zOffset; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // Right panel + clone = clump->clone(); + clumpFrame = clone->getFrame(); + clumpFrame->rotate(&Xaxis, 0.0f, rw::COMBINEREPLACE); + clumpFrame->rotate(&Yaxis, -90.0f, rw::COMBINEPOSTCONCAT); + + pos.x = -25.0f; + pos.y = 0.0f; + pos.z = zOffset; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // Back panel + clone = clump->clone(); + clumpFrame = clone->getFrame(); + clumpFrame->rotate(&Xaxis, 0.0f, rw::COMBINEREPLACE); + + pos.x = 0.0f; + pos.y = 0.0f; + pos.z = zOffset + 25.0f; + clumpFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + return 1; +} + +bool +InitRW(void) +{ +// rw::platform = rw::PLATFORM_D3D8; + if(!sk::InitRW()) + return false; + + Scene.world = rw::World::create(); + + BaseAmbientLight = CreateBaseAmbientLight(); + AmbientLight = CreateAmbientLight(); + DirectLight = CreateDirectLight(); + PointLight = CreatePointLight(); + SpotLight = CreateSpotLight(); + SpotSoftLight = CreateSpotSoftLight(); + + Scene.camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + Scene.camera->setNearPlane(0.1f); + Scene.camera->setFarPlane(300.0f); + Scene.camera->setFOV(FOV, (float)sk::globals.width/sk::globals.height); + Scene.world->addCamera(Scene.camera); + + CreateTestScene(Scene.world); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + return true; +} + +void +SwitchToLight(rw::Light *light) +{ + if(CurrentLight) + Scene.world->removeLight(CurrentLight); + CurrentLight = light; + Scene.world->addLight(CurrentLight); +} + +void +Gui(void) +{ +// ImGui::ShowDemoWindow(&show_demo_window); + + static bool showLightWindow = true; + ImGui::Begin("Lights", &showLightWindow); + static int lightswitch = 0; + if(ImGui::RadioButton("Light Off", &lightswitch, 0)){ + if(CurrentLight) + Scene.world->removeLight(CurrentLight); + CurrentLight = nil; + } + if(ImGui::RadioButton("Ambient Light", &lightswitch, 1)){ + SwitchToLight(AmbientLight); + } + ImGui::SameLine(); + if(ImGui::RadioButton("Directional Light", &lightswitch, 2)){ + SwitchToLight(DirectLight); + } + ImGui::SameLine(); + if(ImGui::RadioButton("Point Light", &lightswitch, 3)){ + SwitchToLight(PointLight); + } + if(ImGui::RadioButton("Spot Light", &lightswitch, 4)){ + SwitchToLight(SpotLight); + } + ImGui::SameLine(); + if(ImGui::RadioButton("Soft Spot Light", &lightswitch, 5)){ + SwitchToLight(SpotSoftLight); + } + ImGui::End(); +} + +void +Draw(float timeDelta) +{ + static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + rw::RGBA clearcol = rw::makeRGBA(clear_color.x*255, clear_color.y*255, clear_color.z*255, clear_color.w*255); + Scene.camera->clear(&clearcol, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + Scene.camera->beginUpdate(); + + ImGui_ImplRW_NewFrame(timeDelta); + + Scene.world->render(); + + Gui(); + + ImGui::EndFrame(); + ImGui::Render(); + + Scene.camera->endUpdate(); + Scene.camera->showRaster(0); +} + + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + + ImGuiEventHandler(e, param); + + switch(e){ + case INITIALIZE: + Init(); + return EVENTPROCESSED; + case RWINITIALIZE: + return ::InitRW() ? EVENTPROCESSED : EVENTERROR; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + if(Scene.camera){ + sk::CameraSize(Scene.camera, r); + Scene.camera->setFOV(FOV, (float)sk::globals.width/sk::globals.height); + } + break; + case IDLE: + Draw(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/tools/playground/Bm437_IBM_BIOS.FON b/tools/playground/Bm437_IBM_BIOS.FON new file mode 100644 index 0000000..be2443f Binary files /dev/null and b/tools/playground/Bm437_IBM_BIOS.FON differ diff --git a/tools/playground/Bm437_IBM_BIOS.tga b/tools/playground/Bm437_IBM_BIOS.tga new file mode 100644 index 0000000..50034b4 Binary files /dev/null and b/tools/playground/Bm437_IBM_BIOS.tga differ diff --git a/tools/playground/Bm437_IBM_VGA8.FON b/tools/playground/Bm437_IBM_VGA8.FON new file mode 100644 index 0000000..6ffa09a Binary files /dev/null and b/tools/playground/Bm437_IBM_VGA8.FON differ diff --git a/tools/playground/Bm437_IBM_VGA8.tga b/tools/playground/Bm437_IBM_VGA8.tga new file mode 100644 index 0000000..521fb40 Binary files /dev/null and b/tools/playground/Bm437_IBM_VGA8.tga differ diff --git a/tools/playground/camera.cpp b/tools/playground/camera.cpp new file mode 100644 index 0000000..cf45f5f --- /dev/null +++ b/tools/playground/camera.cpp @@ -0,0 +1,134 @@ +#include +#include + +#include + +#define PI 3.14159265359f +#include "camera.h" + +using rw::Quat; +using rw::V3d; + +void +Camera::update(void) +{ + if(m_rwcam){ + m_rwcam->setNearPlane(m_near); + m_rwcam->setFarPlane(m_far); + m_rwcam->setFOV(m_fov, m_aspectRatio); + + rw::Frame *f = m_rwcam->getFrame(); + if(f){ + V3d forward = normalize(sub(m_target, m_position)); + V3d left = normalize(cross(m_up, forward)); + V3d nup = cross(forward, left); + f->matrix.right = left; // lol + f->matrix.up = nup; + f->matrix.at = forward; + f->matrix.pos = m_position; + f->matrix.optimize(); + f->updateObjects(); + } + } +} + +void +Camera::setTarget(V3d target) +{ + m_position = sub(m_position, sub(m_target, target)); + m_target = target; +} + +float +Camera::getHeading(void) +{ + V3d dir = sub(m_target, m_position); + float a = atan2(dir.y, dir.x)-PI/2.0f; + return m_localup.z < 0.0f ? a-PI : a; +} + +void +Camera::turn(float yaw, float pitch) +{ + V3d dir = sub(m_target, m_position); + Quat r = Quat::rotation(yaw, rw::makeV3d(0.0f, 0.0f, 1.0f)); + dir = rotate(dir, r); + m_localup = rotate(m_localup, r); + + V3d right = normalize(cross(dir, m_localup)); + r = Quat::rotation(pitch, right); + dir = rotate(dir, r); + m_localup = normalize(cross(right, dir)); + if(m_localup.z >= 0.0) m_up.z = 1.0; + else m_up.z = -1.0f; + + m_target = add(m_position, dir); +} + +void +Camera::orbit(float yaw, float pitch) +{ + V3d dir = sub(m_target, m_position); + Quat r = Quat::rotation(yaw, rw::makeV3d(0.0f, 0.0f, 1.0f)); + dir = rotate(dir, r); + m_localup = rotate(m_localup, r); + + V3d right = normalize(cross(dir, m_localup)); + r = Quat::rotation(-pitch, right); + dir = rotate(dir, r); + m_localup = normalize(cross(right, dir)); + if(m_localup.z >= 0.0) m_up.z = 1.0; + else m_up.z = -1.0f; + + m_position = sub(m_target, dir); +} + +void +Camera::dolly(float dist) +{ + V3d dir = setlength(sub(m_target, m_position), dist); + m_position = add(m_position, dir); + m_target = add(m_target, dir); +} + +void +Camera::zoom(float dist) +{ + V3d dir = sub(m_target, m_position); + float curdist = length(dir); + if(dist >= curdist) + dist = curdist-0.01f; + dir = setlength(dir, dist); + m_position = add(m_position, dir); +} + +void +Camera::pan(float x, float y) +{ + V3d dir = normalize(sub(m_target, m_position)); + V3d right = normalize(cross(dir, m_up)); + V3d localup = normalize(cross(right, dir)); + dir = add(scale(right, x), scale(localup, y)); + m_position = add(m_position, dir); + m_target = add(m_target, dir); +} + +float +Camera::distanceTo(V3d v) +{ + return length(sub(m_position, v)); +} + +Camera::Camera() +{ + m_position.set(0.0f, 6.0f, 0.0f); + m_target.set(0.0f, 0.0f, 0.0f); + m_up.set(0.0f, 0.0f, 1.0f); + m_localup = m_up; + m_fov = 70.0f; + m_aspectRatio = 1.0f; + m_near = 0.1f; + m_far = 100.0f; + m_rwcam = NULL; +} + diff --git a/tools/playground/camera.h b/tools/playground/camera.h new file mode 100644 index 0000000..8a0315d --- /dev/null +++ b/tools/playground/camera.h @@ -0,0 +1,26 @@ +class Camera +{ +public: + rw::Camera *m_rwcam; + rw::V3d m_position; + rw::V3d m_target; + rw::V3d m_up; + rw::V3d m_localup; + + float m_fov, m_aspectRatio; + float m_near, m_far; + + + void setTarget(rw::V3d target); + float getHeading(void); + + void turn(float yaw, float pitch); + void orbit(float yaw, float pitch); + void dolly(float dist); + void zoom(float dist); + void pan(float x, float y); + + void update(void); + float distanceTo(rw::V3d v); + Camera(void); +}; diff --git a/tools/playground/font.cpp b/tools/playground/font.cpp new file mode 100644 index 0000000..3ced5fb --- /dev/null +++ b/tools/playground/font.cpp @@ -0,0 +1,181 @@ +#include +#include + +using namespace rw; + +struct Font +{ + Texture *tex; + int32 glyphwidth, glyphheight; + int32 numglyphs; +}; +Font vga = { nil, 8, 16, 256 }; +Font bios = { nil, 8, 8, 256 }; +Font *curfont = &bios; + +#define NUMCHARS 100 +uint16 indices[NUMCHARS*6]; +RWDEVICE::Im2DVertex vertices[NUMCHARS*4]; +int32 curVert; +int32 curIndex; + +void +printScreen(const char *s, float32 x, float32 y) +{ + char c; + Camera *cam; + RWDEVICE::Im2DVertex *vert; + uint16 *ix; + curVert = 0; + curIndex = 0; + float32 u, v, du, dv; + float recipZ; + + cam = (Camera*)engine->currentCamera; + vert = &vertices[curVert]; + ix = &indices[curIndex]; + du = curfont->glyphwidth/(float32)curfont->tex->raster->width; + dv = curfont->glyphheight/(float32)curfont->tex->raster->height; + recipZ = 1.0f/cam->nearPlane; + while(c = *s){ + if(c >= curfont->numglyphs) + c = 0; + u = (c % 16)*curfont->glyphwidth / (float32)curfont->tex->raster->width; + v = (c / 16)*curfont->glyphheight / (float32)curfont->tex->raster->height; + + vert->setScreenX(x); + vert->setScreenY(y); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u, recipZ); + vert->setV(v, recipZ); + vert++; + + vert->setScreenX(x+curfont->glyphwidth); + vert->setScreenY(y); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u+du, recipZ); + vert->setV(v, recipZ); + vert++; + + vert->setScreenX(x); + vert->setScreenY(y+curfont->glyphheight); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u, recipZ); + vert->setV(v+dv, recipZ); + vert++; + + vert->setScreenX(x+curfont->glyphwidth); + vert->setScreenY(y+curfont->glyphheight); + vert->setScreenZ(rw::im2d::GetNearZ()); + vert->setCameraZ(cam->nearPlane); + vert->setRecipCameraZ(recipZ); + vert->setColor(255, 255, 255, 255); + vert->setU(u+du, recipZ); + vert->setV(v+dv, recipZ); + vert++; + + *ix++ = curVert; + *ix++ = curVert+1; + *ix++ = curVert+2; + *ix++ = curVert+2; + *ix++ = curVert+1; + *ix++ = curVert+3; + + curVert += 4; + curIndex += 6; + x += curfont->glyphwidth+1; + + s++; + } + + rw::SetRenderStatePtr(rw::TEXTURERASTER, curfont->tex->raster); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + + im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, + vertices, curVert, indices, curIndex); + +} + +void +initFont(void) +{ + vga.tex = Texture::read("Bm437_IBM_VGA8", ""); + bios.tex = Texture::read("Bm437_IBM_BIOS", ""); + +/* + FILE *foo = fopen("font.c", "w"); + assert(foo); + int x, y; + rw::Image *img = rw::readTGA("vga_font.tga"); + assert(img); + for(y = 0; y < img->height; y++){ + for(x = 0; x < img->width; x++) + fprintf(foo, "%d, ", !!img->pixels[y*img->width + x]); + fprintf(foo, "\n"); + } +*/ +} + +/* +#define NUMGLYPHS 256 +#define GLYPHWIDTH 8 +#define GLYPHHEIGHT 16 + + +void +convertFont(void) +{ + FILE *f; + Image *img; + uint8 data[NUMGLYPHS*GLYPHHEIGHT]; + int32 i, x, y; + uint8 *px, *line, *glyph; +// f = fopen("font0.bin", "rb"); + f = fopen("Bm437_IBM_VGA8.FON", "rb"); +// f = fopen("Bm437_IBM_BIOS.FON", "rb"); + if(f == nil) + return; +fseek(f, 0x65A, 0); + fread(data, 1, NUMGLYPHS*GLYPHHEIGHT, f); + fclose(f); + + img = Image::create(16*GLYPHWIDTH, NUMGLYPHS/16*GLYPHHEIGHT, 32); + img->allocate(); + for(i = 0; i < NUMGLYPHS; i++){ + glyph = &data[i*GLYPHHEIGHT]; + x = (i % 16)*GLYPHWIDTH; + y = (i / 16)*GLYPHHEIGHT; + line = &img->pixels[x*4 + y*img->stride]; + for(y = 0; y < GLYPHHEIGHT; y++){ + px = line; + for(x = 0; x < 8; x++){ + if(*glyph & 1<<(8-x)){ + *px++ = 255; + *px++ = 255; + *px++ = 255; + *px++ = 255; + }else{ + *px++ = 0; + *px++ = 0; + *px++ = 0; + *px++ = 0; + } + } + glyph++; + line += img->stride; + } + } +// writeTGA(img, "Bm437_IBM_BIOS.tga"); + writeTGA(img, "Bm437_IBM_VGA8.tga"); +} +*/ \ No newline at end of file diff --git a/tools/playground/foobar.tga b/tools/playground/foobar.tga new file mode 100644 index 0000000..895bae0 Binary files /dev/null and b/tools/playground/foobar.tga differ diff --git a/tools/playground/main.cpp b/tools/playground/main.cpp new file mode 100644 index 0000000..ada15a9 --- /dev/null +++ b/tools/playground/main.cpp @@ -0,0 +1,588 @@ +#include +#include +#include "camera.h" +#include + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +Camera *camera; +struct SceneGlobals { + rw::World *world; + rw::Camera *camera; + rw::Clump *clump; +} Scene; +rw::Texture *tex, *tex2; +rw::Raster *testras; +rw::EngineOpenParams engineOpenParams; + +rw::Texture *frontbuffer; + +bool dosoftras = 0; + +namespace gen { +void tlTest(rw::Clump *clump); +} +void genIm3DTransform(void *vertices, rw::int32 numVertices, rw::Matrix *xform); +void genIm3DRenderIndexed(rw::PrimitiveType prim, void *indices, rw::int32 numIndices); +void genIm3DEnd(void); +void initFont(void); +void printScreen(const char *s, float x, float y); + +void initsplines(void); +void rendersplines(void); + +rw::Charset *testfont; + +//#include + +void +Init(void) +{ +// AllocConsole(); +// freopen("CONIN$", "r", stdin); +// freopen("CONOUT$", "w", stdout); +// freopen("CONOUT$", "w", stderr); + + sk::globals.windowtitle = "Clump viewer"; + sk::globals.width = 640; + sk::globals.height = 448; + sk::globals.quit = 0; +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +void +dumpUserData(rw::UserDataArray *ar) +{ + int i; + printf("name: %s\n", ar->name); + for(i = 0; i < ar->numElements; i++){ + switch(ar->datatype){ + case rw::USERDATAINT: + printf(" %d\n", ar->getInt(i)); + break; + case rw::USERDATAFLOAT: + printf(" %f\n", ar->getFloat(i)); + break; + case rw::USERDATASTRING: + printf(" %s\n", ar->getString(i)); + break; + } + } +} + +static rw::Frame* +dumpFrameUserDataCB(rw::Frame *f, void*) +{ + using namespace rw; + int32 i; + UserDataArray *ar; + int32 n = UserDataArray::frameGetCount(f); + for(i = 0; i < n; i++){ + ar = UserDataArray::frameGet(f, i); + dumpUserData(ar); + } + f->forAllChildren(dumpFrameUserDataCB, nil); + return f; +} + +void +dumpUserData(rw::Clump *clump) +{ + printf("Frames\n"); + dumpFrameUserDataCB(clump->getFrame(), nil); +} + +static rw::Frame* +getHierCB(rw::Frame *f, void *data) +{ + using namespace rw; + HAnimData *hd = rw::HAnimData::get(f); + if(hd->hierarchy){ + *(HAnimHierarchy**)data = hd->hierarchy; + return nil; + } + f->forAllChildren(getHierCB, data); + return f; +} + +rw::HAnimHierarchy* +getHAnimHierarchyFromClump(rw::Clump *clump) +{ + using namespace rw; + HAnimHierarchy *hier = nil; + getHierCB(clump->getFrame(), &hier); + return hier; +} + +void +setupAtomic(rw::Atomic *atomic) +{ + using namespace rw; + // just remove pipelines that we can't handle for now +// if(atomic->pipeline && atomic->pipeline->platform != rw::platform) + atomic->pipeline = NULL; + + // Attach hierarchy to atomic if we're skinned + HAnimHierarchy *hier = getHAnimHierarchyFromClump(atomic->clump); + if(hier) + Skin::setHierarchy(atomic, hier); +} + +static void +initHierFromFrames(rw::HAnimHierarchy *hier) +{ + using namespace rw; + int32 i; + for(i = 0; i < hier->numNodes; i++){ + if(hier->nodeInfo[i].frame){ + hier->matrices[hier->nodeInfo[i].index] = *hier->nodeInfo[i].frame->getLTM(); + }else + assert(0); + } +} + +void +setupClump(rw::Clump *clump) +{ + using namespace rw; + HAnimHierarchy *hier = getHAnimHierarchyFromClump(clump); + if(hier){ + hier->attach(); + initHierFromFrames(hier); + } + + FORLIST(lnk, clump->atomics){ + rw::Atomic *a = rw::Atomic::fromClump(lnk); + setupAtomic(a); + } +} + +#define MUL(x, y) ((x)*(y)/255) + +int +calcVCfx(int fb, int col, int a, int iter) +{ + int prev = fb; + int col2 = col*2; + if(col2 > 255) col2 = 255; + for(int i = 0; i < iter; i++){ + int tmp = MUL(fb, 255-a) + MUL(MUL(prev, col2), a); + tmp += MUL(prev, col); + tmp += MUL(prev, col); + prev = tmp > 255 ? 255 : tmp; + } + return prev; +} + +int +calcIIIfx(int fb, int col, int a, int iter) +{ + int prev = fb; + for(int i = 0; i < iter; i++){ + int tmp = MUL(fb, 255-a) + MUL(MUL(prev, col), a); + prev = tmp > 255 ? 255 : tmp; + } + return prev; +} + +void +postfxtest(void) +{ + rw::Image *img = rw::Image::create(256, 256, 32); + img->allocate(); + int x, y; + int iter; + static char filename[100]; + for(iter = 0; iter < 10; iter++){ + for(y = 0; y < 256; y++) + for(x = 0; x < 256; x++){ + int res = calcVCfx(y, x, 30, iter); +// int res = calcIIIfx(y, x, 30, iter); + if(0 && res == y){ + img->pixels[y*img->stride + x*img->bpp + 0] = 255; + img->pixels[y*img->stride + x*img->bpp + 1] = 0; + img->pixels[y*img->stride + x*img->bpp + 2] = 0; + }else{ + img->pixels[y*img->stride + x*img->bpp + 0] = res; + img->pixels[y*img->stride + x*img->bpp + 1] = res; + img->pixels[y*img->stride + x*img->bpp + 2] = res; + } + img->pixels[y*img->stride + x*img->bpp + 3] = 255; + } + sprintf(filename, "vcfx_%02d.bmp", iter); +// sprintf(filename, "iiifx_%02d.bmp", iter); + rw::writeBMP(img, filename); + } + exit(0); +} + +bool +InitRW(void) +{ +// rw::platform = rw::PLATFORM_D3D8; + if(!sk::InitRW()) + return false; + + rw::d3d::isP8supported = false; + + postfxtest(); + + initFont(); + + rw::RGBA foreground = { 255, 255, 0, 255 }; + rw::RGBA background = { 0, 0, 0, 0 }; + rw::Charset::open(); + testfont = rw::Charset::create(&foreground, &background); + assert(testfont); + foreground.blue = 255.0f; + testfont->setColors(&foreground, &background); + + tex = rw::Texture::read("maze", nil); + tex2 = rw::Texture::read("checkers", nil); + + const char *filename = "teapot2.dff"; + if(sk::args.argc > 1) + filename = sk::args.argv[1]; + rw::StreamFile in; + if(in.open(filename, "rb") == NULL){ + printf("couldn't open file\n"); + return false; + } + rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL); + Scene.clump = rw::Clump::streamRead(&in); + assert(Scene.clump); + in.close(); + + // TEST - Set texture to the all materials of the clump +// FORLIST(lnk, Scene.clump->atomics){ +// rw::Atomic *a = rw::Atomic::fromClump(lnk); +// for(int i = 0; i < a->geometry->matList.numMaterials; i++) +// a->geometry->matList.materials[i]->setTexture(tex); +// } + + Scene.clump->getFrame()->translate(&zero, rw::COMBINEREPLACE); + + dumpUserData(Scene.clump); + setupClump(Scene.clump); + + Scene.world = rw::World::create(); + + rw::Light *ambient = rw::Light::create(rw::Light::AMBIENT); + ambient->setColor(0.3f, 0.3f, 0.3f); + Scene.world->addLight(ambient); + + rw::V3d xaxis = { 1.0f, 0.0f, 0.0f }; + rw::Light *direct = rw::Light::create(rw::Light::DIRECTIONAL); + direct->setColor(0.8f, 0.8f, 0.8f); + direct->setFrame(rw::Frame::create()); + direct->getFrame()->rotate(&xaxis, 180.0f, rw::COMBINEREPLACE); + Scene.world->addLight(direct); + + camera = new Camera; + Scene.camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + camera->m_rwcam = Scene.camera; + camera->m_aspectRatio = 640.0f/448.0f; + camera->m_near = 0.5f; +// camera->m_far = 450.0f; + camera->m_far = 15.0f; + camera->m_target.set(0.0f, 0.0f, 0.0f); + camera->m_position.set(0.0f, -10.0f, 0.0f); +// camera->setPosition(Vec3(0.0f, 5.0f, 0.0f)); +// camera->setPosition(Vec3(0.0f, -70.0f, 0.0f)); +// camera->setPosition(Vec3(0.0f, -1.0f, 3.0f)); + camera->update(); + + Scene.world->addCamera(camera->m_rwcam); + + initsplines(); + + return true; +} + +void +im2dtest(void) +{ + using namespace rw::RWDEVICE; + int i; + static struct + { + float x, y; + rw::uint8 r, g, b, a; + float u, v; + } vs[4] = { + { 0.0f, 0.0f, 255, 0, 0, 128, 0.0f, 0.0f }, + { 640.0f, 0.0f, 0, 255, 0, 128, 1.0f, 0.0f }, + { 0.0f, 448.0f, 0, 0, 255, 128, 0.0f, 1.0f }, + { 640.0f, 448.0f, 0, 255, 255, 128, 1.0f, 1.0f }, +/* + { 0.0f, 0.0f, 255, 0, 0, 128, 0.0f, 1.0f }, + { 640.0f, 0.0f, 0, 255, 0, 128, 0.0f, 0.0f }, + { 0.0f, 448.0f, 0, 0, 255, 128, 1.0f, 1.0f }, + { 640.0f, 448.0f, 0, 255, 255, 128, 1.0f, 0.0f }, +*/ + }; + Im2DVertex verts[4]; + static short indices[] = { + 0, 1, 2, 3 + }; + + float recipZ = 1.0f/Scene.camera->nearPlane; + for(i = 0; i < 4; i++){ + verts[i].setScreenX(vs[i].x); + verts[i].setScreenY(vs[i].y); + verts[i].setScreenZ(rw::im2d::GetNearZ()); + verts[i].setCameraZ(Scene.camera->nearPlane); + verts[i].setRecipCameraZ(recipZ); + verts[i].setColor(vs[i].r, vs[i].g, vs[i].b, vs[i].a); + if(dosoftras) + verts[i].setColor(255, 255, 255, 255); + verts[i].setU(vs[i].u + 0.5f/640.0f, recipZ); + verts[i].setV(vs[i].v + 0.5f/448.0f, recipZ); + } + + rw::SetRenderStatePtr(rw::TEXTURERASTER, tex->raster); + if(dosoftras) + rw::SetRenderStatePtr(rw::TEXTURERASTER, testras); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::im2d::RenderIndexedPrimitive(rw::PRIMTYPETRISTRIP, + &verts, 4, &indices, 4); +} + +void +im3dtest(void) +{ + using namespace rw::RWDEVICE; + int i; + static struct + { + float x, y, z; + rw::uint8 r, g, b, a; + float u, v; + } vs[8] = { + { -1.0f, -1.0f, -1.0f, 255, 0, 0, 128, 0.0f, 0.0f }, + { -1.0f, 1.0f, -1.0f, 0, 255, 0, 128, 0.0f, 1.0f }, + { 1.0f, -1.0f, -1.0f, 0, 0, 255, 128, 1.0f, 0.0f }, + { 1.0f, 1.0f, -1.0f, 255, 0, 255, 128, 1.0f, 1.0f }, + + { -1.0f, -1.0f, 1.0f, 255, 0, 0, 128, 0.0f, 0.0f }, + { -1.0f, 1.0f, 1.0f, 0, 255, 0, 128, 0.0f, 1.0f }, + { 1.0f, -1.0f, 1.0f, 0, 0, 255, 128, 1.0f, 0.0f }, + { 1.0f, 1.0f, 1.0f, 255, 0, 255, 128, 1.0f, 1.0f }, + }; + Im3DVertex verts[8]; + static short indices[2*6] = { + 0, 1, 2, 2, 1, 3, + 4, 5, 6, 6, 5, 7 + }; + + for(i = 0; i < 8; i++){ + verts[i].setX(vs[i].x); + verts[i].setY(vs[i].y); + verts[i].setZ(vs[i].z); + verts[i].setColor(vs[i].r, vs[i].g, vs[i].b, vs[i].a); + verts[i].setU(vs[i].u); + verts[i].setV(vs[i].v); + } + + rw::SetRenderStatePtr(rw::TEXTURERASTER, tex->raster); +// rw::SetRenderStatePtr(rw::TEXTURERASTER, testfont->raster); +// rw::SetRenderStatePtr(rw::TEXTURERASTER, frontbuffer->raster); + rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); + rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + +/* + genIm3DTransform(verts, 8, nil); + genIm3DRenderIndexed(rw::PRIMTYPETRILIST, indices, 12); + genIm3DEnd(); +*/ + rw::im3d::Transform(verts, 8, nil, rw::im3d::EVERYTHING); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, 12); + rw::im3d::End(); +} + +void +getFrontBuffer(void) +{ + rw::Raster *fb = Scene.camera->frameBuffer; + + if(frontbuffer == nil || fb->width > frontbuffer->raster->width || fb->height > frontbuffer->raster->height){ + int w, h; + for(w = 1; w < fb->width; w <<= 1); + for(h = 1; h < fb->height; h <<= 1); + rw::Raster *ras = rw::Raster::create(w, h, fb->depth, rw::Raster::CAMERATEXTURE); + if(frontbuffer){ + frontbuffer->raster->destroy(); + frontbuffer->raster = ras; + }else + frontbuffer = rw::Texture::create(ras); + printf("created FB with %d %d %d\n", ras->width, ras->height, ras->depth); + } + + rw::Raster::pushContext(frontbuffer->raster); + fb->renderFast(0, 0); + rw::Raster::popContext(); +} + +void +Draw(float timeDelta) +{ + getFrontBuffer(); + + static rw::RGBA clearcol = { 161, 161, 161, 0xFF }; + camera->m_rwcam->clear(&clearcol, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + camera->update(); + camera->m_rwcam->beginUpdate(); + +extern void beginSoftras(void); + beginSoftras(); + +// gen::tlTest(Scene.clump); +void drawtest(void); +// drawtest(); + +extern void endSoftras(void); + if(dosoftras){ + endSoftras(); + } + // im2dtest(); + +// Scene.clump->render(); +// im3dtest(); +// printScreen("Hello, World!", 10, 10); + +// testfont->print("foo ABC", 200, 200, true); + + rendersplines(); + + camera->m_rwcam->endUpdate(); + + camera->m_rwcam->showRaster(0); +} + + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case 'W': + camera->orbit(0.0f, 0.1f); + break; + case 'S': + camera->orbit(0.0f, -0.1f); + break; + case 'A': + camera->orbit(-0.1f, 0.0f); + break; + case 'D': + camera->orbit(0.1f, 0.0f); + break; + case sk::KEY_UP: + camera->turn(0.0f, 0.1f); + break; + case sk::KEY_DOWN: + camera->turn(0.0f, -0.1f); + break; + case sk::KEY_LEFT: + camera->turn(0.1f, 0.0f); + break; + case sk::KEY_RIGHT: + camera->turn(-0.1f, 0.0f); + break; + case 'R': + camera->zoom(0.1f); + break; + case 'F': + camera->zoom(-0.1f); + break; + case 'V': + dosoftras = !dosoftras; + break; + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +void +MouseMove(int x, int y) +{ +} + +void +MouseButton(int buttons) +{ +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + MouseState *ms; + + switch(e){ + case INITIALIZE: + Init(); + return EVENTPROCESSED; + case RWINITIALIZE: + return ::InitRW() ? EVENTPROCESSED : EVENTERROR; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case MOUSEBTN: + ms = (MouseState*)param; + MouseButton(ms->buttons); + return EVENTPROCESSED; + case MOUSEMOVE: + ms = (MouseState*)param; + MouseMove(ms->posx, ms->posy); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + if(camera) + camera->m_aspectRatio = (float)r->w/r->h; + if(Scene.camera) + sk::CameraSize(Scene.camera, r); + break; + case IDLE: + Draw(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/tools/playground/maze.tga b/tools/playground/maze.tga new file mode 100644 index 0000000..4714c42 Binary files /dev/null and b/tools/playground/maze.tga differ diff --git a/tools/playground/ras_test.cpp b/tools/playground/ras_test.cpp new file mode 100644 index 0000000..185fdec --- /dev/null +++ b/tools/playground/ras_test.cpp @@ -0,0 +1,902 @@ +#include +#include + +namespace rs { + +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint16_t u16; +typedef int32_t i32; +typedef uint32_t u32; +typedef int64_t i64; +typedef uint64_t u64; + +typedef struct Canvas Canvas; +struct Canvas +{ + u8 *fb; + u32 *zbuf; + int w, h; +}; +extern Canvas *canvas; + +typedef struct Texture Texture; +struct Texture +{ + u8 *pixels; + int w, h; + int wrap; +}; + +typedef struct Point3 Point3; +struct Point3 +{ + int x, y, z; +}; + +typedef struct Color Color; +struct Color +{ + u8 r, g, b, a; +}; + +typedef struct Vertex Vertex; +struct Vertex +{ + i32 x, y, z; + float q; // 1/z + u8 r, g, b, a; + u8 f; // fog + float s, t; +}; + +Canvas *makecanvas(int w, int h); +Texture *maketexture(int w, int h); +void putpixel(Canvas *canvas, Point3 p, Color c); +void clearcanvas(Canvas *canvas); +void drawTriangle(Canvas *canvas, Vertex p1, Vertex p2, Vertex p3); + +// not good +void drawRect(Canvas *canvas, Point3 p1, Point3 p2, Color c); +void drawLine(Canvas *canvas, Point3 p1, Point3 p2, Color c); + +//#define trace(...) printf(__VA_ARGS__) +#define trace(...) + + +int clamp(int x); + + +/* + * Render States + */ +enum TextureWrap { + WRAP_REPEAT, + WRAP_CLAMP, + WRAP_BORDER, +}; + +enum TextureFunction { + TFUNC_MODULATE, + TFUNC_DECAL, + TFUNC_HIGHLIGHT, + TFUNC_HIGHLIGHT2, +}; + +enum AlphaTestFunc { + ALPHATEST_NEVER, + ALPHATEST_ALWAYS, + ALPHATEST_LESS, + ALPHATEST_LEQUAL, + ALPHATEST_EQUAL, + ALPHATEST_GEQUAL, + ALPHATEST_GREATER, + ALPHATEST_NOTEQUAL, +}; + +enum AlphaTestFail { + ALPHAFAIL_KEEP, + ALPHAFAIL_FB_ONLY, + ALPHAFAIL_ZB_ONLY, +}; + +enum DepthTestFunc { + DEPTHTEST_NEVER, + DEPTHTEST_ALWAYS, + DEPTHTEST_GEQUAL, + DEPTHTEST_GREATER, +}; + +// The blend equation is +// out = ((A - B) * C >> 7) + D +// A, B and D select the color, C the alpha value +enum AlphaBlendOp { + ALPHABLEND_SRC, + ALPHABLEND_DST, + ALPHABLEND_ZERO, + ALPHABLEND_FIX = ALPHABLEND_ZERO, +}; + +extern int srScissorX0, srScissorX1; +extern int srScissorY0, srScissorY1; +extern int srDepthTestEnable; +extern int srDepthTestFunction; +extern int srWriteZ; +extern int srAlphaTestEnable; +extern int srAlphaTestFunction; +extern int srAlphaTestReference; +extern int srAlphaTestFail; +extern int srAlphaBlendEnable; +extern int srAlphaBlendA; +extern int srAlphaBlendB; +extern int srAlphaBlendC; +extern int srAlphaBlendD; +extern int srAlphaBlendFix; +extern int srTexEnable; +extern Texture *srTexture; +extern int srWrapU; +extern int srWrapV; +extern Color srBorder; +extern int srTexUseAlpha; +extern int srTexFunc; +extern int srFogEnable; +extern Color srFogCol; + + + +// end header + + + + +#define CEIL(p) (((p)+15) >> 4) + +// render states +int srScissorX0, srScissorX1; +int srScissorY0, srScissorY1; +int srDepthTestEnable = 1; +int srDepthTestFunction = DEPTHTEST_GEQUAL; +int srWriteZ = 1; +int srAlphaTestEnable = 1; +int srAlphaTestFunction = ALPHATEST_ALWAYS; +int srAlphaTestReference; +int srAlphaTestFail = ALPHAFAIL_FB_ONLY; +int srAlphaBlendEnable = 1; +int srAlphaBlendA = ALPHABLEND_SRC; +int srAlphaBlendB = ALPHABLEND_DST; +int srAlphaBlendC = ALPHABLEND_SRC; +int srAlphaBlendD = ALPHABLEND_DST; +int srAlphaBlendFix = 0x80; +int srTexEnable = 0; +Texture *srTexture; +int srWrapU = WRAP_REPEAT; +int srWrapV = WRAP_REPEAT; +Color srBorder = { 255, 0, 0, 255 }; +int srTexUseAlpha = 1; +int srTexFunc = TFUNC_MODULATE; +int srFogEnable = 0; +Color srFogCol = { 0, 0, 0, 0 }; + +int clamp(int x) { if(x < 0) return 0; if(x > 255) return 255; return x; } + +Canvas* +makecanvas(int w, int h) +{ + Canvas *canv; + canv = (Canvas*)malloc(sizeof(*canv) + w*h*(4+4)); + canv->w = w; + canv->h = h; + canv->fb = ((u8*)canv + sizeof(*canv)); + canv->zbuf = (u32*)(canv->fb + w*h*4); + return canv; +} + +Texture* +maketexture(int w, int h) +{ + Texture *t; + t = (Texture*)malloc(sizeof(*t) + w*h*4); + t->w = w; + t->h = h; + t->pixels = (u8*)t + sizeof(*t); + t->wrap = 0x11; // wrap u and v + return t; +} + +void +clearcanvas(Canvas *canvas) +{ + memset(canvas->fb, 0, canvas->w*canvas->h*4); + memset(canvas->zbuf, 0, canvas->w*canvas->h*4); +} + +void +writefb(Canvas *canvas, int x, int y, Color c) +{ + u8 *px = &canvas->fb[(y*canvas->w + x)*4]; + u32 *z = &canvas->zbuf[y*canvas->w + x]; + + px[3] = c.r; + px[2] = c.g; + px[1] = c.b; + px[0] = c.a; +} + +void +putpixel(Canvas *canvas, Point3 p, Color c) +{ + // scissor test + if(p.x < srScissorX0 || p.x > srScissorX1 || + p.y < srScissorY0 || p.y > srScissorY1) + return; + + u8 *px = &canvas->fb[(p.y*canvas->w + p.x)*4]; + u32 *z = &canvas->zbuf[p.y*canvas->w + p.x]; + + int fbwrite = 1; + int zbwrite = srWriteZ; + + // alpha test + if(srAlphaTestEnable){ + int fail; + switch(srAlphaTestFunction){ + case ALPHATEST_NEVER: + fail = 1; + break; + case ALPHATEST_ALWAYS: + fail = 0; + break; + case ALPHATEST_LESS: + fail = c.a >= srAlphaTestReference; + break; + case ALPHATEST_LEQUAL: + fail = c.a > srAlphaTestReference; + break; + case ALPHATEST_EQUAL: + fail = c.a != srAlphaTestReference; + break; + case ALPHATEST_GEQUAL: + fail = c.a < srAlphaTestReference; + break; + case ALPHATEST_GREATER: + fail = c.a <= srAlphaTestReference; + break; + case ALPHATEST_NOTEQUAL: + fail = c.a == srAlphaTestReference; + break; + } + if(fail){ + switch(srAlphaTestFail){ + case ALPHAFAIL_KEEP: + return; + case ALPHAFAIL_FB_ONLY: + zbwrite = 0; + break; + case ALPHAFAIL_ZB_ONLY: + fbwrite = 0; + } + } + } + + // ztest + if(srDepthTestEnable){ + switch(srDepthTestFunction){ + case DEPTHTEST_NEVER: + return; + case DEPTHTEST_ALWAYS: + break; + case DEPTHTEST_GEQUAL: + if((u32)p.z < *z) + return; + break; + case DEPTHTEST_GREATER: + if((u32)p.z <= *z) + return; + break; + } + } + + Color d = { px[3], px[2], px[1], px[0] }; + + // blend + if(srAlphaBlendEnable){ + int ar, ag, ab; + int br, bg, bb; + int dr, dg, db; + int ca; + switch(srAlphaBlendA){ + case ALPHABLEND_SRC: + ar = c.r; + ag = c.g; + ab = c.b; + break; + case ALPHABLEND_DST: + ar = d.r; + ag = d.g; + ab = d.b; + break; + case ALPHABLEND_ZERO: + ar = 0; + ag = 0; + ab = 0; + break; + default: assert(0); + } + switch(srAlphaBlendB){ + case ALPHABLEND_SRC: + br = c.r; + bg = c.g; + bb = c.b; + break; + case ALPHABLEND_DST: + br = d.r; + bg = d.g; + bb = d.b; + break; + case ALPHABLEND_ZERO: + br = 0; + bg = 0; + bb = 0; + break; + default: assert(0); + } + switch(srAlphaBlendC){ + case ALPHABLEND_SRC: + ca = c.a; + break; + case ALPHABLEND_DST: + ca = d.a; + break; + case ALPHABLEND_FIX: + ca = srAlphaBlendFix; + break; + default: assert(0); + } + switch(srAlphaBlendD){ + case ALPHABLEND_SRC: + dr = c.r; + dg = c.g; + db = c.b; + break; + case ALPHABLEND_DST: + dr = d.r; + dg = d.g; + db = d.b; + break; + case ALPHABLEND_ZERO: + dr = 0; + dg = 0; + db = 0; + break; + default: assert(0); + } + + int r, g, b; + r = ((ar - br) * ca >> 7) + dr; + g = ((ag - bg) * ca >> 7) + dg; + b = ((ab - bb) * ca >> 7) + db; + + c.r = clamp(r); + c.g = clamp(g); + c.b = clamp(b); + } + + if(fbwrite) + writefb(canvas, p.x, p.y, c); + if(zbwrite) + *z = p.z; +} + +Color +sampletex_nearest(int u, int v) +{ + Texture *tex = srTexture; + + const int usize = tex->w; + const int vsize = tex->h; + + int iu = u >> 4; + int iv = v >> 4; + + switch(srWrapU){ + case WRAP_REPEAT: + iu %= usize; + break; + case WRAP_CLAMP: + if(iu < 0) iu = 0; + if(iu >= usize) iu = usize-1; + break; + case WRAP_BORDER: + if(iu < 0 || iu >= usize) + return srBorder; + } + + switch(srWrapV){ + case WRAP_REPEAT: + iv %= vsize; + break; + case WRAP_CLAMP: + if(iv < 0) iv = 0; + if(iv >= vsize) iv = vsize-1; + break; + case WRAP_BORDER: + if(iv < 0 || iv >= vsize) + return srBorder; + } + + u8 *cp = &tex->pixels[(iv*tex->w + iu)*4]; + Color c = { cp[0], cp[1], cp[2], cp[3] }; + return c; +} + +// t is texture, f is fragment +Color +texfunc(Color t, Color f) +{ + int r, g, b, a; + switch(srTexFunc){ + case TFUNC_MODULATE: + r = t.r * f.r >> 7; + g = t.g * f.g >> 7; + b = t.b * f.b >> 7; + a = srTexUseAlpha ? + t.a * f.a >> 7 : + f.a; + break; + case TFUNC_DECAL: + r = t.r; + g = t.g; + b = t.b; + a = srTexUseAlpha ? t.a : f.a; + break; + case TFUNC_HIGHLIGHT: + r = (t.r * f.r >> 7) + f.a; + g = (t.g * f.g >> 7) + f.a; + b = (t.b * f.b >> 7) + f.a; + a = srTexUseAlpha ? + t.a + f.a : + f.a; + break; + case TFUNC_HIGHLIGHT2: + r = (t.r * f.r >> 7) + f.a; + g = (t.g * f.g >> 7) + f.a; + b = (t.b * f.b >> 7) + f.a; + a = srTexUseAlpha ? t.a : f.a; + break; + } + Color v; + v.r = clamp(r); + v.g = clamp(g); + v.b = clamp(b); + v.a = clamp(a); + return v; +} + +Point3 mkpnt(int x, int y, int z) { Point3 p = { x, y, z}; return p; } + +void +drawRect(Canvas *canvas, Point3 p1, Point3 p2, Color c) +{ + int x, y; + for(y = p1.y; y <= p2.y; y++) + for(x = p1.x; x <= p2.x; x++) + putpixel(canvas, mkpnt(x, y, 0), c); +} + +void +drawLine(Canvas *canvas, Point3 p1, Point3 p2, Color c) +{ + int dx, dy; + int incx, incy; + int e; + int x, y; + + dx = abs(p2.x-p1.x); + incx = p2.x > p1.x ? 1 : -1; + dy = abs(p2.y-p1.y); + incy = p2.y > p1.y ? 1 : -1; + e = 0; + if(dx == 0){ + for(y = p1.y; y != p2.y; y += incy) + putpixel(canvas, mkpnt(p1.x, y, 0), c); + }else if(dx > dy){ + y = p1.y; + for(x = p1.x; x != p2.x; x += incx){ + putpixel(canvas, mkpnt(x, y, 0), c); + e += dy; + if(2*e >= dx){ + e -= dx; + y += incy; + } + } + }else{ + x = p1.x; + for(y = p1.y; y != p2.y; y += incy){ + putpixel(canvas, mkpnt(x, y, 0), c); + e += dx; + if(2*e >= dy){ + e -= dy; + x += incx; + } + } + } +} + +/* + attibutes we want to interpolate: + R G B A + U V / S T Q + X Y Z F +*/ + +struct TriAttribs +{ + i64 z; + i32 r, g, b, a; + i32 f; + float s, t; + float q; +}; + +static void +add1(struct TriAttribs *a, struct TriAttribs *b) +{ + a->z += b->z; + a->r += b->r; + a->g += b->g; + a->b += b->b; + a->a += b->a; + a->f += b->f; + a->s += b->s; + a->t += b->t; + a->q += b->q; +} + +static void +sub1(struct TriAttribs *a, struct TriAttribs *b) +{ + a->z -= b->z; + a->r -= b->r; + a->g -= b->g; + a->b -= b->b; + a->a -= b->a; + a->f -= b->f; + a->s -= b->s; + a->t -= b->t; + a->q -= b->q; +} + +static void +guard(struct TriAttribs *a) +{ + if(a->z < 0) a->z = 0; + else if(a->z > 0x3FFFFFFFC000LL) a->z = 0x3FFFFFFFC000LL; + if(a->r < 0) a->r = 0; + else if(a->r > 0xFF000) a->r = 0xFF000; + if(a->g < 0) a->g = 0; + else if(a->g > 0xFF000) a->g = 0xFF000; + if(a->b < 0) a->b = 0; + else if(a->b > 0xFF000) a->b = 0xFF000; + if(a->a < 0) a->a = 0; + else if(a->a > 0xFF000) a->a = 0xFF000; + if(a->f < 0) a->f = 0; + else if(a->f > 0xFF000) a->f = 0xFF000; +} + +struct RasTri +{ + int x, y; + int ymid, yend; + int right; + int e[2], dx[3], dy[3]; + struct TriAttribs gx, gy, v, s; +}; + +static int +triangleSetup(struct RasTri *tri, Vertex v1, Vertex v2, Vertex v3) +{ + int dx1, dx2, dx3; + int dy1, dy2, dy3; + + dy1 = v3.y - v1.y; // long edge + if(dy1 == 0) return 1; + dx1 = v3.x - v1.x; + dx2 = v2.x - v1.x; // first small edge + dy2 = v2.y - v1.y; + dx3 = v3.x - v2.x; // second small edge + dy3 = v3.y - v2.y; + + // this is twice the triangle area + const int area = dx2*dy1 - dx1*dy2; + if(area == 0) return 1; + // figure out if 0 or 1 is the right edge + tri->right = area < 0; + + /* The gradients are to step whole pixels, + * so they are pre-multiplied by 16. */ + + float denom = 16.0f/area; + // gradients x +#define GX(p) ((v2.p - v1.p)*dy1 - (v3.p - v1.p)*dy2) + tri->gx.z = GX(z)*denom * 16384; + tri->gx.r = GX(r)*denom * 4096; + tri->gx.g = GX(g)*denom * 4096; + tri->gx.b = GX(b)*denom * 4096; + tri->gx.a = GX(a)*denom * 4096; + tri->gx.f = GX(f)*denom * 4096; + tri->gx.s = GX(s)*denom; + tri->gx.t = GX(t)*denom; + tri->gx.q = GX(q)*denom; + + // gradients y + denom = -denom; +#define GY(p) ((v2.p - v1.p)*dx1 - (v3.p - v1.p)*dx2) + tri->gy.z = GY(z)*denom * 16384; + tri->gy.r = GY(r)*denom * 4096; + tri->gy.g = GY(g)*denom * 4096; + tri->gy.b = GY(b)*denom * 4096; + tri->gy.a = GY(a)*denom * 4096; + tri->gy.f = GY(f)*denom * 4096; + tri->gy.s = GY(s)*denom; + tri->gy.t = GY(t)*denom; + tri->gy.q = GY(q)*denom; + + tri->ymid = CEIL(v2.y); + tri->yend = CEIL(v3.y); + + tri->y = CEIL(v1.y); + tri->x = CEIL(v1.x); + + tri->dy[0] = dy2<<4; // upper edge + tri->dy[1] = dy1<<4; // lower edge + tri->dy[2] = dy3<<4; // long edge + tri->dx[0] = dx2<<4; + tri->dx[1] = dx1<<4; + tri->dx[2] = dx3<<4; + + // prestep to land on pixel center + + int stepx = v1.x - (tri->x<<4); + int stepy = v1.y - (tri->y<<4); + tri->e[0] = (-stepy*tri->dx[0] + stepx*tri->dy[0]) >> 4; + tri->e[1] = (-stepy*tri->dx[1] + stepx*tri->dy[1]) >> 4; + + // attributes along interpolated edge + // why is this cast needed? (mingw) + tri->v.z = (i64)v1.z*16384 - (stepy*tri->gy.z + stepx*tri->gx.z)/16; + tri->v.r = v1.r*4096 - (stepy*tri->gy.r + stepx*tri->gx.r)/16; + tri->v.g = v1.g*4096 - (stepy*tri->gy.g + stepx*tri->gx.g)/16; + tri->v.b = v1.b*4096 - (stepy*tri->gy.b + stepx*tri->gx.b)/16; + tri->v.a = v1.a*4096 - (stepy*tri->gy.a + stepx*tri->gx.a)/16; + tri->v.f = v1.f*4096 - (stepy*tri->gy.f + stepx*tri->gx.f)/16; + tri->v.s = v1.s - (stepy*tri->gy.s + stepx*tri->gx.s)/16.0f; + tri->v.t = v1.t - (stepy*tri->gy.t + stepx*tri->gx.t)/16.0f; + tri->v.q = v1.q - (stepy*tri->gy.q + stepx*tri->gx.q)/16.0f; + + return 0; +} + +void +drawTriangle(Canvas *canvas, Vertex v1, Vertex v2, Vertex v3) +{ + Color c; + struct RasTri tri; + int stepx, stepy; + + // Sort such that we have from top to bottom v1,v2,v3 + if(v2.y < v1.y){ Vertex tmp = v1; v1 = v2; v2 = tmp; } + if(v3.y < v1.y){ Vertex tmp = v1; v1 = v3; v3 = tmp; } + if(v3.y < v2.y){ Vertex tmp = v2; v2 = v3; v3 = tmp; } + + if(triangleSetup(&tri, v1, v2, v3)) + return; + + // Current scanline start and end + int xn[2] = { tri.x, tri.x }; + int a = !tri.right; // left edge + int b = tri.right; // right edge + + // If upper triangle has no height, only do the lower part + if(tri.dy[0] == 0) + goto secondtri; + while(tri.y < tri.yend){ + /* TODO: is this the righ way to step the edges? */ + + /* Step x and interpolated value down left edge */ + while(tri.e[a] <= -tri.dy[a]){ + xn[a]--; + tri.e[a] += tri.dy[a]; + sub1(&tri.v, &tri.gx); + } + while(tri.e[a] > 0){ + xn[a]++; + tri.e[a] -= tri.dy[a]; + add1(&tri.v, &tri.gx); + } + + /* Step x down right edge */ + while(tri.e[b] <= -tri.dy[b]){ + xn[b]--; + tri.e[b] += tri.dy[b]; + } + while(tri.e[b] > 0){ + xn[b]++; + tri.e[b] -= tri.dy[b]; + } + + // When we reach the mid vertex, change state and jump to start of loop again + // TODO: this is a bit ugly in here...can we fix it? + if(tri.y == tri.ymid){ + secondtri: + tri.dx[0] = tri.dx[2]; + tri.dy[0] = tri.dy[2]; + // Either the while prevents this or we returned early because dy1 == 0 + assert(tri.dy[0] != 0); + stepx = v2.x - (xn[0]<<4); + stepy = v2.y - (tri.y<<4); + tri.e[0] = (-stepy*tri.dx[0] + stepx*tri.dy[0]) >> 4; + + tri.ymid = -1; // so we don't do this again + continue; + } + + /* Rasterize one line */ + tri.s = tri.v; + for(tri.x = xn[a]; tri.x < xn[b]; tri.x++){ + guard(&tri.s); + c.r = tri.s.r >> 12; + c.g = tri.s.g >> 12; + c.b = tri.s.b >> 12; + c.a = tri.s.a >> 12; + if(srTexEnable && srTexture){ + float w = 1.0f/tri.s.q; + float s = tri.s.s * w; + float t = tri.s.t * w; + int u = s * srTexture->w * 16; + int v = t * srTexture->h * 16; + Color texc = sampletex_nearest(u, v); + c = texfunc(texc, c); + } + if(srFogEnable){ + const int f = tri.s.f >> 12; + c.r = (f*c.r >> 8) + ((255 - f)*srFogCol.r >> 8); + c.g = (f*c.g >> 8) + ((255 - f)*srFogCol.g >> 8); + c.b = (f*c.b >> 8) + ((255 - f)*srFogCol.b >> 8); + } + putpixel(canvas, mkpnt(tri.x, tri.y, tri.s.z>>14), c); + add1(&tri.s, &tri.gx); + } + + /* Step in y */ + tri.y++; + tri.e[a] += tri.dx[a]; + tri.e[b] += tri.dx[b]; + add1(&tri.v, &tri.gy); + } +} + +Canvas *canvas; + +} + +using namespace rw; + +void +rastest_renderTriangles(RWDEVICE::Im2DVertex *scrverts, int32 numVerts, uint16 *indices, int32 numTris) +{ + int i; + RGBA col; + rs::Vertex v[3]; + RWDEVICE::Im2DVertex *iv; + + rs::srDepthTestEnable = 1; + rs::srAlphaTestEnable = 0; + rs::srTexEnable = 0; + rs::srAlphaBlendEnable = 0; + + while(numTris--){ + for(i = 0; i < 3; i++){ + iv = &scrverts[indices[i]]; + v[i].x = iv->getScreenX() * 16.0f; + v[i].y = iv->getScreenY() * 16.0f; + v[i].z = 16777216*(1.0f-iv->getScreenZ()); + v[i].q = iv->getRecipCameraZ(); + col = iv->getColor(); + v[i].r = col.red; + v[i].g = col.green; + v[i].b = col.blue; + v[i].a = col.alpha; + v[i].f = 0; + v[i].s = iv->u*iv->getRecipCameraZ(); + v[i].t = iv->v*iv->getRecipCameraZ(); + } + drawTriangle(rs::canvas, v[0], v[1], v[2]); + + indices += 3; + } +} + +extern rw::Raster *testras; + +void +beginSoftras(void) +{ + Camera *cam = (Camera*)engine->currentCamera; + + if(rs::canvas == nil || + cam->frameBuffer->width != rs::canvas->w || + cam->frameBuffer->height != rs::canvas->h){ + rs::canvas = rs::makecanvas(cam->frameBuffer->width, cam->frameBuffer->height); + testras = rw::Raster::create(rs::canvas->w, rs::canvas->h, 32, rw::Raster::C8888); + } + + clearcanvas(rs::canvas); + rs::srScissorX0 = 0; + rs::srScissorX1 = rs::canvas->w-1; + rs::srScissorY0 = 0; + rs::srScissorY1 = rs::canvas->h-1; +} + +void +endSoftras(void) +{ + int i; + uint8 *dst = testras->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); + if(dst == nil) + return; + uint8 *src = rs::canvas->fb; + for(i = 0; i < rs::canvas->w*rs::canvas->h; i++){ + dst[0] = src[1]; + dst[1] = src[2]; + dst[2] = src[3]; + dst[3] = src[0]; + dst += 4; + src += 4; + } + // abgr in canvas + // bgra in raster + testras->unlock(0); +} + + +/* +typedef struct PixVert PixVert; +struct PixVert +{ + float x, y, z, q; + int r, g, b, a; + float u, v; +}; +#include "test.inc" + +void +drawtest(void) +{ + int i, j; + rs::Vertex v[3]; + + rs::srDepthTestEnable = 1; + rs::srAlphaTestEnable = 0; + rs::srTexEnable = 0; + rs::srAlphaBlendEnable = 0; + + for(i = 0; i < nelem(verts); i += 3){ + for(j = 0; j < 3; j++){ + v[j].x = verts[i+j].x * 16.0f; + v[j].y = verts[i+j].y * 16.0f; + v[j].z = 16777216*(1.0f - verts[i+j].z); + v[j].q = verts[i+j].q; + v[j].r = verts[i+j].r; + v[j].g = verts[i+j].g; + v[j].b = verts[i+j].b; + v[j].a = verts[i+j].a; + v[j].f = 0; + v[j].s = verts[i+j].u*v[j].q; + v[j].t = verts[i+j].v*v[j].q; + } + drawTriangle(rs::canvas, v[0], v[1], v[2]); + } +//exit(0); +} +*/ diff --git a/tools/playground/splines.cpp b/tools/playground/splines.cpp new file mode 100644 index 0000000..73d32eb --- /dev/null +++ b/tools/playground/splines.cpp @@ -0,0 +1,520 @@ +#include +#include + +#include + +using namespace rw; +using namespace RWDEVICE; + +static Im3DVertex im3dVerts[1024]; +static int numImVerts; +static rw::PrimitiveType imPrim; +static Im3DVertex imVert; + +void +BeginIm3D(rw::PrimitiveType prim) +{ + numImVerts = 0; + imPrim = prim; +} + +void +EndIm3D(void) +{ + rw::im3d::Transform(im3dVerts, numImVerts, nil, rw::im3d::EVERYTHING); + rw::im3d::RenderPrimitive(imPrim); + rw::im3d::End(); +} + +void +AddVertex(const rw::V3d &vert) +{ + if(numImVerts >= 1024){ + EndIm3D(); + switch(imPrim){ + case PRIMTYPEPOLYLINE: + im3dVerts[0] = im3dVerts[numImVerts-1]; + numImVerts = 1; + break; + case PRIMTYPETRISTRIP: + // TODO: winding? + im3dVerts[0] = im3dVerts[numImVerts-2]; + im3dVerts[1] = im3dVerts[numImVerts-1]; + numImVerts = 2; + break; + case PRIMTYPETRIFAN: + im3dVerts[1] = im3dVerts[numImVerts-1]; + numImVerts = 2; + break; + default: + numImVerts = 0; + } + } + + imVert.setX(vert.x); + imVert.setY(vert.y); + imVert.setZ(vert.z); + im3dVerts[numImVerts++] = imVert; +} + +float epsilon = 0.000001; + +class RBCurve +{ +public: + int degree; + std::vector verts; + std::vector weights; // for rational + std::vector knots; + + V3d eval(float u); + void drawHull(void); + void drawSpline(void); +}; + +class RBSurf +{ +public: + int degreeU, degreeV; + int numU, numV; + std::vector verts; + std::vector weights; + std::vector knotsU, knotsV; + + void update(void); + V3d eval(float u, float v); + void drawHull(void); + void drawIsoparms(void); + void drawShaded(void); +}; + +float div0(float a, float b) { return b == 0.0f ? a : a/b; } + +// naive algorithm +float +evalBasis(int i, int d, float u, float knots[]) +{ + if(d == 0){ + if(knots[i] <= u && u < knots[i+1]) + return 1.0f; + return 0.0f; + } + + float b0 = evalBasis(i, d-1, u, knots); + float b1 = evalBasis(i+1, d-1, u, knots); + return b0*div0(u-knots[i], knots[i+d] - knots[i]) + b1*div0(knots[i+d+1]-u, knots[i+d+1] - knots[i+1]); +} + +float +evalBasisFast(int i, int d, float u, float knots[]) +{ + int r, j; + float tmp[10]; + + // degree 0 values + for(j = 0; j < d+1; j++) + tmp[j] = knots[i+j] <= u && u < knots[i+j+1] ? 1.0f : 0.0f; + + // build up from degree zero + for(r = d, d = 1; r > 0; r--, d++){ + for(j = 0; j < r; j++){ + float t1 = div0(u-knots[i+j], knots[i+j + d] - knots[i+j]); + float t2 = div0(knots[i+j + d+1]-u, knots[i+j + d+1] - knots[i+j + 1]); + tmp[j] = tmp[j]*t1 + tmp[j+1]*t2; + } + } + return tmp[0]; +} + +V3d +RBCurve::eval(float u) +{ + int i; + V3d vert = { 0.0f, 0.0f, 0.0f }; + float w = 0.0f; + + // Find knots we're interested in + for(i = 0; i < knots.size(); i++) + if(knots[i] <= u && u < knots[i+1]) + break; + int startI = i-degree; + int endI = i+1; + + for(i = startI; i < endI; i++){ + float r = evalBasisFast(i, degree, u, &knots[0]); + w += weights[i]*r; + vert = add(vert, scale(verts[i], weights[i]*r)); + } + return scale(vert, div0(1.0f,w)); +} + +void +RBCurve::drawHull(void) +{ + int i; + + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + + BeginIm3D(rw::PRIMTYPEPOLYLINE); + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(138, 72, 51, 255); +// imVert.setColor(228, 172, 121, 255); + for(i = 0; i < verts.size(); i++) + AddVertex(verts[i]); + EndIm3D(); +} + +void +RBCurve::drawSpline(void) +{ + int i; + float u, endu; + V3d vert; + + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + BeginIm3D(rw::PRIMTYPEPOLYLINE); + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(0, 4, 96, 255); + + u = knots[0]; + endu = knots[knots.size()-1] - epsilon; + float uinc = (endu-knots[0])/40.0f; + for(;; u += uinc){ + if(u > endu) + u = endu; + AddVertex(eval(u)); + if(u >= endu) + break; + } + + EndIm3D(); +} + +void +RBSurf::update(void) +{ + numU = knotsU.size() - degreeU - 1; + numV = knotsV.size() - degreeV - 1; +} + +V3d +RBSurf::eval(float u, float v) +{ + int i, j; + V3d vert = { 0.0f, 0.0f, 0.0f }; + float w = 0.0f; + + float basisU[10], basisV[10]; + + // Find knots we're interested in + int k; + for(k = 0; k < knotsU.size(); k++) + if(knotsU[k] <= u && u < knotsU[k+1]) + break; + int startI = k-degreeU; + int endI = k+1; + for(k = 0; k < knotsV.size(); k++) + if(knotsV[k] <= v && v < knotsV[k+1]) + break; + int startJ = k-degreeV; + int endJ = k+1; + + for(i = startI; i < endI; i++) basisU[i-startI] = evalBasisFast(i, degreeU, u, &knotsU[0]); + for(j = startJ; j < endJ; j++) basisV[j-startJ] = evalBasisFast(j, degreeV, v, &knotsV[0]); + + for(j = startJ; j < endJ; j++) + for(i = startI; i < endI; i++){ + float r = basisV[j-startJ]*basisU[i-startI]; + w += weights[j*numU + i]*r; + vert = add(vert, scale(verts[j*numU + i], weights[j*numU + i]*r)); + } + return scale(vert, div0(1.0f,w)); +} + +void +RBSurf::drawHull(void) +{ + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(138, 72, 51, 255); +// imVert.setColor(228, 172, 121, 255); + + int iu, iv; + for(iv = 0; iv < numV; iv++){ + BeginIm3D(rw::PRIMTYPEPOLYLINE); + for(iu = 0; iu < numU; iu++) + AddVertex(verts[iu + iv*numU]); + EndIm3D(); + } + + for(iu = 0; iu < numU; iu++){ + BeginIm3D(rw::PRIMTYPEPOLYLINE); + for(iv = 0; iv < numV; iv++) + AddVertex(verts[iu + iv*numU]); + EndIm3D(); + } +} + +void +RBSurf::drawIsoparms(void) +{ + V3d vert; + int iu, iv; + float u, v; + + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(0, 4, 96, 255); + + float endu = knotsU[knotsU.size()-1] - epsilon; + float endv = knotsU[knotsV.size()-1] - epsilon; + float uinc = (endu-knotsU[0])/40.0f; + float vinc = (endv-knotsV[0])/40.0f; + + v = -100000.0f; + for(iv = 0; iv < knotsV.size(); iv++){ + if(knotsV[iv] <= v) continue; + v = knotsV[iv]; + if(v > endv) v = endv; + + BeginIm3D(rw::PRIMTYPEPOLYLINE); + for(u = knotsU[0];; u += uinc){ + if(u > endu) + u = endu; + AddVertex(eval(u, v)); + if(u >= endu) + break; + } + EndIm3D(); + } + + u = -100000.0f; + for(iu = 0; iu < knotsU.size(); iu++){ + if(knotsU[iu] <= u) continue; + u = knotsU[iu]; + if(u > endu) u = endu; + + BeginIm3D(rw::PRIMTYPEPOLYLINE); + for(v = knotsV[0];; v += vinc){ + if(v > endv) + v = endv; + AddVertex(eval(u, v)); + if(v >= endv) + break; + } + EndIm3D(); + } +} + +void +RBSurf::drawShaded(void) +{ + V3d vert; + int iu, iv; + float u, v; + + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::FOGENABLE, 0); + + imVert.setU(0.0f); + imVert.setV(0.0f); + imVert.setColor(0, 128, 240, 255); + + float endu = knotsU[knotsU.size()-1] - epsilon; + float endv = knotsU[knotsV.size()-1] - epsilon; + float uinc = (endu-knotsU[0])/40.0f; + float vinc = (endv-knotsV[0])/40.0f; + + float vnext; + for(v = knotsV[0];; v = vnext){ + if(v > endv) + v = endv; + vnext = v + vinc; + + BeginIm3D(rw::PRIMTYPETRISTRIP); + for(u = knotsU[0];; u += uinc){ + if(u > endu) + u = endu; + + AddVertex(eval(u, v)); + AddVertex(eval(u, vnext)); + + if(u >= endu) + break; + } + EndIm3D(); + if(vnext >= endv) + break; + } +} + + +RBCurve testspline1, testspline2; +RBSurf testsurf; + +void +initsplines(void) +{ + V3d vert; + + testspline1.degree = 3; + testspline1.verts.clear(); + testspline1.weights.clear(); + vert.set(-30.63383, 22.65459, 0); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + vert.set(13.50783, 33.01786, 15.06403); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + vert.set(34.252, -10.36327, 15.06403); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + vert.set(-7.959972, -1.205032, 0); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + vert.set(6.995127, -41.32158, -18.19684); + vert = scale(vert, 1.0f/20.0f); + testspline1.verts.push_back(vert); + testspline1.weights.push_back(1.0f); + + testspline1.knots.clear(); + testspline1.knots.push_back(0); + testspline1.knots.push_back(0); + testspline1.knots.push_back(0); + testspline1.knots.push_back(0); + testspline1.knots.push_back(1); + testspline1.knots.push_back(2); + testspline1.knots.push_back(2); + testspline1.knots.push_back(2); + testspline1.knots.push_back(2); + + + testspline2.degree = 2; + testspline2.verts.clear(); +#define V(x, y, z) \ + vert.set(x, y, z); \ + testspline2.verts.push_back(scale(vert, 1.0f/20.0f)); \ + testspline2.weights.push_back(1.0f); + V(-61.9913, 9.158239, 0); + V(-32.32231, 27.23371, 0); + V(25.80961, -4.820126, 0); + testspline2.knots.clear(); + testspline2.knots.push_back(0); + testspline2.knots.push_back(0); + testspline2.knots.push_back(0); + testspline2.knots.push_back(1); + testspline2.knots.push_back(1); + testspline2.knots.push_back(1); + +/* + testspline2 = testspline1; +// testspline2.knots.clear(); +// testspline2.knots.push_back(0); +// testspline2.knots.push_back(0); +// testspline2.knots.push_back(0); +// testspline2.knots.push_back(0); +// testspline2.knots.push_back(3); +// testspline2.knots.push_back(4); +// testspline2.knots.push_back(4); +// testspline2.knots.push_back(4); +// testspline2.knots.push_back(4); + testspline1.weights[2] = 5.0f; +*/ + +#define V(x, y, z) \ + vert.set(x, y, z); \ + testsurf.verts.push_back(scale(vert, 1.0f/20.0f)); \ + testsurf.weights.push_back(1.0f); + + testsurf.degreeU = 3; + testsurf.degreeV = 3; + testsurf.verts.clear(); + testsurf.weights.clear(); + V(-69.22764, -0, 12.77366); + V(-48.72468, 0, 29.16251); + V(-24.84476, 0, 39.52605); + V(22.43265, -0, 45.79238); + V(36.4229, 0, 28.9215); + V(61.02645, 0, 6.74835); + V(86.35364, -0, 20.96809); + V(-69.22764, 9.286676, 12.77366); + V(-48.72468, 9.286676, 29.16251); + V(-24.84476, 9.286676, 39.52605); + V(22.43265, 9.286676, 45.79238); + V(36.4229, 9.286676, 28.9215); + V(61.02645, 9.286676, 6.74835); + V(86.35364, 9.286676, 20.96809); + V(-68.13416, 23.51821, 6.114491); + V(-48.19925, 23.51821, 22.27994); + V(-26.91943, 23.51821, 33.20763); + V(18.69658, 23.51821, 39.0488); + V(27.88844, 23.51821, 30.80604); + V(57.49908, 23.51821, -0.6488895); + V(86.35364, 23.51821, 14.21974); + V(-67.00163, 52.46369, -0.7825046); + V(-47.65504, 52.46369, 15.15157); + V(-29.06818, 52.46369, 26.66356); + V(14.82709, 52.46369, 32.06438); + V(19.04919, 52.46369, 32.75788); + V(53.84574, 52.46369, -8.310311); + V(86.35364, 52.46369, 7.230374); + V(-67.86079, 70.07219, 4.449699); + V(-48.06789, 70.07219, 20.5593); + V(-27.43809, 70.07219, 31.62803); + V(17.76257, 70.07219, 37.36291); + V(25.75483, 70.07219, 31.27717); + V(56.61724, 70.07219, -2.498198); + V(86.35364, 70.07219, 12.53265); + testsurf.knotsU.clear(); + testsurf.knotsU.push_back(0); + testsurf.knotsU.push_back(0); + testsurf.knotsU.push_back(0); + testsurf.knotsU.push_back(0); + testsurf.knotsU.push_back(0.25); + testsurf.knotsU.push_back(0.5); + testsurf.knotsU.push_back(0.75); + testsurf.knotsU.push_back(1); + testsurf.knotsU.push_back(1); + testsurf.knotsU.push_back(1); + testsurf.knotsU.push_back(1); + testsurf.knotsV.clear(); + testsurf.knotsV.push_back(0); + testsurf.knotsV.push_back(0); + testsurf.knotsV.push_back(0); + testsurf.knotsV.push_back(0); + testsurf.knotsV.push_back(0.5); + testsurf.knotsV.push_back(1); + testsurf.knotsV.push_back(1); + testsurf.knotsV.push_back(1); + testsurf.knotsV.push_back(1); + + testsurf.update(); +} + +void +rendersplines(void) +{ + testspline1.drawHull(); + testspline1.drawSpline(); + +// testspline2.drawHull(); +// testspline2.drawSpline(); + + testsurf.drawHull(); + testsurf.drawShaded(); + testsurf.drawIsoparms(); +} diff --git a/tools/playground/teapot.dff b/tools/playground/teapot.dff new file mode 100644 index 0000000..7a65176 Binary files /dev/null and b/tools/playground/teapot.dff differ diff --git a/tools/playground/tl_tests.cpp b/tools/playground/tl_tests.cpp new file mode 100644 index 0000000..19e7f3a --- /dev/null +++ b/tools/playground/tl_tests.cpp @@ -0,0 +1,662 @@ +#include +#include + +extern bool dosoftras; + +using namespace rw; +using namespace RWDEVICE; + +void rastest_renderTriangles(RWDEVICE::Im2DVertex *scrverts, int32 verts, uint16 *indices, int32 numTris); + +// +// This is a test to implement T&L in software and render with Im2D +// + +namespace gen { + +#define MAX_LIGHTS 8 + +struct Directional { + V3d at; + RGBAf color; +}; +static Directional directionals[MAX_LIGHTS]; +static int32 numDirectionals; +static RGBAf ambLight; + +static void +enumLights(Matrix *lightmat) +{ + int32 n; + World *world; + + world = (World*)engine->currentWorld; + ambLight.red = 0.0; + ambLight.green = 0.0; + ambLight.blue = 0.0; + ambLight.alpha = 0.0; + numDirectionals = 0; + // only unpositioned lights right now + FORLIST(lnk, world->globalLights){ + Light *l = Light::fromWorld(lnk); + if(l->getType() == Light::DIRECTIONAL){ + if(numDirectionals >= MAX_LIGHTS) + continue; + n = numDirectionals++; + V3d::transformVectors(&directionals[n].at, &l->getFrame()->getLTM()->at, 1, lightmat); + directionals[n].color = l->color; + directionals[n].color.alpha = 0.0f; + }else if(l->getType() == Light::AMBIENT){ + ambLight.red += l->color.red; + ambLight.green += l->color.green; + ambLight.blue += l->color.blue; + } + } +} + +struct ObjSpace3DVertex +{ + V3d objVertex; + V3d objNormal; + RGBA color; + TexCoords texCoords; +}; + +enum { + CLIPXLO = 0x01, + CLIPXHI = 0x02, + CLIPX = 0x03, + CLIPYLO = 0x04, + CLIPYHI = 0x08, + CLIPY = 0x0C, + CLIPZLO = 0x10, + CLIPZHI = 0x20, + CLIPZ = 0x30, +}; + +struct CamSpace3DVertex +{ + V3d camVertex; + uint8 clipFlags; + RGBAf color; + TexCoords texCoords; +}; + +struct InstanceData +{ + uint16 *indices; + int32 numIndices; + ObjSpace3DVertex *vertices; + int32 numVertices; + // int vertStride; // not really needed right now + Material *material; + Mesh *mesh; +}; + +struct InstanceDataHeader : public rw::InstanceDataHeader +{ + uint32 serialNumber; + ObjSpace3DVertex *vertices; + uint16 *indices; + InstanceData *inst; +}; + +static void +instanceAtomic(Atomic *atomic) +{ + static V3d zeroNorm = { 0.0f, 0.0f, 0.0f }; + static RGBA black = { 0, 0, 0, 255 }; + static TexCoords zeroTex = { 0.0f, 0.0f }; + int i; + uint j; + int x, x1, x2, x3; + Geometry *geo; + MeshHeader *header; + Mesh *mesh; + InstanceDataHeader *insthead; + InstanceData *inst; + uint32 firstVert; + uint16 *srcindices, *dstindices; + + geo = atomic->geometry; + if(geo->instData) + return; + header = geo->meshHeader; + int numTris; + if(header->flags & MeshHeader::TRISTRIP) + numTris = header->totalIndices - 2*header->numMeshes; + else + numTris = header->totalIndices / 3; + int size; + size = sizeof(InstanceDataHeader) + header->numMeshes*sizeof(InstanceData) + + geo->numVertices*sizeof(ObjSpace3DVertex) + numTris*6*sizeof(uint16); + insthead = (InstanceDataHeader*)rwNew(size, ID_GEOMETRY); + geo->instData = insthead; + insthead->platform = 0; + insthead->serialNumber = header->serialNum; + inst = (InstanceData*)(insthead+1); + insthead->inst = inst; + insthead->vertices = (ObjSpace3DVertex*)(inst+header->numMeshes); + dstindices = (uint16*)(insthead->vertices+geo->numVertices); + insthead->indices = dstindices; + + // TODO: morphing + MorphTarget *mt = geo->morphTargets; + for(i = 0; i < geo->numVertices; i++){ + insthead->vertices[i].objVertex = mt->vertices[i]; + if(geo->flags & Geometry::NORMALS) + insthead->vertices[i].objNormal = mt->normals[i]; + else + insthead->vertices[i].objNormal = zeroNorm; + if(geo->flags & Geometry::PRELIT) + insthead->vertices[i].color = geo->colors[i]; + else + insthead->vertices[i].color = black; + if(geo->numTexCoordSets > 0) + insthead->vertices[i].texCoords = geo->texCoords[0][i]; + else + insthead->vertices[i].texCoords = zeroTex; + } + + mesh = header->getMeshes(); + for(i = 0; i < header->numMeshes; i++){ + findMinVertAndNumVertices(mesh->indices, mesh->numIndices, + &firstVert, &inst->numVertices); + inst->indices = dstindices; + inst->vertices = &insthead->vertices[firstVert]; + inst->mesh = mesh; + inst->material = mesh->material; + srcindices = mesh->indices; + if(header->flags & MeshHeader::TRISTRIP){ + inst->numIndices = 0; + x = 0; + for(j = 0; j < mesh->numIndices-2; j++){ + x1 = srcindices[j+x]; + x ^= 1; + x2 = srcindices[j+x]; + x3 = srcindices[j+2]; + if(x1 != x2 && x2 != x3 && x1 != x3){ + dstindices[0] = x1; + dstindices[1] = x2; + dstindices[2] = x3; + dstindices += 3; + inst->numIndices += 3; + } + } + }else{ + inst->numIndices = mesh->numIndices; + for(j = 0; j < mesh->numIndices; j += 3){ + dstindices[0] = srcindices[j+0] - firstVert; + dstindices[1] = srcindices[j+1] - firstVert; + dstindices[2] = srcindices[j+2] - firstVert; + dstindices += 3; + } + } + + inst++; + mesh++; + } +} + +struct MeshState +{ + int32 flags; + Matrix obj2cam; + Matrix obj2world; + int32 numVertices; + int32 numPrimitives; + SurfaceProperties surfProps; + RGBA matCol; +}; + +static void +cam2screen(Im2DVertex *scrvert, CamSpace3DVertex *camvert) +{ + RGBA col; + float32 recipZ; + Camera *cam = (Camera*)engine->currentCamera; + int32 width = cam->frameBuffer->width; + int32 height = cam->frameBuffer->height; + recipZ = 1.0f/camvert->camVertex.z; + + scrvert->setScreenX(camvert->camVertex.x * recipZ * width); + scrvert->setScreenY(camvert->camVertex.y * recipZ * height); +// scrvert->setScreenX(camvert->camVertex.x * recipZ * width/2 + width/4); +// scrvert->setScreenY(camvert->camVertex.y * recipZ * height/2 + height/4); + scrvert->setScreenZ(recipZ * cam->zScale + cam->zShift); + scrvert->setCameraZ(camvert->camVertex.z); + scrvert->setRecipCameraZ(recipZ); + scrvert->setU(camvert->texCoords.u, recipZ); + scrvert->setV(camvert->texCoords.v, recipZ); + convColor(&col, &camvert->color); + scrvert->setColor(col.red, col.green, col.blue, col.alpha); +} + +static void +transform(MeshState *mstate, ObjSpace3DVertex *objverts, CamSpace3DVertex *camverts, Im2DVertex *scrverts) +{ + int32 i; + float32 z; + Camera *cam = (Camera*)engine->currentCamera; + + for(i = 0; i < mstate->numVertices; i++){ + V3d::transformPoints(&camverts[i].camVertex, &objverts[i].objVertex, 1, &mstate->obj2cam); + convColor(&camverts[i].color, &objverts[i].color); + camverts[i].texCoords = objverts[i].texCoords; + + camverts[i].clipFlags = 0; + z = camverts[i].camVertex.z; + // 0 < x < z + if(camverts[i].camVertex.x >= z) camverts[i].clipFlags |= CLIPXHI; + if(camverts[i].camVertex.x <= 0) camverts[i].clipFlags |= CLIPXLO; + // 0 < y < z + if(camverts[i].camVertex.y >= z) camverts[i].clipFlags |= CLIPYHI; + if(camverts[i].camVertex.y <= 0) camverts[i].clipFlags |= CLIPYLO; + // near < z < far + if(z >= cam->farPlane) camverts[i].clipFlags |= CLIPZHI; + if(z <= cam->nearPlane) camverts[i].clipFlags |= CLIPZLO; + + cam2screen(&scrverts[i], &camverts[i]); + } +} + +static void +light(MeshState *mstate, ObjSpace3DVertex *objverts, CamSpace3DVertex *camverts) +{ + int32 i; + RGBAf colf; + RGBAf amb = ambLight; + amb = scale(ambLight, mstate->surfProps.ambient); + for(i = 0; i < mstate->numVertices; i++){ + camverts[i].color = add(camverts[i].color, amb); + if((mstate->flags & Geometry::NORMALS) == 0) + continue; + for(int32 k = 0; k < numDirectionals; k++){ + float32 f = dot(objverts[i].objNormal, neg(directionals[k].at)); + if(f <= 0.0f) continue; + f *= mstate->surfProps.diffuse; + colf = scale(directionals[k].color, f); + camverts[i].color = add(camverts[i].color, colf); + } + } +} + +static void +postlight(MeshState *mstate, CamSpace3DVertex *camverts, Im2DVertex *scrverts) +{ + int32 i; + RGBA col; + RGBAf colf; + for(i = 0; i < mstate->numVertices; i++){ + convColor(&colf, &mstate->matCol); + camverts[i].color = modulate(camverts[i].color, colf); + clamp(&camverts[i].color); + convColor(&col, &camverts[i].color); + scrverts[i].setColor(col.red, col.green, col.blue, col.alpha); + } +} + +static int32 +cullTriangles(MeshState *mstate, CamSpace3DVertex *camverts, uint16 *indices, uint16 *clipindices) +{ + int32 i; + int32 x1, x2, x3; + int32 newNumPrims; + int32 numClip; + + newNumPrims = 0; + numClip = 0; + for(i = 0; i < mstate->numPrimitives; i++, indices += 3){ + x1 = indices[0]; + x2 = indices[1]; + x3 = indices[2]; + // Only a simple frustum call + if(camverts[x1].clipFlags & + camverts[x2].clipFlags & + camverts[x3].clipFlags) + continue; + if(camverts[x1].clipFlags | + camverts[x2].clipFlags | + camverts[x3].clipFlags) + numClip++; + // The Triangle is in, probably + clipindices[0] = x1; + clipindices[1] = x2; + clipindices[2] = x3; + clipindices += 3; + newNumPrims++; + } + mstate->numPrimitives = newNumPrims; + return numClip; +} + +static void +interpVertex(CamSpace3DVertex *out, CamSpace3DVertex *v1, CamSpace3DVertex *v2, float32 t) +{ + float32 z; + float32 invt; + Camera *cam = (Camera*)engine->currentCamera; + + invt = 1.0f - t; + out->camVertex = add(scale(v1->camVertex, invt), scale(v2->camVertex, t)); + out->color = add(scale(v1->color, invt), scale(v2->color, t)); + out->texCoords.u = v1->texCoords.u*invt + v2->texCoords.u*t; + out->texCoords.v = v1->texCoords.v*invt + v2->texCoords.v*t; + + out->clipFlags = 0; + z = out->camVertex.z; + // 0 < x < z + if(out->camVertex.x >= z) out->clipFlags |= CLIPXHI; + if(out->camVertex.x <= 0) out->clipFlags |= CLIPXLO; + // 0 < y < z + if(out->camVertex.y >= z) out->clipFlags |= CLIPYHI; + if(out->camVertex.y <= 0) out->clipFlags |= CLIPYLO; + // near < z < far + if(z >= cam->farPlane) out->clipFlags |= CLIPZHI; + if(z <= cam->nearPlane) out->clipFlags |= CLIPZLO; +} + +static void +clipTriangles(MeshState *mstate, CamSpace3DVertex *camverts, Im2DVertex *scrverts, uint16 *indices, uint16 *clipindices) +{ + int32 i, j; + int32 x1, x2, x3; + int32 newNumPrims; + CamSpace3DVertex buf[18]; + CamSpace3DVertex *in, *out, *tmp; + int32 nin, nout; + float32 t; + Camera *cam = (Camera*)engine->currentCamera; + + newNumPrims = 0; + for(i = 0; i < mstate->numPrimitives; i++, indices += 3){ + x1 = indices[0]; + x2 = indices[1]; + x3 = indices[2]; + + if((camverts[x1].clipFlags | + camverts[x2].clipFlags | + camverts[x3].clipFlags) == 0){ + // all inside + clipindices[0] = x1; + clipindices[1] = x2; + clipindices[2] = x3; + clipindices += 3; + newNumPrims++; + continue; + } + + // set up triangle + in = &buf[0]; + out = &buf[9]; + in[0] = camverts[x1]; + in[1] = camverts[x2]; + in[2] = camverts[x3]; + nin = 3; + nout = 0; + +#define V(a) in[a].camVertex. + + // clip z near + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPZLO){ + t = (cam->nearPlane - V(x1)z)/(V(x2)z - V(x1)z); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPZLO) == 0) + out[nout++] = in[x2]; + } + // clip z far + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPZHI){ + t = (cam->farPlane - V(x1)z)/(V(x2)z - V(x1)z); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPZHI) == 0) + out[nout++] = in[x2]; + } + // clip y 0 + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPYLO){ + t = -V(x1)y/(V(x2)y - V(x1)y); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPYLO) == 0) + out[nout++] = in[x2]; + } + // clip y z + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPYHI){ + t = (V(x1)z - V(x1)y)/(V(x1)z - V(x1)y + V(x2)y - V(x2)z); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPYHI) == 0) + out[nout++] = in[x2]; + } + // clip x 0 + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPXLO){ + t = -V(x1)x/(V(x2)x - V(x1)x); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPXLO) == 0) + out[nout++] = in[x2]; + } + // clip x z + nin = nout; nout = 0; + tmp = in; in = out; out = tmp; + for(j = 0; j < nin; j++){ + x1 = j; + x2 = (j+1) % nin; + if((in[x1].clipFlags ^ in[x2].clipFlags) & CLIPXHI){ + t = (V(x1)z - V(x1)x)/(V(x1)z - V(x1)x + V(x2)x - V(x2)z); + interpVertex(&out[nout++], &in[x1], &in[x2], t); + } + if((in[x2].clipFlags & CLIPXHI) == 0) + out[nout++] = in[x2]; + } + + // Insert new triangles + x1 = mstate->numVertices; + for(j = 0; j < nout; j++){ + x2 = mstate->numVertices++; + camverts[x2] = out[j]; + cam2screen(&scrverts[x2], &camverts[x2]); + } + x2 = x1+1; + for(j = 0; j < nout-2; j++){ + clipindices[0] = x1; + clipindices[1] = x2++; + clipindices[2] = x2; + clipindices += 3; + newNumPrims++; + } + } + mstate->numPrimitives = newNumPrims; +} + +static void +submitTriangles(RWDEVICE::Im2DVertex *scrverts, int32 numVerts, uint16 *indices, int32 numTris) +{ + rw::SetRenderStatePtr(rw::TEXTURERASTER, nil); + if(dosoftras) + rastest_renderTriangles(scrverts, numVerts, indices, numTris); + else{ + //int i; + //for(i = 0; i < numVerts; i++){ + // scrverts[i].x = (int)(scrverts[i].x*16.0f) / 16.0f; + // scrverts[i].y = (int)(scrverts[i].y*16.0f) / 16.0f; + //} + im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, scrverts, numVerts, + indices, numTris*3); + } +} + + +static void +drawMesh(MeshState *mstate, ObjSpace3DVertex *objverts, uint16 *indices) +{ + CamSpace3DVertex *camverts; + Im2DVertex *scrverts; + uint16 *cullindices, *clipindices; + uint32 numClip; + + camverts = rwNewT(CamSpace3DVertex, mstate->numVertices, MEMDUR_FUNCTION); + scrverts = rwNewT(Im2DVertex, mstate->numVertices, MEMDUR_FUNCTION); + cullindices = rwNewT(uint16, mstate->numPrimitives*3, MEMDUR_FUNCTION); + + transform(mstate, objverts, camverts, scrverts); + + numClip = cullTriangles(mstate, camverts, indices, cullindices); + +// int32 i; +// for(i = 0; i < mstate->numVertices; i++){ +// if(camverts[i].clipFlags & CLIPX) +// camverts[i].color.red = 255; +// if(camverts[i].clipFlags & CLIPY) +// camverts[i].color.green = 255; +// if(camverts[i].clipFlags & CLIPZ) +// camverts[i].color.blue = 255; +// } + + light(mstate, objverts, camverts); + +// mstate->matCol.red = 255; +// mstate->matCol.green = 255; +// mstate->matCol.blue = 255; + + postlight(mstate, camverts, scrverts); + + // each triangle can have a maximum of 9 vertices (7 triangles) after clipping + // so resize to whatever we may need + camverts = rwResizeT(CamSpace3DVertex, camverts, mstate->numVertices + numClip*9, MEMDUR_FUNCTION); + scrverts = rwResizeT(Im2DVertex, scrverts, mstate->numVertices + numClip*9, MEMDUR_FUNCTION); + clipindices = rwNewT(uint16, mstate->numPrimitives*3 + numClip*7*3, MEMDUR_FUNCTION); + + clipTriangles(mstate, camverts, scrverts, cullindices, clipindices); + + submitTriangles(scrverts, mstate->numVertices, clipindices, mstate->numPrimitives); + + rwFree(camverts); + rwFree(scrverts); + rwFree(cullindices); + rwFree(clipindices); +} + +static void +drawAtomic(Atomic *atomic) +{ + MeshState mstate; + Matrix lightmat; + Geometry *geo; + MeshHeader *header; + InstanceData *inst; + int i; + Camera *cam = (Camera*)engine->currentCamera; + + instanceAtomic(atomic); + + mstate.obj2world = *atomic->getFrame()->getLTM(); + mstate.obj2cam = mstate.obj2world; + mstate.obj2cam.transform(&cam->viewMatrix, COMBINEPOSTCONCAT); + Matrix::invert(&lightmat, &mstate.obj2world); + enumLights(&lightmat); + + geo = atomic->geometry; + header = geo->meshHeader; + inst = ((InstanceDataHeader*)geo->instData)->inst; + for(i = 0; i < header->numMeshes; i++){ + mstate.flags = geo->flags; + mstate.numVertices = inst->numVertices; + mstate.numPrimitives = inst->numIndices / 3; + mstate.surfProps = inst->material->surfaceProps; + mstate.matCol = inst->material->color; + drawMesh(&mstate, inst->vertices, inst->indices); + inst++; + } +} + +void +tlTest(Clump *clump) +{ + FORLIST(lnk, clump->atomics){ + Atomic *a = Atomic::fromClump(lnk); + drawAtomic(a); + } +} + +} + +static Im2DVertex *clipverts; +static int32 numClipverts; + +void +genIm3DTransform(void *vertices, int32 numVertices, Matrix *world) +{ + Im3DVertex *objverts; + V3d pos; + Matrix xform; + Camera *cam; + int32 i; + objverts = (Im3DVertex*)vertices; + + cam = (Camera*)engine->currentCamera; + int32 width = cam->frameBuffer->width; + int32 height = cam->frameBuffer->height; + + + xform = cam->viewMatrix; + if(world) + xform.transform(world, COMBINEPRECONCAT); + + clipverts = rwNewT(Im2DVertex, numVertices, MEMDUR_EVENT); + numClipverts = numVertices; + + for(i = 0; i < numVertices; i++){ + V3d::transformPoints(&pos, &objverts[i].position, 1, &xform); + + float32 recipZ = 1.0f/pos.z; + RGBA c = objverts[i].getColor(); + + clipverts[i].setScreenX(pos.x * recipZ * width); + clipverts[i].setScreenY(pos.y * recipZ * height); + clipverts[i].setScreenZ(recipZ * cam->zScale + cam->zShift); + clipverts[i].setCameraZ(pos.z); + clipverts[i].setRecipCameraZ(recipZ); + clipverts[i].setColor(c.red, c.green, c.blue, c.alpha); + clipverts[i].setU(objverts[i].u, recipZ); + clipverts[i].setV(objverts[i].v, recipZ); + } +} + +void +genIm3DRenderIndexed(PrimitiveType prim, void *indices, int32 numIndices) +{ + im2d::RenderIndexedPrimitive(prim, clipverts, numClipverts, indices, numIndices); +} + +void +genIm3DEnd(void) +{ + rwFree(clipverts); + clipverts = nil; + numClipverts = 0; +} diff --git a/tools/ps2test/gs.h b/tools/ps2test/gs.h new file mode 100644 index 0000000..361ad8c --- /dev/null +++ b/tools/ps2test/gs.h @@ -0,0 +1,437 @@ +#define GS_NONINTERLACED 0 +#define GS_INTERLACED 1 + +#define GS_NTSC 2 +#define GS_PAL 3 +#define GS_VESA1A 0x1a +#define GS_VESA1B 0x1b +#define GS_VESA1C 0x1c +#define GS_VESA1D 0x1d +#define GS_VESA2A 0x2a +#define GS_VESA2B 0x2b +#define GS_VESA2C 0x2c +#define GS_VESA2D 0x2d +#define GS_VESA2E 0x2e +#define GS_VESA3B 0x3b +#define GS_VESA3C 0x3c +#define GS_VESA3D 0x3d +#define GS_VESA3E 0x3e +#define GS_VESA4A 0x4a +#define GS_VESA4B 0x4b +#define GS_DTV480P 0x50 + +#define GS_FIELD 0 +#define GS_FRAME 1 + +#define GS_PSMCT32 0 +#define GS_PSMCT24 1 +#define GS_PSMCT16 2 +#define GS_PSMCT16S 10 +#define GS_PS_GPU24 18 + +#define GS_PSMZ32 0 +#define GS_PSMZ24 1 +#define GS_PSMZ16 2 +#define GS_PSMZ16S 10 + +#define GS_ZTST_NEVER 0 +#define GS_ZTST_ALWAYS 1 +#define GS_ZTST_GREATER 2 +#define GS_ZTST_GEQUAL 3 + +#define GS_PRIM_POINT 0 +#define GS_PRIM_LINE 1 +#define GS_PRIM_LINE_STRIP 2 +#define GS_PRIM_TRI 3 +#define GS_PRIM_TRI_STRIP 4 +#define GS_PRIM_TRI_FAN 5 +#define GS_PRIM_SPRITE 6 +#define GS_PRIM_NO_SPEC 7 +#define GS_IIP_FLAT 0 +#define GS_IIP_GOURAUD 1 + +/* GS general purpose registers */ + +#define GS_PRIM 0x00 +#define GS_RGBAQ 0x01 +#define GS_ST 0x02 +#define GS_UV 0x03 +#define GS_XYZF2 0x04 +#define GS_XYZ2 0x05 +#define GS_TEX0_1 0x06 +#define GS_TEX0_2 0x07 +#define GS_CLAMP_1 0x08 +#define GS_CLAMP_2 0x09 +#define GS_FOG 0x0a +#define GS_XYZF3 0x0c +#define GS_XYZ3 0x0d +#define GS_TEX1_1 0x14 +#define GS_TEX1_2 0x15 +#define GS_TEX2_1 0x16 +#define GS_TEX2_2 0x17 +#define GS_XYOFFSET_1 0x18 +#define GS_XYOFFSET_2 0x19 +#define GS_PRMODECONT 0x1a +#define GS_PRMODE 0x1b +#define GS_TEXCLUT 0x1c +#define GS_SCANMSK 0x22 +#define GS_MIPTBP1_1 0x34 +#define GS_MIPTBP1_2 0x35 +#define GS_MIPTBP2_1 0x36 +#define GS_MIPTBP2_2 0x37 +#define GS_TEXA 0x3b +#define GS_FOGCOL 0x3d +#define GS_TEXFLUSH 0x3f +#define GS_SCISSOR_1 0x40 +#define GS_SCISSOR_2 0x41 +#define GS_ALPHA_1 0x42 +#define GS_ALPHA_2 0x43 +#define GS_DIMX 0x44 +#define GS_DTHE 0x45 +#define GS_COLCLAMP 0x46 +#define GS_TEST_1 0x47 +#define GS_TEST_2 0x48 +#define GS_PABE 0x49 +#define GS_FBA_1 0x4a +#define GS_FBA_2 0x4b +#define GS_FRAME_1 0x4c +#define GS_FRAME_2 0x4d +#define GS_ZBUF_1 0x4e +#define GS_ZBUF_2 0x4f +#define GS_BITBLTBUF 0x50 +#define GS_TRXPOS 0x51 +#define GS_TRXREG 0x52 +#define GS_TRXDIR 0x53 +#define GS_HWREG 0x54 +#define GS_SIGNAL 0x60 +#define GS_FINISH 0x61 +#define GS_LABEL 0x62 + +typedef union +{ + struct { + uint64 EN1 : 1; + uint64 EN2 : 1; + uint64 CRTMD : 3; + uint64 MMOD : 1; + uint64 AMOD : 1; + uint64 SLBG : 1; + uint64 ALP : 8; + } f; + uint64 d; +} GsPmode; + +#define GS_MAKE_PMODE(EN1,EN2,MMOD,AMOD,SLBG,ALP) \ + (BIT64(EN1,0) | BIT64(EN2,1) | BIT64(1,2) | \ + BIT64(MMOD,5) | BIT64(AMOD,6) | BIT64(SLBG,7) | BIT64(ALP,8)) + +typedef union +{ + struct { + uint64 INT : 1; + uint64 FFMD : 1; + uint64 DPMS : 2; + } f; + uint64 d; +} GsSmode2; + +#define GS_MAKE_SMODE2(INT,FFMD,DPMS) \ + (BIT64(INT,0) | BIT64(FFMD,1) | BIT64(DPMS,2)) + +typedef union +{ + struct { + uint64 FBP : 9; + uint64 FBW : 6; + uint64 PSM : 5; + uint64 : 12; + uint64 DBX : 11; + uint64 DBY : 11; + } f; + uint64 d; +} GsDispfb; + +#define GS_MAKE_DISPFB(FBP,FBW,PSM,DBX,DBY) \ + (BIT64(FBP,0) | BIT64(FBW,9) | BIT64(PSM,15) | \ + BIT64(DBX,32) | BIT64(DBY,43)) + +typedef union +{ + struct { + uint64 DX : 12; + uint64 DY : 11; + uint64 MAGH : 4; + uint64 MAGV : 2; + uint64 : 3; + uint64 DW : 12; + uint64 DH : 11; + } f; + uint64 d; +} GsDisplay; + +#define GS_MAKE_DISPLAY(DX,DY,MAGH,MAGV,DW,DH) \ + (BIT64(DX,0) | BIT64(DY,12) | BIT64(MAGH,23) | \ + BIT64(MAGV,27) | BIT64(DW,32) | BIT64(DH,44)) + +typedef union +{ + struct { + uint64 EXBP : 14; + uint64 EXBW : 6; + uint64 FBIN : 2; + uint64 WFFMD : 1; + uint64 EMODA : 2; + uint64 EMODC : 2; + uint64 : 5; + uint64 WDX : 11; + uint64 WDY : 11; + } f; + uint64 d; +} GsExtbuf; + +#define GS_MAKE_EXTBUF(EXBP,EXBW,FBIN,WFFMD,EMODA,EMODC,WDX,WDY) \ + (BIT64(EXBP,0) | BIT64(EXBW,14) | BIT64(FBIN,20) | \ + BIT64(WFFMD,22) | BIT64(EMODA,23) | BIT64(EMODC,25) | \ + BIT64(WDX,32) | BIT64(WDY,43)) + +typedef union +{ + struct { + uint64 SX : 12; + uint64 SY : 11; + uint64 SMPH : 4; + uint64 SMPV : 2; + uint64 : 3; + uint64 WW : 12; + uint64 WH : 11; + } f; + uint64 d; +} GsExtdata; + +#define GS_MAKE_EXTDATA(SX,SY,SMPH,SMPV,WW,WH) \ + (BIT64(SX,0) | BIT64(SY,12) | BIT64(SMPH,23) | \ + BIT64(SMPV,27) | BIT64(WW,32) | BIT64(WH,44)) + +typedef union +{ + struct { + uint64 WRITE : 1; + } f; + uint64 d; +} GsExtwrite; + +typedef union +{ + struct { + uint64 R : 8; + uint64 G : 8; + uint64 B : 8; + } f; + uint64 d; +} GsBgcolor; + +#define GS_MAKE_BGCOLOR(R,G,B) \ + (BIT64(R,0) | BIT64(G,8) | BIT64(B,16)) + +typedef union +{ + struct { + uint64 SIGNAL : 1; + uint64 FINISH : 1; + uint64 HSINT : 1; + uint64 VSINT : 1; + uint64 EDWINT : 1; + uint64 : 3; + uint64 FLUSH : 1; + uint64 RESET : 1; + uint64 : 2; + uint64 NFIELD : 1; + uint64 FIELD : 1; + uint64 FIFO : 2; + uint64 REV : 8; + uint64 ID : 8; + } f; + uint64 d; +} GsCsr; + +#define GS_CSR_SIGNAL_O 0 +#define GS_CSR_FINISH_O 1 +#define GS_CSR_HSINT_O 2 +#define GS_CSR_VSINT_O 3 +#define GS_CSR_EDWINT_O 4 +#define GS_CSR_FLUSH_O 8 +#define GS_CSR_RESET_O 9 +#define GS_CSR_NFIELD_O 12 +#define GS_CSR_FIELD_O 13 +#define GS_CSR_FIFO_O 14 +#define GS_CSR_REV_O 16 +#define GS_CSR_ID_O 24 + +typedef union +{ + struct { + uint64 : 8; + uint64 SIGMSK : 1; + uint64 FINISHMSK : 1; + uint64 HSMSKMSK : 1; + uint64 VSMSKMSK : 1; + uint64 EDWMSKMSK : 1; + } f; + uint64 d; +} GsImr; + +typedef union +{ + struct { + uint64 DIR : 1; + } f; + uint64 d; +} GsBusdir; + +typedef union +{ + struct { + uint64 SIGID : 32; + uint64 LBLID : 32; + } f; + uint64 d; +} GsSiglblid; + + +typedef union +{ + struct { + uint64 FBP : 9; + uint64 : 7; + uint64 FBW : 6; + uint64 : 2; + uint64 PSM : 6; + uint64 : 2; + uint64 FBMSK : 32; + } f; + uint64 d; +} GsFrame; + +#define GS_MAKE_FRAME(FBP,FBW,PSM,FBMASK) \ + (BIT64(FBP,0) | BIT64(FBW,16) | BIT64(PSM,24) | BIT64(FBMASK,32)) + +typedef union +{ + struct { + uint64 ZBP : 9; + uint64 : 15; + uint64 PSM : 4; + uint64 : 4; + uint64 ZMSDK : 1; + } f; + uint64 d; +} GsZbuf; + +#define GS_MAKE_ZBUF(ZBP,PSM,ZMSK) \ + (BIT64(ZBP,0) | BIT64(PSM,24) | BIT64(ZMSK,32)) + +typedef union +{ + struct { + uint64 OFX : 16; + uint64 : 16; + uint64 OFY : 16; + } f; + uint64 d; +} GsXyOffset; + +#define GS_MAKE_XYOFFSET(OFX,OFY) \ + (BIT64(OFX,0) | BIT64(OFY,32)) + +typedef union +{ + struct { + uint64 SCAX0 : 11; + uint64 : 5; + uint64 SCAX1 : 11; + uint64 : 5; + uint64 SCAY0 : 11; + uint64 : 5; + uint64 SCAY1 : 11; + } f; + uint64 d; +} GsScissor; + +#define GS_MAKE_SCISSOR(SCAX0,SCAX1,SCAY0,SCAY1) \ + (BIT64(SCAX0,0) | BIT64(SCAX1,16) | BIT64(SCAY0,32) | BIT64(SCAY1,48)) + +#define GS_MAKE_TEST(ATE,ATST,AREF,AFAIL,DATE,DATM,ZTE,ZTST) \ + (BIT64(ATE,0) | BIT64(ATST,1) | BIT64(AREF,4) | BIT64(AFAIL,12) | \ + BIT64(DATE,14) | BIT64(DATM,15) | BIT64(ZTE,16) | BIT64(ZTST,17)) + +#define GS_MAKE_PRIM(PRIM,IIP,TME,FGE,ABE,AA1,FST,CTXT,FIX) \ + (BIT64(PRIM,0) | BIT64(IIP,3) | BIT64(TME,4) | BIT64(FGE,5) | \ + BIT64(ABE,6) | BIT64(AA1,7) | BIT64(FST,8) | BIT64(CTXT,9) | BIT64(FIX,10)) + +#define GS_MAKE_RGBAQ(R,G,B,A,Q) \ + (BIT64(R,0) | BIT64(G,8) | BIT64(B,16) | BIT64(A,24) | BIT64(Q,32)) + +#define GS_MAKE_XYZ(X,Y,Z) \ + (BIT64(X,0) | BIT64(Y,16) | BIT64(Z,32)) + +#define GIF_PACKED 0 +#define GIF_REGLIST 1 +#define GIF_IMAGE 2 + +#define GIF_MAKE_TAG(NLOOP,EOP,PRE,PRIM,FLG,NREG) \ + (BIT64(NLOOP,0) | BIT64(EOP,15) | BIT64(PRE,46) | \ + BIT64(PRIM,47) | BIT64(FLG,58) | BIT64(NREG,60)) + +/* This is global and not tied to a user context because + * it is set up by kernel functions and not really changed + * afterwards. */ +typedef struct GsCrtState GsCrtState; +struct GsCrtState +{ + short inter, mode, ff; +}; +extern GsCrtState gsCrtState; + +typedef struct GsDispCtx GsDispCtx; +struct GsDispCtx +{ + // two circuits + GsPmode pmode; + GsDispfb dispfb1; + GsDispfb dispfb2; + GsDisplay display1; + GsDisplay display2; + GsBgcolor bgcolor; +}; + +typedef struct GsDrawCtx GsDrawCtx; +struct GsDrawCtx +{ + //two contexts + uint128 gifTag; + GsFrame frame1; + uint64 ad_frame1; + GsFrame frame2; + uint64 ad_frame2; + GsZbuf zbuf1; + uint64 ad_zbuf1; + GsZbuf zbuf2; + uint64 ad_zbuf2; + GsXyOffset xyoffset1; + uint64 ad_xyoffset1; + GsXyOffset xyoffset2; + uint64 ad_xyoffset2; + GsScissor scissor1; + uint64 ad_scissor1; + GsScissor scissor2; + uint64 ad_scissor2; +}; + +typedef struct GsCtx GsCtx; +struct GsCtx +{ + // display context; two buffers + GsDispCtx disp[2]; + // draw context; two buffers + GsDrawCtx draw[2]; +}; diff --git a/tools/ps2test/main.cpp b/tools/ps2test/main.cpp new file mode 100644 index 0000000..5e58558 --- /dev/null +++ b/tools/ps2test/main.cpp @@ -0,0 +1,787 @@ +#include +#include + +#define PAL + +#include +using rw::uint8; +using rw::uint16; +using rw::uint32; +using rw::uint64; +using rw::int8; +using rw::int16; +using rw::int32; +using rw::int64; +using rw::bool32; +using rw::float32; +typedef uint8 uchar; +typedef uint16 ushort; +typedef uint32 uint; + +#define WIDTH 640 +#ifdef PAL +#define HEIGHT 512 +#define VMODE GS_PAL +#else +#define HEIGHT 448 +#define VMODE GS_NTSC +#endif + +#include "ps2.h" + +// getting undefined references otherwise :/ +int *__errno() { return &errno; } + +// NONINTERLACED and FRAME have half of the FIELD vertical resolution! +// NONINTERLACED has half the vertical units + +uint128 packetbuf[128]; +uint128 vuXYZScale; +uint128 vuXYZOffset; +extern uint32 geometryCall[]; +extern uint32 skinPipe[]; + +uint128 *curVifPtr; +uint128 lightpacket[128]; +int32 numLightQ; + + + +rw::World *world; +rw::Camera *camera; + + +int frames; + +void +printquad(uint128 p) +{ + uint64 *lp; + lp = (uint64*)&p; + printf("%016lx %016lx\n", lp[1], lp[0]); +} + +void +printquad4(uint128 p) +{ + uint32 *lp; + lp = (uint32*)&p; + printf("%08x %08x %08x %08x\n", lp[0], lp[1], lp[2], lp[3]); +} + +void +dump4(uint128 *p, int n) +{ +printf("data at %p\n", p); + while(n--) + printquad4(*p++); +} + +struct DmaChannel { + uint32 chcr; uint32 pad0[3]; + uint32 madr; uint32 pad1[3]; + uint32 qwc; uint32 pad2[3]; + uint32 tadr; uint32 pad3[3]; + uint32 asr0; uint32 pad4[3]; + uint32 asr1; uint32 pad5[3]; + uint32 pad6[8]; + uint32 sadr; +}; + +static struct DmaChannel *dmaChannels[] = { + (struct DmaChannel *) &D0_CHCR, + (struct DmaChannel *) &D1_CHCR, + (struct DmaChannel *) &D2_CHCR, + (struct DmaChannel *) &D3_CHCR, + (struct DmaChannel *) &D4_CHCR, + (struct DmaChannel *) &D5_CHCR, + (struct DmaChannel *) &D6_CHCR, + (struct DmaChannel *) &D7_CHCR, + (struct DmaChannel *) &D8_CHCR, + (struct DmaChannel *) &D9_CHCR +}; + +void +dmaReset(void) +{ + /* don't clear the SIF channels */ + int doclear[] = { 1, 1, 1, 1, 1, 0, 0, 0, 1, 1 }; + int i; + + D_CTRL = 0; + for(i = 0; i < 10; i++) + if(doclear[i]){ + dmaChannels[i]->chcr = 0; + dmaChannels[i]->madr = 0; + dmaChannels[i]->qwc = 0; + dmaChannels[i]->tadr = 0; + dmaChannels[i]->asr0 = 0; + dmaChannels[i]->asr1 = 0; + dmaChannels[i]->sadr = 0; + } + D_CTRL = 1; +} + +void +waitDMA(volatile uint32 *chcr) +{ + while(*chcr & (1<<8)); +} + +void +qwcpy(uint128 *dst, uint128 *src, int n) +{ + while(n--) *dst++ = *src++; +} + +void +toGIF(void *src, int n) +{ + FlushCache(0); + D2_QWC = n; + D2_MADR = (uint32)src; + D2_CHCR = 1<<8; + waitDMA(&D2_CHCR); +} + +void +toGIFchain(void *src) +{ + FlushCache(0); + D2_QWC = 0; + D2_TADR = (uint32)src & 0x0FFFFFFF; + D2_CHCR = 1<<0 | 1<<2 | 1<<6 | 1<<8; + waitDMA(&D2_CHCR); +} + +void +toVIF1chain(void *src) +{ + FlushCache(0); + D1_QWC = 0; + D1_TADR = (uint32)src & 0x0FFFFFFF; + D1_CHCR = 1<<0 | 1<<2 | 1<<6 | 1<<8; + waitDMA(&D1_CHCR); +} + + +GsCrtState gsCrtState; + +int psmsizemap[64] = { + 4, // GS_PSMCT32 + 4, // GS_PSMCT24 + 2, // GS_PSMCT16 + 0, 0, 0, 0, 0, 0, 0, + 2, // GS_PSMCT16S + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, // GS_PSMZ32 + 4, // GS_PSMZ24 + 2, // GS_PSMZ16 + 2, // GS_PSMZ16S + 0, 0, 0, 0, 0 +}; + +void +GsResetCrt(uchar inter, uchar mode, uchar ff) +{ + gsCrtState.inter = inter; + gsCrtState.mode = mode; + gsCrtState.ff = ff; + GS_CSR = 1 << GS_CSR_RESET_O; + __asm__("sync.p; nop"); + GsPutIMR(0xff00); + SetGsCrt(gsCrtState.inter, gsCrtState.mode, gsCrtState.ff); +} + +uint gsAllocPtr = 0; + +void +GsInitDispCtx(GsDispCtx *disp, int width, int height, int psm) +{ + int magh, magv; + int dx, dy; + int dw, dh; + + dx = gsCrtState.mode == GS_NTSC ? 636 : 656; + dy = gsCrtState.mode == GS_NTSC ? 25 : 36; + magh = 2560/width - 1; + magv = 0; + dw = 2560-1; + dh = height-1; + + if(gsCrtState.inter == GS_INTERLACED){ + dy *= 2; + if(gsCrtState.ff == GS_FRAME) + dh = (dh+1)*2-1; + } + + disp->pmode.d = GS_MAKE_PMODE(0, 1, 1, 1, 0, 0x00); +// disp->bgcolor.d = 0x404040; + disp->bgcolor.d = 0x000000; + disp->dispfb1.d = 0; + disp->dispfb2.d = GS_MAKE_DISPFB(0, width/64, psm, 0, 0); + disp->display1.d = 0; + disp->display2.d = GS_MAKE_DISPLAY(dx, dy, magh, magv, dw, dh); +} + +void +GsPutDispCtx(GsDispCtx *disp) +{ + GS_PMODE = disp->pmode.d; + GS_DISPFB1 = disp->dispfb1.d; + GS_DISPLAY1 = disp->display1.d; + GS_DISPFB2 = disp->dispfb2.d; + GS_DISPLAY2 = disp->display2.d; + GS_BGCOLOR = disp->bgcolor.d; +} + +void +GsInitDrawCtx(GsDrawCtx *draw, int width, int height, int psm, int zpsm) +{ + MAKE128(draw->gifTag, 0xe, + GIF_MAKE_TAG(8, 1, 0, 0, GIF_PACKED, 1)); + draw->frame1.d = GS_MAKE_FRAME(0, width/64, psm, 0); + draw->ad_frame1 = GS_FRAME_1; + draw->frame2.d = draw->frame1.d; + draw->ad_frame2 = GS_FRAME_2; + draw->zbuf1.d = GS_MAKE_ZBUF(0, zpsm, 0); + draw->ad_zbuf1 = GS_ZBUF_1; + draw->zbuf2.d = draw->zbuf1.d; + draw->ad_zbuf2 = GS_ZBUF_2; + draw->xyoffset1.d = GS_MAKE_XYOFFSET(2048<<4, 2048<<4); + draw->ad_xyoffset1 = GS_XYOFFSET_1; + draw->xyoffset2.d = draw->xyoffset1.d; + draw->ad_xyoffset2 = GS_XYOFFSET_2; + draw->scissor1.d = GS_MAKE_SCISSOR(0, width-1, 0, height-1); + draw->ad_scissor1 = GS_SCISSOR_1; + draw->scissor2.d = draw->scissor1.d; + draw->ad_scissor2 = GS_SCISSOR_2; +} + +void +GsPutDrawCtx(GsDrawCtx *draw) +{ +// printquad(*(uint128*)&draw->frame1); + toGIF(draw, 9); +} + +void +GsInitCtx(GsCtx *ctx, int width, int height, int psm, int zpsm) +{ + uint fbsz, zbsz; + uint fbp, zbp; + fbsz = (width*height*psmsizemap[psm] + 2047)/2048; + zbsz = (width*height*psmsizemap[0x30|zpsm] + 2047)/2048; + gsAllocPtr = 2*fbsz + zbsz; + fbp = fbsz; + zbp = fbsz*2; + + GsInitDispCtx(&ctx->disp[0], width, height, psm); + GsInitDispCtx(&ctx->disp[1], width, height, psm); + GsInitDrawCtx(&ctx->draw[0], width, height, psm, zpsm); + GsInitDrawCtx(&ctx->draw[1], width, height, psm, zpsm); + ctx->disp[1].dispfb2.f.FBP = fbp/4; + ctx->draw[0].frame1.f.FBP = fbp/4; + ctx->draw[0].frame2.f.FBP = fbp/4; + ctx->draw[0].zbuf1.f.ZBP = zbp/4; + ctx->draw[0].zbuf2.f.ZBP = zbp/4; + ctx->draw[1].zbuf1.f.ZBP = zbp/4; + ctx->draw[1].zbuf2.f.ZBP = zbp/4; +} + +void +initrender(void) +{ + uint128 *p, tmp; + p = packetbuf; + MAKE128(tmp, 0xe, GIF_MAKE_TAG(2, 1, 0, 0, GIF_PACKED, 1)); + *p++ = tmp; + MAKE128(tmp, GS_PRMODECONT, 1); + *p++ = tmp; + MAKE128(tmp, GS_COLCLAMP, 1); + *p++ = tmp; + toGIF(packetbuf, 3); +} + +void +clearscreen(int r, int g, int b) +{ + int x, y; + uint128 *p, tmp; + p = packetbuf; + + x = (2048 + WIDTH)<<4; + y = (2048 + HEIGHT)<<4; + + MAKE128(tmp, 0xe, GIF_MAKE_TAG(5, 1, 0, 0, GIF_PACKED, 1)); + *p++ = tmp; + MAKE128(tmp, GS_TEST_1, GS_MAKE_TEST(0, 0, 0, 0, 0, 0, 1, 1)); + *p++ = tmp; + MAKE128(tmp, GS_PRIM, GS_MAKE_PRIM(GS_PRIM_SPRITE,0,0,0,0,0,0,0,0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(r, g, b, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(2048<<4, 2048<<4, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x, y, 0)); + *p++ = tmp; + toGIF(packetbuf, 6); +} + +void +drawtest(void) +{ + int x0, x1, x2, x3; + int y0, y1, y2; + uint128 *p, tmp; + int n; + + x0 = 2048<<4; + x1 = (2048 + 210)<<4; + x2 = (2048 + 430)<<4; + x3 = (2048 + 640)<<4; + y0 = 2048<<4; + y1 = (2048 + 224)<<4; + y2 = (2048 + 448)<<4; + + n = 2 + 3*7; + p = packetbuf; + MAKEQ(tmp, 0x70000000 | n+1, 0, 0, 0); + *p++ = tmp; + MAKE128(tmp, 0xe, GIF_MAKE_TAG(n, 1, 0, 0, GIF_PACKED, 1)); + *p++ = tmp; + MAKE128(tmp, GS_TEST_1, GS_MAKE_TEST(0, 0, 0, 0, 0, 0, 1, 1)); + *p++ = tmp; + MAKE128(tmp, GS_PRIM, GS_MAKE_PRIM(GS_PRIM_SPRITE,0,0,0,0,0,0,0,0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(255, 0, 0, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x0, y0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x1, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(0, 255, 0, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x1, y0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x2, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(0, 0, 255, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x2, y0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x3, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(0, 255, 255, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x0, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x1, y2, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(255, 0, 255, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x1, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x2, y2, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(255, 255, 0, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x2, y1, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x3, y2, 0)); + *p++ = tmp; + MAKE128(tmp, GS_RGBAQ, GS_MAKE_RGBAQ(255, 255, 255, 0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ((2048+20)<<4, y0, 0)); + *p++ = tmp; + MAKE128(tmp, GS_XYZ2, GS_MAKE_XYZ(x3, (2048+20)<<4, 0)); + *p++ = tmp; + toGIFchain(packetbuf); +} + +void +drawtri(void) +{ + uint128 *p, tmp; + uint32 *ip; + int nverts, n; + + nverts = 3; + n = 2*nverts; + p = packetbuf; + MAKEQ(tmp, 0x70000000 | n+1, 0, 0, 0); + *p++ = tmp; + MAKE128(tmp, (0x5<<4) | 0x1, + GIF_MAKE_TAG(nverts, 1, 1, GS_MAKE_PRIM(GS_PRIM_TRI, 1, 0, 0, 0, 0, 0, 0, 0), GIF_PACKED, 2)); + *p++ = tmp; + MAKEQ(tmp, 255, 0, 0, 0); + *p++ = tmp; + MAKEQ(tmp, (2048+85)<<4, (2048+70)<<4, 0, 0); + *p++ = tmp; + MAKEQ(tmp, 0, 255, 0, 0); + *p++ = tmp; + MAKEQ(tmp, (2048+260)<<4, (2048+200)<<4, 0, 0); + *p++ = tmp; + MAKEQ(tmp, 0, 0, 255, 0); + *p++ = tmp; + MAKEQ(tmp, (2048+180)<<4, (2048+350)<<4, 0, 0); + *p++ = tmp; + toGIFchain(packetbuf); +} + +void +printMatrix(rw::Matrix *m) +{ + rw::V3d *x = &m->right; + rw::V3d *y = &m->up; + rw::V3d *z = &m->at; + rw::V3d *w = &m->pos; + printf( + "[ [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" + " [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" + " [ %8.4f, %8.4f, %8.4f, %8.4f ]\n" + " [ %8.4f, %8.4f, %8.4f, %8.4f ] ]\n" + " %08x == flags\n", + x->x, y->x, z->x, w->x, + x->y, y->y, z->y, w->y, + x->z, y->z, z->z, w->z, + 0.0f, 0.0f, 0.0f, 1.0f, + m->flags); +} + +// This is not proper data, just for testing +void +setupLight(rw::Atomic *atomic) +{ + using namespace rw; + Matrix *lightmat; + float32 *lp; + + numLightQ = 0; + lp = (float32*)lightpacket; + + // TODO: this is the wrong matrix. we actually want to + // transform the light, not all normals. + lightmat = atomic->getFrame()->getLTM(); + *lp++ = lightmat->right.x; + *lp++ = lightmat->right.y; + *lp++ = lightmat->right.z; + *lp++ = 0.0f; + *lp++ = lightmat->up.x; + *lp++ = lightmat->up.y; + *lp++ = lightmat->up.z; + *lp++ = 0.0f; + *lp++ = lightmat->at.x; + *lp++ = lightmat->at.y; + *lp++ = lightmat->at.z; + *lp++ = 0.0f; + *lp++ = lightmat->pos.x; + *lp++ = lightmat->pos.y; + *lp++ = lightmat->pos.z; + *lp++ = 1.0f; + // TODO: make a proper light block + // ambient + *lp++ = 80.0f; + *lp++ = 80.0f; + *lp++ = 80.0f; + *lp++ = 0.0f; + // directional + *lp++ = 0.5f; + *lp++ = -0.5f; + *lp++ = -0.7071f; + *lp++ = 0.0f; + numLightQ = 6; +} + +void +setupTransform(rw::Atomic *atomic, rw::Matrix *trans) +{ + rw::Matrix::mult(trans, atomic->getFrame()->getLTM(), &camera->viewMatrix); +} + +enum { + DMAcnt = 0x10000000, + DMAref = 0x30000000, + DMAcall = 0x50000000, + DMAret = 0x60000000, + DMAend = 0x70000000, + + V4_32 = 0x6C +}; + +#define UNPACK(type, nq, offset) ((type)<<24 | (nq)<<16 | (offset)) +#define STCYCL(WL,CL) (0x01000000 | (WL)<<8 | (CL)) + +void +drawAtomic(rw::Atomic *atomic) +{ + using namespace rw; + + Matrix trans; + Geometry *geo; + ps2::ObjPipeline *pipe; + ps2::MatPipeline *matpipe; + Material *material; + uint128 tmp, *lp; + uint32 *vec; + RGBAf color; + int i; + + geo = atomic->geometry; + pipe = (ps2::ObjPipeline*)atomic->getPipeline(); + if(pipe->platform != PLATFORM_PS2) + return; + + setupLight(atomic); + setupTransform(atomic, &trans); + + curVifPtr = packetbuf; + // upload lights + MAKEQ(tmp, DMAcnt | numLightQ+8, 0, STCYCL(4,4), UNPACK(V4_32, numLightQ, 0x3d0)); + *curVifPtr++ = tmp; + for(lp = lightpacket; numLightQ--;) + *curVifPtr++ = *lp++; + + // upload transformation matrix + MAKEQ(tmp, 0, 0, STCYCL(4,4), UNPACK(V4_32, 4, 0x3f0)); + *curVifPtr++ = tmp; + vec = (uint32*)&trans.right; + MAKEQ(tmp, vec[0], vec[1], vec[2], vec[2]); + *curVifPtr++ = tmp; + vec = (uint32*)&trans.up; + MAKEQ(tmp, vec[0], vec[1], vec[2], vec[2]); + *curVifPtr++ = tmp; + vec = (uint32*)&trans.at; + MAKEQ(tmp, vec[0], vec[1], vec[2], vec[2]); + *curVifPtr++ = tmp; + vec = (uint32*)&trans.pos; + MAKEQ(tmp, vec[0], vec[1], vec[2], vec[2]); + *curVifPtr++ = tmp; + + // upload camera/screen info + MAKEQ(tmp, 0, 0, STCYCL(4,4), UNPACK(V4_32, 2, 0x3f7)); + *curVifPtr++ = tmp; + *curVifPtr++ = vuXYZScale; + *curVifPtr++ = vuXYZOffset; + + assert(geo->instData != NULL); + rw::ps2::InstanceDataHeader *instData = + (rw::ps2::InstanceDataHeader*)geo->instData; + rw::MeshHeader *meshHeader = geo->meshHeader; + rw::Mesh *mesh; + for(i = 0; i < instData->numMeshes; i++){ + material = instData->instanceMeshes[i].material; + matpipe = pipe->groupPipeline; + if(matpipe == nil) + matpipe = (ps2::MatPipeline*)material->pipeline; + if(matpipe == nil) + matpipe = ps2::defaultMatPipe; + + // call vu code + MAKEQ(tmp, DMAcall, (uint32)skinPipe, 0, 0); + *curVifPtr++ = tmp; + // unpack GIF tag, material color, surface properties + MAKEQ(tmp, DMAcnt | 3, 0, STCYCL(4,4), UNPACK(V4_32, 3, 0x3fa)); + *curVifPtr++ = tmp; + MAKE128(tmp, 0x412, + GIF_MAKE_TAG(0, 1, 1, GS_MAKE_PRIM(GS_PRIM_TRI_STRIP,1,0,0,0,0,0,0,0), GIF_PACKED, 3)); + *curVifPtr++ = tmp; + convColor(&color, &material->color); + color.alpha *= 128.0f/255.0f; + MAKEQ(tmp, *(uint32*)&color.red, *(uint32*)&color.green, + *(uint32*)&color.blue, *(uint32*)&color.alpha); + *curVifPtr++ = tmp; + MAKEQ(tmp, *(uint32*)&material->surfaceProps.ambient, + *(uint32*)&material->surfaceProps.specular, + *(uint32*)&material->surfaceProps.diffuse, + 0.0f); // extra + *curVifPtr++ = tmp; + // call geometry + MAKEQ(tmp, DMAcall, (uint32)instData->instanceMeshes[i].data, 0x03000000, 0x02000000 | matpipe->vifOffset); + *curVifPtr++ = tmp; + } + MAKEQ(tmp, DMAend, 0, 0, 0); + *curVifPtr++ = tmp; +// for(lp = packetbuf; lp < curVifPtr; lp++) +// printquad4(*lp); + toVIF1chain(packetbuf); +} + +void +beginCamera(void) +{ + uint128 *p, tmp; + float32 *f; + + p = packetbuf; + MAKE128(tmp, 0xe, GIF_MAKE_TAG(2, 1, 0, 0, GIF_PACKED, 1)); + *p++ = tmp; + MAKE128(tmp, GS_XYOFFSET_1, GS_MAKE_XYOFFSET(2048-WIDTH/2 <<4, 2048-HEIGHT/2 <<4)); + *p++ = tmp; + MAKE128(tmp, GS_TEST_1, GS_MAKE_TEST(0, 0, 0, 0, 0, 0, 1, 2)); + *p++ = tmp; + toGIF(packetbuf, 3); + f = (float32*)&vuXYZScale; + f[0] = WIDTH; + f[1] = HEIGHT; + f[2] = camera->zScale; + f[3] = 0.0f; + f = (float32*)&vuXYZOffset; + f[0] = 2048.0f; + f[1] = 2048.0f; + f[2] = camera->zShift; + f[3] = 0.0f; +} + +rw::EngineOpenParams engineOpenParams; + +void +pluginattach(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); +} + +bool32 +initrw(void) +{ + rw::version = 0x34000; + rw::platform = rw::PLATFORM_PS2; + if(!rw::Engine::init()) + return 0; + pluginattach(); + if(!rw::Engine::open(&engineOpenParams)) + return 0; + if(!rw::Engine::start()) + return 0; + rw::Texture::setLoadTextures(0); + + rw::TexDictionary::setCurrent(rw::TexDictionary::create()); + rw::Image::setSearchPath("."); + + world = rw::World::create(); + camera = rw::Camera::create(); + camera->frameBuffer = rw::Raster::create(WIDTH, HEIGHT, 0, rw::Raster::CAMERA); + camera->zBuffer = rw::Raster::create(WIDTH, HEIGHT, 0, rw::Raster::ZBUFFER); + camera->setFrame(rw::Frame::create()); + rw::V3d t = { 0.0f, 0.0f, -4.0f }; +// rw::V3d t = { 0.0f, 0.0f, -40.0f }; + camera->getFrame()->translate(&t, rw::COMBINEPOSTCONCAT); + rw::V3d axis = { 0.0f, 1.0f, 0.0f }; + camera->getFrame()->rotate(&axis, 40.0f, rw::COMBINEPOSTCONCAT); + camera->setNearPlane(0.1f); + camera->setFarPlane(450.0f); + camera->setFOV(60.0f, 4.0f/3.0f); + world->addCamera(camera); + return 1; +} + +int vsynchInt = 0; + +int +vsynch(int id) +{ + vsynchInt = 1; + frames++; + ExitHandler(); + return 0; +} + +int +main() +{ + FlushCache(0); + if(!initrw()){ + printf("init failed!\n"); + for(;;); + } + + rw::uint32 len; + rw::uint8 *data = rw::getFileContents("host:player.DFF", &len); +// rw::uint8 *data = rw::getFileContents("host:od_newscafe_dy.dff", &len); + rw::StreamMemory in; + in.open(data, len); + rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL); + rw::Clump *clump = rw::Clump::streamRead(&in); + in.close(); + rwFree(data); + + GsCtx gsCtx; + + dmaReset(); + +// GsResetCrt(GS_NONINTERLACED, GS_NTSC, 0); +// GsInitCtx(&gsCtx, 640, 224, GS_PSMCT32, GS_PSMZ32); + +// GsResetCrt(GS_INTERLACED, GS_NTSC, GS_FRAME); +// GsInitCtx(&gsCtx, 640, 224, GS_PSMCT32, GS_PSMZ32); + + GsResetCrt(GS_INTERLACED, VMODE, GS_FIELD); + GsInitCtx(&gsCtx, WIDTH, HEIGHT, GS_PSMCT32, GS_PSMZ32); + + initrender(); + + AddIntcHandler(2, vsynch, 0); + EnableIntc(2); + + GsPutDrawCtx(&gsCtx.draw[0]); + GsPutDispCtx(&gsCtx.disp[1]); + // PCSX2 needs a delay for some reason + { int i; for(i = 0; i < 1000000; i++); } + clearscreen(0x80, 0x80, 0x80); +// drawtest(); +// drawtri(); + + float angle = 40.0f; + int drawbuf = 0; + int dispbuf = 1; + for(;;){ + clearscreen(0x80, 0x80, 0x80); + + rw::V3d t = { 0.0f, 0.0f, -4.0f }; + camera->getFrame()->translate(&t, rw::COMBINEREPLACE); + rw::V3d axis = { 0.0f, 1.0f, 0.0f }; + camera->getFrame()->rotate(&axis, angle, rw::COMBINEPOSTCONCAT); + angle += 1.0f; + + camera->beginUpdate(); + beginCamera(); + FORLIST(lnk, clump->atomics) + drawAtomic(rw::Atomic::fromClump(lnk)); + camera->endUpdate(); + printf(""); + + while(vsynchInt == 0); + GsPutDrawCtx(&gsCtx.draw[drawbuf]); + GsPutDispCtx(&gsCtx.disp[dispbuf]); + drawbuf = !drawbuf; + dispbuf = !dispbuf; + vsynchInt = 0; + } +/* + camera->beginUpdate(); + beginCamera(); + FORLIST(lnk, clump->atomics) + drawAtomic(rw::Atomic::fromClump(lnk)); + camera->endUpdate(); + + printf("hello %p\n", clump); + for(;;) + printf(""); +*/ + + return 0; +} diff --git a/tools/ps2test/mem.h b/tools/ps2test/mem.h new file mode 100644 index 0000000..088c1d4 --- /dev/null +++ b/tools/ps2test/mem.h @@ -0,0 +1,95 @@ +/* FIFOs */ +#define VIF0_FIFO (*(volatile uint128*)0x10004000) +#define VIF1_FIFO (*(volatile uint128*)0x10005000) +#define GIF_FIFO (*(volatile uint128*)0x10006000) +#define IPU_out_FIFO (*(volatile uint128*)0x10007000) +#define IPU_in_FIFO (*(volatile uint128*)0x10007010) + +/* DMA channels */ +// to VIF0 +#define D0_CHCR (*(volatile uint32*)0x10008000) +#define D0_MADR (*(volatile uint32*)0x10008010) +#define D0_QWC (*(volatile uint32*)0x10008020) +#define D0_TADR (*(volatile uint32*)0x10008030) +#define D0_ASR0 (*(volatile uint32*)0x10008040) +#define D0_ASR1 (*(volatile uint32*)0x10008050) +// VIF1 +#define D1_CHCR (*(volatile uint32*)0x10009000) +#define D1_MADR (*(volatile uint32*)0x10009010) +#define D1_QWC (*(volatile uint32*)0x10009020) +#define D1_TADR (*(volatile uint32*)0x10009030) +#define D1_ASR0 (*(volatile uint32*)0x10009040) +#define D1_ASR1 (*(volatile uint32*)0x10009050) +// to GIF +#define D2_CHCR (*(volatile uint32*)0x1000a000) +#define D2_MADR (*(volatile uint32*)0x1000a010) +#define D2_QWC (*(volatile uint32*)0x1000a020) +#define D2_TADR (*(volatile uint32*)0x1000a030) +#define D2_ASR0 (*(volatile uint32*)0x1000a040) +#define D2_ASR1 (*(volatile uint32*)0x1000a050) +// fromIPU +#define D3_CHCR (*(volatile uint32*)0x1000b000) +#define D3_MADR (*(volatile uint32*)0x1000b010) +#define D3_QWC (*(volatile uint32*)0x1000b020) +// toIPU +#define D4_CHCR (*(volatile uint32*)0x1000b400) +#define D4_MADR (*(volatile uint32*)0x1000b410) +#define D4_QWC (*(volatile uint32*)0x1000b420) +#define D4_TADR (*(volatile uint32*)0x1000b430) +// from SIF0 +#define D5_CHCR (*(volatile uint32*)0x1000c000) +#define D5_MADR (*(volatile uint32*)0x1000c010) +#define D5_QWC (*(volatile uint32*)0x1000c020) +// to SIF1 +#define D6_CHCR (*(volatile uint32*)0x1000c400) +#define D6_MADR (*(volatile uint32*)0x1000c410) +#define D6_QWC (*(volatile uint32*)0x1000c420) +#define D6_TADR (*(volatile uint32*)0x1000c430) +// SIF2 +#define D7_CHCR (*(volatile uint32*)0x1000c800) +#define D7_MADR (*(volatile uint32*)0x1000c810) +#define D7_QWC (*(volatile uint32*)0x1000c820) +// fromSPR +#define D8_CHCR (*(volatile uint32*)0x1000d000) +#define D8_MADR (*(volatile uint32*)0x1000d010) +#define D8_QWC (*(volatile uint32*)0x1000d020) +#define D8_SADR (*(volatile uint32*)0x1000d080) +// toSPR +#define D9_CHCR (*(volatile uint32*)0x1000d400) +#define D9_MADR (*(volatile uint32*)0x1000d410) +#define D9_QWC (*(volatile uint32*)0x1000d420) +#define D9_TADR (*(volatile uint32*)0x1000d430) +#define D9_SADR (*(volatile uint32*)0x1000d480) + +/* DMA controller */ +#define D_CTRL (*(volatile uint32*)0x1000e000) +#define D_STAT (*(volatile uint32*)0x1000e010) +#define D_PCR (*(volatile uint32*)0x1000e020) +#define D_SQWC (*(volatile uint32*)0x1000e030) +#define D_RBSR (*(volatile uint32*)0x1000e040) +#define D_RBOR (*(volatile uint32*)0x1000e050) +#define D_STADR (*(volatile uint32*)0x1000e060) +#define D_ENABLER (*(volatile uint32*)0x1000f520) +#define D_ENABLEW (*(volatile uint32*)0x1000f590) + + +/* GS privileged registers */ +#define GS_PMODE (*(volatile uint64*)0x12000000) +#define GS_SMODE1 (*(volatile uint64*)0x12000010) +#define GS_SMODE2 (*(volatile uint64*)0x12000020) +#define GS_SRFSH (*(volatile uint64*)0x12000030) +#define GS_SYNCH1 (*(volatile uint64*)0x12000040) +#define GS_SYNCH2 (*(volatile uint64*)0x12000050) +#define GS_SYNCV (*(volatile uint64*)0x12000060) +#define GS_DISPFB1 (*(volatile uint64*)0x12000070) +#define GS_DISPLAY1 (*(volatile uint64*)0x12000080) +#define GS_DISPFB2 (*(volatile uint64*)0x12000090) +#define GS_DISPLAY2 (*(volatile uint64*)0x120000a0) +#define GS_EXTBUF (*(volatile uint64*)0x120000b0) +#define GS_EXTDATA (*(volatile uint64*)0x120000c0) +#define GS_EXTWRITE (*(volatile uint64*)0x120000d0) +#define GS_BGCOLOR (*(volatile uint64*)0x120000e0) +#define GS_CSR (*(volatile uint64*)0x12001000) +#define GS_IMR (*(volatile uint64*)0x12001010) +#define GS_BUSDIR (*(volatile uint64*)0x12001040) +#define GS_SIGLBLID (*(volatile uint64*)0x12001080) diff --git a/tools/ps2test/ps2.h b/tools/ps2test/ps2.h new file mode 100644 index 0000000..ced015a --- /dev/null +++ b/tools/ps2test/ps2.h @@ -0,0 +1,23 @@ +#include + +typedef int quad __attribute__((mode(TI))); +typedef int int128 __attribute__((mode(TI))); +typedef unsigned int uquad __attribute__((mode(TI))); +typedef unsigned int uint128 __attribute__((mode(TI))); + +#define MAKE128(RES,MSB,LSB) \ + __asm__ ( "pcpyld %0, %1, %2" : "=r" (RES) : "r" ((uint64)MSB), "r" ((uint64)LSB)) +#define UINT64(LOW,HIGH) (((uint64)HIGH)<<32 | ((uint64)LOW)) +#define MAKEQ(RES,W0,W1,W2,W3) MAKE128(RES,UINT64(W2,W3),UINT64(W0,W1)) + +#define BIT64(v,s) (((uint64)(v)) << (s)) + +#include "mem.h" +#include "gs.h" + +extern uint128 packetbuf[128]; + +void waitDMA(volatile uint32 *chcr); +void toGIF(void *src, int n); + +void drawcube(void); diff --git a/tools/ps2test/vu/defaultpipe.dsm b/tools/ps2test/vu/defaultpipe.dsm new file mode 100644 index 0000000..ae3fa39 --- /dev/null +++ b/tools/ps2test/vu/defaultpipe.dsm @@ -0,0 +1,93 @@ +.global defaultPipe + +.equ vertexTop, 0x3d0 +.equ numInAttribs, 4 +.equ numOutAttribs, 3 +.equ numOutBuf, 2 +.equ vertCount, ((vertexTop-numOutBuf)/(numInAttribs*2+numOutAttribs*numOutBuf)) +.equ offset, (vertCount*numInAttribs) +.equ outBuf1, (2*offset) +.equ outSize, ((vertexTop-outBuf1-2)/2) +.equ outBuf2, (outBuf1+outSize) + +.equ lightMat, 0x3d0 +.equ ambientLight, 0x3d4 +.equ lightDir, 0x3d5 + +.equ matrix, 0x3f0 +.equ XYZScale, 0x3f7 +.equ XYZOffset, 0x3f8 +.equ gifTag, 0x3fa +.equ matColor, 0x3fb +.equ surfProps, 0x3fc + + +.balign 16,0 +defaultPipe: +DMAret * +MPG 0, * +.vu +Start: +#include "setup_persp.vu" +Cnt: + NOP XTOP VI02 ; input pointer + NOP LQ VF01, gifTag(VI00) + NOP XITOP VI01 ; vertex count + NOP IADDIU VI05, VI00, 0x4000 + NOP IADD VI05, VI05, VI05 + NOP IOR VI05, VI05, VI01 + NOP SQ VF01, 0(VI12) + NOP ISW.x VI05, 0(VI12) + NOP IADDIU VI03, VI12, 1 ; output pointer + NOP LQ VF18, lightMat(VI00) + NOP LQ VF19, lightMat+1(VI00) + NOP LQ VF20, lightMat+2(VI00) + +Loop: + NOP LQI VF01, (VI02++) ; vertex + NOP LQI VF02, (VI02++) ; UV + NOP LQI VF03, (VI02++) ; color + NOP LQI VF04, (VI02++) ; normal + + MULAw.xyzw ACC, VF31, VF00w NOP ; transform vertex + MADDAx.xyw ACC, VF28, VF01x NOP + MADDAy.xyw ACC, VF29, VF01y NOP + MADDz.xyzw VF01, VF30, VF01z NOP + ITOF0 VF03, VF03 NOP + ITOF0[I] VF04, VF04 LOI 0.0078125 ; - normal scale + NOP NOP + NOP DIV Q, VF00w, VF01w + NOP WAITQ + MULq VF01, VF01, Q NOP ; perspective division + MULi VF04, VF04, I NOP ; scale normal + NOP MR32.z VF02, VF00 + NOP NOP + SUB.w VF01, VF01, VF01 NOP + MULAx.xyz ACC, VF18, VF04x NOP ; transform normal + MADDAy.xyz ACC, VF19, VF04y NOP + MADDz.xyz VF04, VF20, VF04z NOP + ADD.xyz VF01, VF01, VF25 NOP + MULq VF02, VF02, Q NOP + NOP NOP + FTOI0 VF03, VF03 NOP + FTOI4 VF01, VF01 NOP + NOP SQ VF04, -2(VI02) ; store normal + NOP IADDI VI01, VI01, -1 + NOP SQI VF02, (VI03++) ; STQ + NOP SQI VF03, (VI03++) ; color + NOP SQI VF01, (VI03++) ; vertex + NOP IBNE VI01, VI00, Loop + NOP NOP + +#include "light.vu" + + NOP XGKICK VI12 + NOP IADD VI15,VI00,VI12 + NOP IADD VI12,VI00,VI13 + NOP[E] IADD VI13,VI00,VI15 + NOP NOP + NOP B Cnt + NOP NOP + +.EndMPG +.EndDmaData diff --git a/tools/ps2test/vu/light.vu b/tools/ps2test/vu/light.vu new file mode 100644 index 0000000..8f7a31c --- /dev/null +++ b/tools/ps2test/vu/light.vu @@ -0,0 +1,94 @@ +; Ambient light: + NOP LQ VF26, ambientLight(VI00) + NOP XITOP VI01 + NOP IADDIU VI03, VI12, 2 +Ambloop: + NOP LQ VF03, 0(VI03) ; output color + NOP NOP + NOP NOP + NOP NOP + ITOF0 VF03, VF03 NOP + NOP NOP + NOP NOP + NOP NOP + ADD.xyz VF03, VF03, VF26 NOP + NOP NOP + NOP NOP + NOP NOP + FTOI0 VF03, VF03 NOP + NOP IADDI VI01, VI01, -1 + NOP IADDIU VI03, VI03, numOutAttribs + NOP IBNE VI01, VI00, Ambloop + NOP SQ VF03, -numOutAttribs(VI03) +; end amblight + +; Direct Light + NOP LQ VF26, lightDir(VI00) + NOP XITOP VI01 + NOP XTOP VI02 + NOP IADDIU VI03, VI12, 2 + SUB.xyz VF26, VF00, VF26 NOP +Dirloop: + NOP LQ VF01, 3(VI02); ; normal + NOP LQ VF02, 0(VI03); ; output color + NOP NOP + NOP NOP + MUL VF03, VF01, VF26 NOP + ITOF0 VF02, VF02 NOP + NOP NOP + NOP NOP + ADDy.x VF03, VF03, VF03y NOP + NOP NOP + NOP NOP + NOP NOP + ADDz.x VF03, VF03, VF03z NOP + NOP NOP + NOP NOP + NOP NOP + MAX.x VF03, VF00, VF03 NOP ; clamp to 0 + NOP[I] LOI 255 + NOP NOP + NOP NOP + MULi.x VF03, VF03, I NOP + NOP NOP + NOP NOP + NOP NOP + ADDx.xyz VF02, VF02, VF03x NOP + NOP NOP + NOP NOP + NOP NOP + FTOI0 VF02, VF02 NOP + NOP IADDI VI01, VI01, -1 + NOP IADDIU VI02, VI02, numInAttribs + NOP IADDIU VI03, VI03, numOutAttribs + NOP IBNE VI01, VI00, Dirloop + NOP SQ VF02, -numOutAttribs(VI03) +; end dirlight + +; Material color and clamp + NOP LQ VF27, matColor(VI00) + NOP XITOP VI01 + NOP IADDIU VI03, VI12, 2 +Colorloop: + NOP LQ VF03, 0(VI03) + NOP NOP + NOP NOP + NOP NOP + ITOF0 VF03, VF03 NOP + NOP NOP + NOP NOP + NOP NOP + MUL VF03, VF03, VF27 NOP + NOP[I] LOI 255 + NOP NOP + NOP NOP + MINIi VF03, VF03, I NOP + NOP NOP + NOP NOP + NOP NOP + FTOI0 VF03, VF03 NOP + NOP IADDI VI01, VI01, -1 + NOP IADDIU VI03, VI03, numOutAttribs + NOP IBNE VI01, VI00, Colorloop + NOP SQ VF03, -numOutAttribs(VI03) +; end material color diff --git a/tools/ps2test/vu/setup_persp.vu b/tools/ps2test/vu/setup_persp.vu new file mode 100644 index 0000000..1d6a1fb --- /dev/null +++ b/tools/ps2test/vu/setup_persp.vu @@ -0,0 +1,39 @@ +/* This is the the projection matrix we start with: + * 1/2w 0 ox/2w + 1/2 -ox/2w + * 0 -1/2h -oy/2h + 1/2 oy/2h + * 0 0 1 0 + * 0 0 1 0 + * To get rid of the +1/2 in the combined matrix we + * subtract the z-row/2 from the x- and y-rows. + * + * The z-row is then set to [0 0 0 1] such that multiplication + * by XYZscale gives [0 0 0 zScale]. After perspective division + * and addition of XYZoffset we then get zScale/w + zShift for z. + * + * XYZScale scales xy to the resolution and z by zScale. + * XYZOffset translates xy to the GS coordinate system (where + * [2048, 2048] is the center of the frame buffer) and add zShift to z. + */ + +; constant: +; VF28-VF31 transformation matrix +; VF25 XYZ offset + + + SUB.z VF28, VF28, VF28 LOI 0.5 ; right.z = 0 + SUB.z VF29, VF29, VF29 LQ VF28, matrix(VI00) ; up.z = 0 - load matrix + SUB.z VF30, VF30, VF30 LQ VF29, matrix+1(VI00) ; at.z = 0 - load matrix + ADDw.z VF31, VF00, VF00 LQ VF30, matrix+2(VI00) ; at.z = 1 - load matrix + NOP LQ VF31, matrix+3(VI00) ; - load matrix + MULi.w VF20, VF28, I LQ.xyz VF01, XYZScale(VI00) ; fix matrix - load scale + MULi.w VF21, VF29, I NOP ; fix matrix + MULi.w VF22, VF30, I NOP ; fix matrix + MULi.w VF23, VF31, I NOP ; fix matrix + SUBw.xy VF28, VF28, VF20 NOP ; fix matrix + SUBw.xy VF29, VF29, VF21 NOP ; fix matrix + SUBw.xy VF30, VF30, VF22 NOP ; fix matrix + SUBw.xy VF31, VF31, VF23 NOP ; fix matrix + MUL.xy VF28, VF28, VF01 LQ.xyz VF25, XYZOffset(VI00) ; scale matrix + MUL.xy VF29, VF29, VF01 IADDIU VI12, VI00, outBuf1 ; scale matrix + MUL.xy VF30, VF30, VF01 IADDIU VI13, VI00, outBuf2 ; scale matrix + MUL.xyz VF31, VF31, VF01 NOP ; scale matrix diff --git a/tools/ps2test/vu/skinpipe.dsm b/tools/ps2test/vu/skinpipe.dsm new file mode 100644 index 0000000..18536cd --- /dev/null +++ b/tools/ps2test/vu/skinpipe.dsm @@ -0,0 +1,94 @@ +.global skinPipe + +.equ vertexTop, 0x2d0 +.equ numInAttribs, 5 +.equ numOutAttribs, 3 +.equ numOutBuf, 2 +.equ vertCount, ((vertexTop-numOutBuf)/(numInAttribs*2+numOutAttribs*numOutBuf)) +.equ offset, (vertCount*numInAttribs) +.equ outBuf1, (2*offset) +.equ outSize, ((vertexTop-outBuf1-2)/2) +.equ outBuf2, (outBuf1+outSize) + +.equ lightMat, 0x3d0 +.equ ambientLight, 0x3d4 +.equ lightDir, 0x3d5 + +.equ matrix, 0x3f0 +.equ XYZScale, 0x3f7 +.equ XYZOffset, 0x3f8 +.equ gifTag, 0x3fa +.equ matColor, 0x3fb +.equ surfProps, 0x3fc + + +.balign 16,0 +skinPipe: +DMAret * +MPG 0, * +.vu +Start: +#include "setup_persp.vu" +Cnt: + NOP XTOP VI02 ; input pointer + NOP LQ VF01, gifTag(VI00) + NOP XITOP VI01 ; vertex count + NOP IADDIU VI05, VI00, 0x4000 + NOP IADD VI05, VI05, VI05 + NOP IOR VI05, VI05, VI01 + NOP SQ VF01, 0(VI12) + NOP ISW.x VI05, 0(VI12) + NOP IADDIU VI03, VI12, 1 ; output pointer + NOP LQ VF18, lightMat(VI00) + NOP LQ VF19, lightMat+1(VI00) + NOP LQ VF20, lightMat+2(VI00) + +Loop: + NOP LQI VF01, (VI02++) ; vertex + NOP LQI VF02, (VI02++) ; UV + NOP LQI VF03, (VI02++) ; color + NOP LQI VF04, (VI02++) ; normal + NOP IADDIU VI02, VI02, 1 ; skip weights + + MULAw.xyzw ACC, VF31, VF00w NOP ; transform vertex + MADDAx.xyw ACC, VF28, VF01x NOP + MADDAy.xyw ACC, VF29, VF01y NOP + MADDz.xyzw VF01, VF30, VF01z NOP + ITOF0 VF03, VF03 NOP + ITOF0[I] VF04, VF04 LOI 0.0078125 ; - normal scale + NOP NOP + NOP DIV Q, VF00w, VF01w + NOP WAITQ + MULq VF01, VF01, Q NOP ; perspective division + MULi VF04, VF04, I NOP ; scale normal + NOP MR32.z VF02, VF00 + NOP NOP + SUB.w VF01, VF01, VF01 NOP + MULAx.xyz ACC, VF18, VF04x NOP ; transform normal + MADDAy.xyz ACC, VF19, VF04y NOP + MADDz.xyz VF04, VF20, VF04z NOP + ADD.xyz VF01, VF01, VF25 NOP + MULq VF02, VF02, Q NOP + NOP NOP + FTOI0 VF03, VF03 NOP + FTOI4 VF01, VF01 NOP + NOP SQ VF04, -2(VI02) ; store normal + NOP IADDI VI01, VI01, -1 + NOP SQI VF02, (VI03++) ; STQ + NOP SQI VF03, (VI03++) ; color + NOP SQI VF01, (VI03++) ; vertex + NOP IBNE VI01, VI00, Loop + NOP NOP + +#include "light.vu" + + NOP XGKICK VI12 + NOP IADD VI15,VI00,VI12 + NOP IADD VI12,VI00,VI13 + NOP[E] IADD VI13,VI00,VI15 + NOP NOP + NOP B Cnt + NOP NOP + +.EndMPG +.EndDmaData diff --git a/tools/ska2anm/ska2anm.cpp b/tools/ska2anm/ska2anm.cpp new file mode 100644 index 0000000..900ba82 --- /dev/null +++ b/tools/ska2anm/ska2anm.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +#include +#include + +using namespace rw; + +char *argv0; + +void +usage(void) +{ + fprintf(stderr, "usage: %s in.ska [out.anm]\n", argv0); + fprintf(stderr, " or: %s in.anm [out.ska]\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + rw::Engine::init(); + rw::registerHAnimPlugin(); + rw::Engine::open(nil); + rw::Engine::start(); + + ARGBEGIN{ + case 'v': + sscanf(EARGF(usage()), "%x", &rw::version); + break; + default: + usage(); + }ARGEND; + + if(argc < 1) + usage(); + + StreamFile stream; + if(!stream.open(argv[0], "rb")){ + fprintf(stderr, "Error: couldn't open %s\n", argv[0]); + return 1; + } + + int32 firstword = stream.readU32(); + stream.seek(0, 0); + Animation *anim = nil; + if(firstword == ID_ANIMANIMATION){ + // it's an anm file + if(findChunk(&stream, ID_ANIMANIMATION, nil, nil)) + anim = Animation::streamRead(&stream); + }else{ + // it's a ska file + anim = Animation::streamReadLegacy(&stream); + } + stream.close(); + + if(anim == nil){ + fprintf(stderr, "Error: couldn't read anim file\n"); + return 1; + } + + const char *file; + if(argc > 1) + file = argv[1]; + else if(firstword == ID_ANIMANIMATION) + file = "out.ska"; + else + file = "out.anm"; + if(!stream.open(file, "wb")){ + fprintf(stderr, "Error: couldn't open %s\n", file); + return 1; + } + if(firstword == ID_ANIMANIMATION) + anim->streamWriteLegacy(&stream); + else + anim->streamWrite(&stream); + + anim->destroy(); + + return 0; +}