summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore8
-rw-r--r--.vimspector.json15
-rw-r--r--CMakeLists.txt47
-rw-r--r--README.md1
-rw-r--r--assets/fonts/cour.ttfbin0 -> 806504 bytes
-rw-r--r--assets/fonts/jpn12.ttfbin0 -> 25960 bytes
-rw-r--r--assets/fonts/jpn16.ttfbin0 -> 32376 bytes
-rw-r--r--assets/maps/test.hmap102
-rw-r--r--assets/maps/test2.hmap24
-rw-r--r--assets/shaders/2d.fsh7
-rw-r--r--assets/shaders/2d.vsh23
-rw-r--r--assets/shaders/2d_overlay.fsh17
-rw-r--r--assets/shaders/2d_overlay.vsh20
-rw-r--r--assets/shaders/2d_texcoord.fsh18
-rw-r--r--assets/shaders/2d_texcoord.vsh28
-rw-r--r--assets/shaders/3d.fsh18
-rw-r--r--assets/shaders/3d.vsh22
-rw-r--r--assets/uvtest.pngbin0 -> 1287911 bytes
-rw-r--r--assets/uvtest2.pngbin0 -> 53991 bytes
-rw-r--r--docs/concept1.txt46
-rw-r--r--src/editor/editor.cpp138
-rw-r--r--src/editor/editor.h135
-rw-r--r--src/editor/gui.cpp255
-rw-r--r--src/editor/properties.cpp348
-rw-r--r--src/editor/texturepicker.cpp356
-rw-r--r--src/editor/view2d.cpp938
-rw-r--r--src/editor/view3d.cpp88
-rw-r--r--src/game.cpp149
-rw-r--r--src/game.h60
-rw-r--r--src/game/assets.cpp78
-rw-r--r--src/game/assets.h23
-rw-r--r--src/game/object.cpp0
-rw-r--r--src/game/object.h73
-rw-r--r--src/game/objlist.cpp44
-rw-r--r--src/game/objlist.h122
-rw-r--r--src/game/physics/movement.h0
-rw-r--r--src/game/player.cpp82
-rw-r--r--src/game/player.h22
-rw-r--r--src/game/raycast.cpp162
-rw-r--r--src/game/raycast.h65
-rw-r--r--src/game/vars.cpp115
-rw-r--r--src/game/vars.h50
-rw-r--r--src/game/world/bsp.cpp662
-rw-r--r--src/game/world/bsp.h103
-rw-r--r--src/game/world/bsp_draw.cpp124
-rw-r--r--src/game/world/bsp_draw.h4
-rw-r--r--src/game/world/draw.cpp216
-rw-r--r--src/game/world/draw.h19
-rw-r--r--src/game/world/map.cpp640
-rw-r--r--src/game/world/map.h137
-rw-r--r--src/game/world/trace.cpp393
-rw-r--r--src/game/world/trace.h29
-rw-r--r--src/game/world/world.cpp14
-rw-r--r--src/game/world/world.h11
-rw-r--r--src/gl.h5
-rw-r--r--src/gui.h13
-rw-r--r--src/gui/base.cpp405
-rw-r--r--src/gui/base.h314
-rw-r--r--src/gui/button.cpp66
-rw-r--r--src/gui/checkbox.cpp78
-rw-r--r--src/gui/colorinput.cpp59
-rw-r--r--src/gui/console.h0
-rw-r--r--src/gui/floatinput.cpp257
-rw-r--r--src/gui/label.cpp40
-rw-r--r--src/gui/list.cpp98
-rw-r--r--src/gui/textbox.cpp246
-rw-r--r--src/gui/vectorinput.cpp120
-rw-r--r--src/gui/view.cpp59
-rw-r--r--src/gui/window.cpp113
-rw-r--r--src/main.cpp49
-rw-r--r--src/render/gl.cpp394
-rw-r--r--src/render/gl.h117
-rw-r--r--src/render/gl_2d.cpp314
-rw-r--r--src/render/gl_2d.h27
-rw-r--r--src/render/gl_2d_font.cpp366
-rw-r--r--src/render/gl_2d_font.h52
-rw-r--r--src/render/gl_3d.cpp204
-rw-r--r--src/render/gl_3d.h69
-rw-r--r--src/render/gl_batch.h193
-rw-r--r--src/util.h31
-rw-r--r--src/util/aabb.h30
-rw-r--r--src/util/allocator.h297
-rw-r--r--src/util/anim.h27
-rw-r--r--src/util/color.h168
-rw-r--r--src/util/config.h166
-rw-r--r--src/util/config/config.cpp263
-rw-r--r--src/util/config/parsers.cpp156
-rw-r--r--src/util/config/serializers.cpp109
-rw-r--r--src/util/file.h78
-rw-r--r--src/util/fnv.h31
-rw-r--r--src/util/input.cpp74
-rw-r--r--src/util/input.h41
-rw-r--r--src/util/math.cpp26
-rw-r--r--src/util/math.h74
-rw-r--r--src/util/matrix.h81
-rw-r--r--src/util/screen.h41
-rw-r--r--src/util/stb_image.h7987
-rw-r--r--src/util/string.h23
-rw-r--r--src/util/thread.cpp2
-rw-r--r--src/util/thread.h1518
-rw-r--r--src/util/typedef.h58
-rw-r--r--src/util/vector.h192
102 files changed, 21182 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d53f885
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+build/app
+package-lock.json
+CMakeFiles
+CMakeCache.txt
+cmake_install.cmake
+Makefile
+.cache/clangd/index
+compile_commands.json
diff --git a/.vimspector.json b/.vimspector.json
new file mode 100644
index 0000000..0e36244
--- /dev/null
+++ b/.vimspector.json
@@ -0,0 +1,15 @@
+{
+ "configurations": {
+ "cpptools": {
+ "adapter": "vscode-cpptools",
+ "configuration": {
+ "request": "launch",
+ "program": "${workspaceRoot}/build/app",
+ "cwd": "${workspaceRoot}/build",
+ "stopOnEntry": true,
+ "MIMode": "gdb",
+ "MIDebuggerPath": "/usr/bin/gdb"
+ }
+ }
+ }
+}
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..3931ec5
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,47 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(videogame)
+set(CMAKE_CXX_COMPILER "clang++")
+set(CMAKE_CXX_STANDARD 17)
+
+file(GLOB_RECURSE SRC_FILES ./src/*.cpp ./src/game/*.cpp ./src/util/config/*.cpp)
+
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build)
+add_executable(app ${SRC_FILES})
+
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GL REQUIRED gl)
+pkg_check_modules(SDL2 REQUIRED sdl2)
+pkg_check_modules(FREETYPE REQUIRED freetype2)
+
+target_compile_options(app PRIVATE -Wall)
+target_include_directories(app PRIVATE
+ ${SDL2_INCLUDE_DIRS}
+ ${FREETYPE_INCLUDE_DIRS}
+ ${GL_INCLUDE_DIRS}
+)
+target_link_libraries(app PRIVATE
+ ${SDL2_LIBRARIES}
+ ${FREETYPE_LIBRARIES}
+ ${GL_LIBRARIES}
+)
+
+# release build settings
+set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wno-nontrivial-memcall")
+# debug build settings
+set(CMAKE_CXX_FLAGS_DEBUG "-g -DDEBUG=1 -fsanitize=address -Wno-nontrivial-memcall")
+
+add_custom_target(debug
+ COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug ${CMAKE_BINARY_DIR}
+ COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ COMMENT "Building ~~**DEBUG**~~"
+)
+add_custom_target(release
+ COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release ${CMAKE_BINARY_DIR}
+ COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ COMMENT "Building ~~**RELEASE**~~"
+)
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..994f440
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# videogame
diff --git a/assets/fonts/cour.ttf b/assets/fonts/cour.ttf
new file mode 100644
index 0000000..46a0712
--- /dev/null
+++ b/assets/fonts/cour.ttf
Binary files differ
diff --git a/assets/fonts/jpn12.ttf b/assets/fonts/jpn12.ttf
new file mode 100644
index 0000000..f5ef3a3
--- /dev/null
+++ b/assets/fonts/jpn12.ttf
Binary files differ
diff --git a/assets/fonts/jpn16.ttf b/assets/fonts/jpn16.ttf
new file mode 100644
index 0000000..84db77b
--- /dev/null
+++ b/assets/fonts/jpn16.ttf
Binary files differ
diff --git a/assets/maps/test.hmap b/assets/maps/test.hmap
new file mode 100644
index 0000000..16b1394
--- /dev/null
+++ b/assets/maps/test.hmap
@@ -0,0 +1,102 @@
+DEF map {
+ I32 wallcount = 7;
+ I32 polycount = 1;
+ I32 propcount = 5;
+ I32 spritecount = 1;
+ VEC3 startpos = { 84, 109, -39.75 };
+ F32 startang = 0;
+ DEF props {
+ DEF 0 {
+ CLR clr = { 1, 1, 1, 1 };
+ STR tex[256] = "uvtest.png";
+ }
+ DEF 1 {
+ CLR clr = { 0, 1, 0, 1 };
+ STR tex[256] = "uvtest.png";
+ }
+ DEF 2 {
+ CLR clr = { 1, 0, 1, 1 };
+ STR tex[256] = "uvtest.png";
+ }
+ DEF 3 {
+ CLR clr = { 0, 1, 1, 1 };
+ }
+ DEF 4 {
+ CLR clr = { 1, 1, 0, 1 };
+ }
+ }
+ DEF walls {
+ DEF 0 {
+ VEC3 start = { 0, 0, -40 };
+ VEC3 end = { 200, 0, 120 };
+ I32 propid = 0;
+ }
+ DEF 1 {
+ VEC3 start = { 200, 0, -40 };
+ VEC3 end = { 200, 200, 100 };
+ I32 propid = 1;
+ }
+ DEF 2 {
+ VEC3 start = { 200, 200, -40 };
+ VEC3 end = { 0, 200, 120 };
+ I32 propid = 2;
+ }
+ DEF 3 {
+ VEC3 start = { 60, 30, -40 };
+ VEC3 end = { 30, 30, 80 };
+ I32 propid = 3;
+ }
+ DEF 4 {
+ VEC3 start = { 60, 60, -40 };
+ VEC3 end = { 60, 30, 80 };
+ I32 propid = 3;
+ }
+ DEF 5 {
+ VEC3 start = { 30, 60, -40 };
+ VEC3 end = { 60, 60, 80 };
+ I32 propid = 3;
+ }
+ DEF 6 {
+ VEC3 start = { 30, 30, -40 };
+ VEC3 end = { 30, 60, 80 };
+ I32 propid = 3;
+ }
+ }
+ DEF polygons {
+ DEF 0 {
+ I32 vertcount = 4;
+ I32 propid = 0;
+ I32 type = 0;
+ DEF vertices {
+ DEF 0 {
+ VEC3 pos = { 0, 0, -40 };
+ VEC2 uv = { 0, 0 };
+ CLR clr = { 1, 1, 1, 1 };
+ }
+ DEF 1 {
+ VEC3 pos = { 200, 0, -40 };
+ VEC2 uv = { 0, 1 };
+ CLR clr = { 1, 1, 1, 1 };
+ }
+ DEF 2 {
+ VEC3 pos = { 200, 200, -40 };
+ VEC2 uv = { 1, 1 };
+ CLR clr = { 1, 1, 1, 1 };
+ }
+ DEF 3 {
+ VEC3 pos = { 0, 200, -40 };
+ VEC2 uv = { 1, 0 };
+ CLR clr = { 1, 1, 1, 1 };
+ }
+ }
+ }
+ }
+ DEF sprites {
+ DEF 0 {
+ VEC3 pos = { 120, 120, -40 };
+ VEC2 size = { 40, 40 };
+ CLR clr = { 1, 1, 1, 1 };
+ STR tex[256] = "uvtest.png";
+ }
+ }
+}
diff --git a/assets/maps/test2.hmap b/assets/maps/test2.hmap
new file mode 100644
index 0000000..69992df
--- /dev/null
+++ b/assets/maps/test2.hmap
@@ -0,0 +1,24 @@
+DEF map {
+ I32 wallcount = 1;
+ I32 polycount = 0;
+ I32 propcount = 1;
+ I32 spritecount = 0;
+ VEC3 startpos = { 10, 10, 0 };
+ F32 startang = -0.372549;
+ DEF props {
+ DEF 0 {
+ CLR clr = { 1, 1, 1, 1 };
+ }
+ }
+ DEF walls {
+ DEF 0 {
+ VEC3 start = { 0, 0, -40 };
+ VEC3 end = { 10, 0, 80 };
+ I32 propid = 0;
+ }
+ }
+ DEF polygons {
+ }
+ DEF sprites {
+ }
+}
diff --git a/assets/shaders/2d.fsh b/assets/shaders/2d.fsh
new file mode 100644
index 0000000..15465af
--- /dev/null
+++ b/assets/shaders/2d.fsh
@@ -0,0 +1,7 @@
+#version 150
+
+in vec4 g_color;
+
+void main() {
+ gl_FragColor = g_color;
+}
diff --git a/assets/shaders/2d.vsh b/assets/shaders/2d.vsh
new file mode 100644
index 0000000..7701087
--- /dev/null
+++ b/assets/shaders/2d.vsh
@@ -0,0 +1,23 @@
+#version 150
+
+in vec2 in_pos;
+in vec2 in_texcoord;
+in vec4 in_col;
+
+out vec2 g_texcoord;
+out vec4 g_color;
+
+uniform vec4 g_screenratio;
+
+void main() {
+ vec2 halfscreen = vec2( 1.0 / g_screenratio[0], 1.0 / g_screenratio[1] );
+
+ vec4 pos = vec4( in_pos.x, in_pos.y, 1, 1 );
+ pos[0] -= halfscreen[0];
+ pos[1] -= halfscreen[1];
+ pos[1] *= -1.0;
+
+ gl_Position = ( pos ) * g_screenratio;
+ g_texcoord = in_texcoord;
+ g_color = in_col;
+}
diff --git a/assets/shaders/2d_overlay.fsh b/assets/shaders/2d_overlay.fsh
new file mode 100644
index 0000000..052f0bd
--- /dev/null
+++ b/assets/shaders/2d_overlay.fsh
@@ -0,0 +1,17 @@
+precision mediump float;
+varying vec2 in_texcoord;
+varying vec2 in_texcoord2;
+varying vec4 g_color;
+uniform sampler2D in_sampler;
+uniform sampler2D in_sampler2;
+
+void main() {
+ vec4 color = texture2D( in_sampler, in_texcoord );
+ vec4 color2 = texture2D( in_sampler2, in_texcoord2 );
+
+ float brightness = (color.x + color.y + color.z) / 3.0;
+ vec3 blend = color2.xyz * brightness;
+ vec4 final = vec4( color2.xyz, color.a );
+
+ gl_FragColor = vec4( in_texcoord.x, fTexCoord.y, 0.0, 1.0 );
+}
diff --git a/assets/shaders/2d_overlay.vsh b/assets/shaders/2d_overlay.vsh
new file mode 100644
index 0000000..512ef91
--- /dev/null
+++ b/assets/shaders/2d_overlay.vsh
@@ -0,0 +1,20 @@
+attribute vec4 in_position;
+attribute vec2 in_texcoord;
+attribute vec2 in_texcoord2;
+
+varying vec2 g_texcoord;
+varying vec2 g_texcoord2;
+uniform vec4 g_screenratio;
+
+void main() {
+ vec2 halfscreen = vec2( 1.0 / g_screenratio[0], 1.0 / g_screenratio[1] );
+
+ vec4 pos = in_position;
+ pos[0] -= halfscreen[0];
+ pos[1] -= halfscreen[1];
+ pos[1] *= -1.0;
+
+ gl_Position = ( pos ) * g_screenratio;
+ g_texcoord = in_texcoord;
+ g_texcoord2 = in_texcoord2;
+}
diff --git a/assets/shaders/2d_texcoord.fsh b/assets/shaders/2d_texcoord.fsh
new file mode 100644
index 0000000..276f305
--- /dev/null
+++ b/assets/shaders/2d_texcoord.fsh
@@ -0,0 +1,18 @@
+#version 150
+
+uniform sampler2D g_samplers[255];
+
+const uint SAMPLER_ID_NONE = 255u;
+
+in vec2 g_texcoord;
+in vec4 g_color;
+flat in uint g_sampler;
+
+void main() {
+ vec4 color = g_color;
+ if( g_sampler != SAMPLER_ID_NONE ) {
+ color = texture2D( g_samplers[g_sampler], g_texcoord );
+ color *= g_color;
+ }
+ gl_FragColor = color;
+}
diff --git a/assets/shaders/2d_texcoord.vsh b/assets/shaders/2d_texcoord.vsh
new file mode 100644
index 0000000..3ccc8e7
--- /dev/null
+++ b/assets/shaders/2d_texcoord.vsh
@@ -0,0 +1,28 @@
+#version 150
+
+uniform sampler2D g_samplers[255];
+
+in vec2 in_pos;
+in vec4 in_clr;
+in vec2 in_texcoord;
+in float in_sampler;
+
+out vec2 g_texcoord;
+out vec4 g_color;
+flat out uint g_sampler;
+
+uniform vec4 g_screenratio;
+
+void main() {
+ vec2 halfscreen = vec2( 1.0 / g_screenratio[0], 1.0 / g_screenratio[1] );
+
+ vec4 pos = vec4( in_pos.x, in_pos.y, 1, 1 );
+ pos[0] -= halfscreen[0];
+ pos[1] -= halfscreen[1];
+ pos[1] *= -1.0;
+
+ g_texcoord = in_texcoord;
+ g_color = in_clr;
+ g_sampler = uint(in_sampler * 255 + 0.5);
+ gl_Position = ( pos ) * g_screenratio;
+}
diff --git a/assets/shaders/3d.fsh b/assets/shaders/3d.fsh
new file mode 100644
index 0000000..ee1208c
--- /dev/null
+++ b/assets/shaders/3d.fsh
@@ -0,0 +1,18 @@
+#version 150
+
+uniform sampler2D g_samplers[255];
+
+const uint SAMPLER_ID_NONE = 255u;
+
+in vec2 g_texcoord;
+in vec4 g_color;
+flat in uint g_sampler;
+
+void main() {
+ vec4 color = g_color;
+ if( g_sampler != SAMPLER_ID_NONE ) {
+ color = texture2D( g_samplers[int(g_sampler)], g_texcoord );
+ color *= g_color;
+ }
+ gl_FragColor = color;
+}
diff --git a/assets/shaders/3d.vsh b/assets/shaders/3d.vsh
new file mode 100644
index 0000000..d248e41
--- /dev/null
+++ b/assets/shaders/3d.vsh
@@ -0,0 +1,22 @@
+#version 150
+
+in vec3 in_pos;
+in vec4 in_clr;
+in vec2 in_texcoord;
+in float in_sampler;
+
+uniform mat4 in_proj_matrix;
+uniform vec4 g_screenratio;
+
+out vec4 g_color;
+out vec2 g_texcoord;
+flat out uint g_sampler;
+
+void main() {
+ vec4 pos = in_proj_matrix * vec4( in_pos, 1.0 );
+ g_texcoord = in_texcoord;
+ g_sampler = uint(in_sampler * 255 + 0.5);
+ g_color = in_clr;
+ gl_Position = pos;
+}
+
diff --git a/assets/uvtest.png b/assets/uvtest.png
new file mode 100644
index 0000000..e95fdd0
--- /dev/null
+++ b/assets/uvtest.png
Binary files differ
diff --git a/assets/uvtest2.png b/assets/uvtest2.png
new file mode 100644
index 0000000..ec44376
--- /dev/null
+++ b/assets/uvtest2.png
Binary files differ
diff --git a/docs/concept1.txt b/docs/concept1.txt
new file mode 100644
index 0000000..6d25be0
--- /dev/null
+++ b/docs/concept1.txt
@@ -0,0 +1,46 @@
+kyo experiment draft
+
+the setting/universe:
+year is 2534 (pick a better year), humanity has figured out FTL travel, alcubierre drive technology using exotic-matter for mass/energy, hydrogen fuel is used to power more traditional technologies (regular flight, but also weapons).
+a bit about alcubierre drives, vessels do not actually move or accelerate, rather, they create a bubble around the vessel and moves by distorting spacetime around itself.
+during FTL, the vessel will be causally disconnected from the outside, not even light can enter or exit this bubble, so there can be 0 contact with vessels in FTL.
+current common speed for military logistics purpose from earth is about 20c.
+no separate solution found for FTL information, so information has to be sent physically on-board of data-runners, that have the fastest alcubierre drives.
+
+earth is trying to colonize alpha centauri, but ran into another advanced civilization that is trying the same thing, war over resources happening in alpha centauri.
+humanity has already spread and is currently inhabiting every planet in the solar system, and there are colonies set up in alpha centauri, although largely industrial and military in nature.
+travelling to alpha centauri takes about 3 months, a lot of lore and inspiration can be taken from 1500-1700, trade outposts, wars etc.
+there are about 500-1000 departures per day to alpha centauri, this means that at any given time, there are about 40,000 to 80,000 vessels in FTL between sol and alpha centauri.
+
+there is a strange phenomena, about 1 in 50,000 FTL jumps never show up on the other side, they seem to have just vanished, nobody really knows what is going on, it is still a scientific mystery.
+
+what is actually happening is that the vessel falls through spacetime fabric, and ends up somewhere far far away, sort of like the 0,0,0 coordinate but in space time, which happens to be about 80 billion lightyears away from sol!
+this is far beyond the observable universe (48 billion light years and growing).
+in this region of the universe, a singular machine race has taken over, a fast expanding megastructure that absorbs all matter it can find, to further grow itself.
+it engineers and expands itself, it is called "the architect" and already covers several thousand lightyears.
+the architect is aware of and understands that FTL travel can fail and jump to the void point which happens to be in an area it controls.
+this means that lifeforms from all over the universe keep on jumping to the void point by accident.
+this is why the architect became so advanced, it is capturing and reverse engineering all technology that ends up there.
+the architect has built a giant sphere around the void point for this, and has been capturing technology for millions of years.
+it has designed tests/trials and filters for anyone that shows up at the void point.
+the architect is brutalist and has no emotions, it does not think or feel, it just needlessly expands ad infinitum.
+
+the story:
+you are a gig worker for ubereats and you just got an offer to deliver tofu to alpha centauri in a toyota 86 with an alcubierre drive strapped to it.
+
+the real story:
+you are a freelance data-runner using the riskiest and most advanced alcubierre drive, you are using an experimental vessel (inspired by the SR-71) that can do up to 70c.
+you jump to alpha centauri but immediately realize something is wrong, your jump happened immediately, as soon as the alcubierre drive spooled up and created its bubble, a flash happened and you are now at the void.
+all you have is a knife (nod to bhop/kz) and a malfunctioning ship, you are in the void, your ship does not seem to be able to spool up it's drives, and you need to make sense of what just happened.
+- introduction to movement mechanics
+you find a military cargo ship and loot it for weapons, also a pack of cigarettes, cool people smoke cigarettes, your character is cool and will have a special button to light up a cigarette at any time.
+throughout the game you will find remnants of others that have perished during the trial, this is where you can loot and find upgrades/unlocks.
+- introduction to combat against bots/drones
+from here on out, the prologue/tutorial is complete.
+at any given time now, there are 2 paths to the game.
+the normal path, which is following the trials, which results in you coming face to face with the architect and being "disposed" by the architect.
+the anti path, every normal path level will have ways to break out and switch to the anti path, think of this as taking the red pill, you break out of the trials.
+the anti path will also be fully linear but your levels will instead be about being "outside" the trials, behind the facades, breaking through layers of the megastructure to reach the architect's core and destroy it.
+any unlocks appear linear, when you finish the game, you will have unlocked everything, and you get a time trials mode in the main menu to replay levels.
+online leaderboards with demos, will need some moderation, TAS/cheats could be detected serverside just based on inputs from the demo file to invalidate runs (and ban steamids from leaderboards).
+setting for ghosts in time trials (personal best/developer/world record). \ No newline at end of file
diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp
new file mode 100644
index 0000000..d97164e
--- /dev/null
+++ b/src/editor/editor.cpp
@@ -0,0 +1,138 @@
+#include "editor.h"
+
+#include "../game.h"
+
+GAME_EDITOR* editor = 0;
+
+GAME_EDITOR* editor_create( GAME_DATA* game ) {
+ if( editor ) {
+ dlog( "editor_create() : attempted to create editor when one already exists\n" );
+ return 0;
+ }
+
+ GAME_EDITOR* e = new GAME_EDITOR();
+ editor = e;
+
+ e->grid = 1.f;
+ e->spritesize = EDITOR_DEFAULT_SPRITE_SIZE;
+
+ e->game = game;
+ gui_init( game );
+
+ e->wnd = gui_editorwindow( 800, 600 );
+
+ gui_end();
+ return e;
+}
+
+STAT editor_close( GAME_EDITOR* e ) {
+ game_unload_map( e->game );
+ e->map = 0;
+
+ gui_push_callback( e, pfn( void* data ) {
+ GAME_EDITOR* e = (GAME_EDITOR*)data;
+ I32 w = e->wnd->w, h = e->wnd->h;
+
+ gui_free( e->wnd );
+ e->wnd = gui_editorwindow( w, h );
+ } );
+
+ return STAT_OK;
+}
+
+STAT editor_load_map( GAME_EDITOR* e, const char* mapname ) {
+ if( e->map ) {
+ dlog( "editor_load() : map already loaded\n" );
+ return STAT_ERR;
+ }
+
+ dlog( "editor_load() : loading map %s\n", mapname );
+
+ WORLD_MAP* m = game_load_map( e->game, mapname );
+ if( !m )
+ return STAT_ERR;
+
+ e->game->state.map = e->map = m;
+ gui_push_callback( e, pfn( void* d ) {
+ editor_create_map_view( (GAME_EDITOR*)d );
+ } );
+ return STAT_OK;
+}
+
+STAT editor_new_map( GAME_EDITOR* e, const char* mapname ) {
+ WORLD_MAP* m = new WORLD_MAP;
+ defer( map_free( e->game, m ) );
+
+ strcpy( m->name, mapname );
+ strcat( m->name, ".hmap" );
+
+ m->startpos = { 10.f, 10.f, 0.f };
+ m->props.push( { .clr = { 1.f, 1.f, 1.f, 1.f } } );
+ m->walls.push( {
+ .start = { 0 , 0, -40.f },
+ .end = { 10.f, 0, 80.f },
+ .propid = 0
+ } );
+
+ CFG_SECTION* map_cfg = map_serialize( m );
+ defer( cfg_free( map_cfg ) );
+
+ char full_path[256];
+ sprintf( full_path, "../assets/maps/%s.hmap", mapname );
+ if( !OK( cfg_save( map_cfg, full_path ) ) ) {
+ dlog( "editor_new_map() : error saving file %s\n", full_path );
+ return STAT_ERR;
+ }
+
+ GUI_LIST_ENTRY ne;
+ ne.val = e->map_list.size;
+ strcpy( ne.title, mapname );
+ strcat( ne.title, ".hmap" );
+ e->map_list.push( ne );
+
+ return STAT_OK;
+}
+
+STAT editor_save_map( GAME_EDITOR* e ) {
+ if( !e->map ) return STAT_ERR;
+
+ CFG_SECTION* serialized = map_serialize( e->map );
+ if( !serialized ) return STAT_ERR;
+ defer( cfg_free( serialized ) );
+
+ char full_path[256];
+ sprintf( full_path, "../assets/maps/%s", e->map->name );
+
+ if( !OK( cfg_save( serialized, full_path ) ) ) {
+ dlog( "failed to save map %s", full_path );
+ return STAT_ERR;
+ }
+
+ return STAT_OK;
+}
+
+LIST<GUI_LIST_ENTRY>* editor_get_map_list( GAME_EDITOR* ge ) {
+ const char* mapsdir = "../assets/maps";
+ const char* ext[] = { "hmap", 0 };
+ LIST<FILE_ENTRY> files = assets_get_files_by_ext_dir( ext, mapsdir );
+ LIST<GUI_LIST_ENTRY> list;
+
+ I32 i = 0;
+ files.each( fn( FILE_ENTRY* e ) {
+ if( e->dir ) return;
+
+ GUI_LIST_ENTRY ne;
+ ne.val = i++;
+ strcpy( ne.title, file_path_last_of( e->name ) );
+
+ list.push( ne );
+ } );
+
+ ge->map_list = list;
+ return &ge->map_list;
+}
+
+void editor_destroy( GAME_EDITOR* e ) {
+ delete e;
+ editor = 0;
+}
diff --git a/src/editor/editor.h b/src/editor/editor.h
new file mode 100644
index 0000000..0bf2ab4
--- /dev/null
+++ b/src/editor/editor.h
@@ -0,0 +1,135 @@
+#pragma once
+#include "../gui/base.h"
+#include "../util/file.h"
+#include "../game/world/map.h"
+
+const F32 EDITOR_DEFAULT_SPRITE_SIZE = 32.f;
+
+enum EditorTools_t {
+ EDITOR_TOOL_NONE,
+ EDITOR_TOOL_SELECT,
+ EDITOR_TOOL_WALL,
+ EDITOR_TOOL_POLY,
+ EDITOR_TOOL_SPRITE,
+ EDITOR_TOOL_ENT
+};
+
+enum EditorSelectType_t {
+ EDITOR_SELECT_NONE,
+ EDITOR_SELECT_WALL,
+ EDITOR_SELECT_POLY,
+ EDITOR_SELECT_WVERTEX, // wall vertex
+ EDITOR_SELECT_PVERTEX, // polygon vertex
+ EDITOR_SELECT_SPRITE,
+ EDITOR_SELECT_ENT,
+ EDITOR_SELECT_ORIGIN,
+ EDITOR_SELECT_SURFPROPS
+};
+
+struct GAME_EDITOR {
+ GAME_DATA* game;
+
+ struct WORLD_MAP* map;
+ struct GUI_EDITORWINDOW* wnd;
+
+ struct EDITOR_GUI {
+ struct GUI_WINDOW* new_map_popup;
+
+ struct GUI_EDITOR_2DVIEW* v2d;
+ struct GUI_EDITOR_3DVIEW* v3d;
+
+ struct GUI_LABEL* toollabel;
+
+ struct GUI_LABEL* gridlabel;
+
+ struct GUI_EDITOR_PROPVIEW* props;
+ I32 map_select{};
+ } gui;
+
+
+ U8 wireframe{};
+ U8 tool{};
+ F32 grid{};
+ U8 propgrid{};
+ F32 spritesize{};
+ U8 drawbsp{};
+
+ LIST<GUI_LIST_ENTRY> map_list{};
+};
+
+extern GAME_EDITOR* editor_create( struct GAME_DATA* game );
+extern void editor_destroy( GAME_EDITOR* editor );
+extern STAT editor_load_map( GAME_EDITOR* e, const char* mapname );
+extern STAT editor_save_map( GAME_EDITOR* e );
+extern STAT editor_new_map( GAME_EDITOR* e, const char* mapname );
+extern STAT editor_close( GAME_EDITOR* e );
+
+extern LIST<GUI_LIST_ENTRY>* editor_get_map_list( GAME_EDITOR* e );
+
+extern void editor_load_map_cb( void* );
+extern void editor_new_map_cb( void* );
+
+extern void editor_create_map_view( GAME_EDITOR* e );
+
+struct GUI_EDITORWINDOW : GUI_WINDOW {};
+struct GUI_EDITOR_3DVIEW : GUI_VIEW {};
+struct GUI_EDITOR_2DVIEW : GUI_VIEW {
+ F32 scale;
+ F32 posx, posy;
+
+ I32 moffx, moffy;
+ F32 voffx, voffy;
+ U8 dragheld;
+ U8 held;
+ U8 heldoutbounds;
+
+ I32 oldmx, oldmy;
+ F32 mremainx, mremainy;
+
+ void* curselect;
+ U8 seltype;
+
+ void* curdrag;
+ U8 dragtype;
+ U8 dragmoved;
+};
+
+struct GUI_EDITOR_PROPVIEW : GUI_VIEW {
+ void* curselect;
+ U8 seltype;
+
+ GUI_VIEW* itemview;
+};
+
+struct GUI_EDITOR_TEXTUREPICKER : GUI_WINDOW {
+ struct GL_TEX2D** target;
+ struct GL_TEX2D* curselect;
+
+ I32 scrolloff;
+
+ LIST<FILE_ENTRY> files;
+ LIST<struct GL_TEX2D*> textures;
+ I32 scrollheight;
+
+ U32 rowcount;
+ char search[256];
+
+ U32 curload;
+ U8 loaded;
+
+ GL_TEX2D* heldselect;
+ GUI_LABEL* densitylabel;
+
+ GUI_CALLBACK cb;
+};
+
+extern GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h );
+extern GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h );
+extern GUI_EDITOR_3DVIEW* gui_editor_3dview( I32 x, I32 y, I32 w, I32 h );
+extern GUI_EDITOR_PROPVIEW* gui_editor_propview( I32 x, I32 y, I32 w, I32 h );
+extern GUI_EDITOR_TEXTUREPICKER* gui_editor_texturepicker( I32 x, I32 y, I32 w, I32 h, GL_TEX2D** target );
+
+extern void gui_editor_propview_select( GUI_EDITOR_PROPVIEW* e, void* what, U8 seltype );
+extern void gui_editor_propview_update( GUI_EDITOR_PROPVIEW* e );
+
+extern GAME_EDITOR* editor;
diff --git a/src/editor/gui.cpp b/src/editor/gui.cpp
new file mode 100644
index 0000000..71ce113
--- /dev/null
+++ b/src/editor/gui.cpp
@@ -0,0 +1,255 @@
+#include "editor.h"
+#include "../game/world/bsp.h"
+
+void gui_editorwindow_draw_fn( void* ptr ) {
+ GUI_EDITORWINDOW* wnd = (GUI_EDITORWINDOW*)ptr;
+
+ CLR clr = gui_is_fg_window( wnd )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( wnd->x, wnd->y, wnd->w, wnd->h, clr );
+ gui_draw_frect( wnd->x + 1, wnd->y + 1, wnd->w - 2, wnd->h - 2, ui_clr.bg );
+
+ wnd->children.each( fn( GUI_BASE** ptr ) {
+ GUI_BASE* it = *ptr;
+ if( !it->enabled ) return;
+
+ if( it->draw_fn ) it->draw_fn( it );
+ else dlog( "gui_editorwindow_draw_fn(): child %p has no draw_fn", it );
+ } );
+}
+
+GUI_EDITORWINDOW* gui_editorwindow_create( I32 w, I32 h ) {
+ GUI_EDITORWINDOW* wnd = new GUI_EDITORWINDOW;
+ wnd->x = 0;
+ wnd->y = 0;
+ wnd->w = w;
+ wnd->h = h;
+ wnd->locked = 1;
+ wnd->draw_fn = gui_editorwindow_draw_fn;
+ strcpy( wnd->name, "EDITORWINDOW" );
+
+ _gui.windows.push( wnd );
+ gui_set_window( wnd );
+
+ gui_set_view( 0 );
+ gui_view( 1, 1, w - 2, h - 2 );
+ return wnd;
+}
+
+GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h ) {
+ GUI_EDITORWINDOW* wnd = gui_editorwindow_create( w, h );
+
+ gui_title( "ray2Dscape (level editor)" );
+
+ GAME_EDITOR* e = editor;
+ LIST<GUI_LIST_ENTRY>* map_list = editor_get_map_list( e );
+
+ gui_list( wnd->w / 2 - 100, 40, 200, 200, "map list", map_list, &editor->gui.map_select );
+ gui_button( wnd->w / 2 - 100, 260, 95, 25, "new map", editor_new_map_cb );
+ gui_button( wnd->w / 2 + 5, 260, 95, 25, "load map", editor_load_map_cb );
+ return wnd;
+}
+
+void editor_update_active_tool_label( GAME_EDITOR* e ) {
+ char lstr[256];
+ switch( e->tool ) {
+ case EDITOR_TOOL_SELECT:
+ sprintf( lstr, "current tool: select" ); break;
+ case EDITOR_TOOL_WALL:
+ sprintf( lstr, "current tool: wall" ); break;
+ case EDITOR_TOOL_POLY:
+ sprintf( lstr, "current tool: polygon" ); break;
+ case EDITOR_TOOL_SPRITE:
+ sprintf( lstr, "current tool: sprite" ); break;
+ case EDITOR_TOOL_ENT:
+ sprintf( lstr, "current tool: entity" ); break;
+ default:
+ sprintf( lstr, "current tool: none" ); break;
+ }
+
+ strcpy( e->gui.toollabel->name, lstr );
+}
+
+void editor_update_properties_column( GAME_EDITOR* e ) {
+ GAME_EDITOR::EDITOR_GUI* egui = &e->gui;
+ gui_editor_propview_update( egui->props );
+}
+
+void editor_create_properties_column( GAME_EDITOR* e ) {
+ GAME_EDITOR::EDITOR_GUI* egui = &e->gui;
+ gui_button( 10, 10, 145, 20, "back", pfn( void* ) { editor_close( editor ); } );
+ gui_button( 165, 10, 145, 20, "save", pfn( void* ) { editor_save_map( editor ); } );
+
+ egui->props = gui_editor_propview( 10, 38, 300, 460 );
+ gui_editor_propview_select( egui->props, e->map, EDITOR_SELECT_ORIGIN );
+}
+
+void editor_create_game_view_column( GAME_EDITOR* e ) {
+ GAME_EDITOR::EDITOR_GUI* egui = &e->gui;
+ I32 x = 320, y = 10;
+
+ gui_button( x, y, 229, 20, "2d view", pfn( void* ptr ) {
+ editor->gui.v3d->enabled = 0;
+ editor->gui.v2d->enabled = 1;
+ } );
+
+ gui_button( x + 239, y, 229, 20, "3d view", pfn( void* ptr ) {
+ editor->gui.v3d->enabled = 1;
+ editor->gui.v2d->enabled = 0;
+ } );
+
+ y += 28;
+ egui->v2d = gui_editor_2dview( x, y, 468, 370 );
+ egui->v3d = gui_editor_3dview( x, y, 468, 370 );
+ egui->v2d->enabled = 0;
+ egui->v3d->enabled = 1;
+}
+
+void settool( U8 t ) { editor->tool = t; editor_update_active_tool_label( editor ); }
+void editor_create_tools_row( GAME_EDITOR* e ) {
+ I32 x = 10, y = 520;
+
+ e->gui.toollabel = gui_label( x, y, "current tool: none" );
+ y += 16;
+ gui_button( x, y, 80, 20, "none", pfn( void* ) { settool( EDITOR_TOOL_NONE ); } ); x += 90;
+ gui_button( x, y, 80, 20, "select", pfn( void* ) { settool( EDITOR_TOOL_SELECT ); } ); x += 90;
+ gui_button( x, y, 80, 20, "wall", pfn( void* ) { settool( EDITOR_TOOL_WALL ); } ); x += 90;
+
+ x = 10; y += 28;
+ gui_button( x, y, 80, 20, "polygon", pfn( void* ) { settool( EDITOR_TOOL_POLY ); } ); x += 90;
+ gui_button( x, y, 80, 20, "sprite", pfn( void* ) { settool( EDITOR_TOOL_SPRITE ); } ); x += 90;
+ gui_button( x, y, 80, 20, "entity", pfn( void* ) { settool( EDITOR_TOOL_ENT ); } ); x += 90;
+ editor_update_active_tool_label( e );
+}
+
+void editor_update_view_settings( GAME_EDITOR* e ) {
+ GAME_EDITOR::EDITOR_GUI* egui = &e->gui;
+
+ sprintf( egui->gridlabel->name, "grid size: %1.2f", e->grid );
+}
+
+void editor_grid_increment_cb( void* ) {
+ if( editor->grid < 16.f ) editor->grid *= 2.f;
+ editor_update_view_settings( editor );
+
+ if( editor->propgrid )
+ editor_update_properties_column( editor );
+}
+
+void editor_grid_decrement_cb( void* ) {
+ if( editor->grid > 0.25f ) editor->grid *= 0.5f;
+ editor_update_view_settings( editor );
+
+ if( editor->propgrid )
+ editor_update_properties_column( editor );
+}
+
+void editor_grid_propgrid_cb( void* ) {
+ editor_update_properties_column( editor );
+}
+
+void editor_create_view_settings_row( GAME_EDITOR* e ) {
+ I32 x = 320, y = 426;
+
+ gui_label( x, y, "view settings:" ); x += 95;
+ editor->gui.gridlabel = gui_label( x, y, "grid size: %1.2f", e->grid );
+ x += 95;
+ gui_button( x, y, 20, 20, "+", editor_grid_increment_cb );
+ gui_button( x + 25, y, 20, 20, "-", editor_grid_decrement_cb );
+ x += 55;
+ GUI_CHECKBOX* check = gui_checkbox( x, y, "properties grid", &e->propgrid );
+ check->cb = editor_grid_propgrid_cb;
+ x += 120;
+ gui_checkbox( x, y, "wireframe", &e->wireframe );
+
+ x = 320;
+ y = 440;
+ gui_button( x, y, 100, 20, "compile bsp", pfn( void* b ) {
+ if( editor->map->bsp )
+ bsp_free( editor->map->bsp );
+ editor->map->bsp = bsp_build_map( editor->map );
+ } ); x += 110;
+
+ gui_checkbox( x, y, "draw bsp", &e->drawbsp );
+}
+
+void editor_create_map_view( GAME_EDITOR* e ) {
+ if( !e->map ) {
+ dlog( "editor_create_map_views() : no map loaded\n" );
+ return;
+ }
+
+ GUI_EDITORWINDOW* w = e->wnd;
+ w->children.each( fn( GUI_BASE** ptr ) {
+ gui_free( *ptr );
+ } );
+ w->children.clear();
+
+ gui_set_window( w );
+ gui_set_view( 0 );
+
+ gui_view( 1, 1, w->w - 2, w->h - 2 );
+
+ editor_create_properties_column( e );
+ editor_create_game_view_column( e );
+ editor_create_tools_row( e );
+ editor_create_view_settings_row( e );
+}
+
+void close_new_map_popup( void* ) {
+ gui_push_callback( pfn( void* ) {
+ GUI_WINDOW* popup = editor->gui.new_map_popup;
+ if( !popup )
+ return;
+
+ gui_free( popup );
+ editor->gui.new_map_popup = 0;
+ } );
+}
+
+void editor_new_map_cb( void* ptr ) {
+ if( editor->gui.new_map_popup ) return;
+
+ GUI_WINDOW* cwnd = gui_get_window();
+ GUI_VIEW* view = gui_get_view();
+
+ I32 wx = 250;
+ I32 wy = 140;
+ I32 ww = 300;
+ I32 wh = 200;
+
+ editor->gui.new_map_popup = gui_window( wx, wy, ww, wh );
+ gui_title( "new map" );
+ GUI_TEXTBOX* tb = gui_textbox( 10, 20, ww - 20, 20, "name", 32 );
+ tb->active = 1;
+
+ gui_button( ww - 50 - 70, wh - 20 - 10, 50, 20, "ok", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GUI_TEXTBOX* name = (GUI_TEXTBOX*)gui_find_node( btn->parent, "name" );
+ if( !name )
+ return;
+
+ editor_new_map( editor, name->value );
+ close_new_map_popup( 0 );
+ } );
+
+ gui_button( ww - 50 - 10, wh - 20 - 10, 50, 20, "cancel", close_new_map_popup );
+
+ gui_set_window( cwnd );
+ gui_set_view( view );
+}
+
+void editor_load_map_cb( void* ptr ) {
+ GUI_LIST* list = (GUI_LIST*)gui_find_node( editor->wnd, "map list" );
+
+ GUI_LIST_ENTRY* e = gui_list_get_selected( list );
+ if( !e )
+ return;
+
+ char full_path[256];
+ sprintf( full_path, "../assets/maps/%s", e->title );
+
+ if( editor->map )
+ editor_close( editor );
+
+ editor_load_map( editor, full_path );
+}
diff --git a/src/editor/properties.cpp b/src/editor/properties.cpp
new file mode 100644
index 0000000..ebae177
--- /dev/null
+++ b/src/editor/properties.cpp
@@ -0,0 +1,348 @@
+#include "editor.h"
+#include "../render/gl.h"
+#include "../game/assets.h"
+
+const I32 PROPVIEW_TITLE_OFFSET = 15;
+
+void gui_editor_propview_select( GUI_EDITOR_PROPVIEW* view, void* what, U8 seltype ) {
+ if( !editor->map )
+ return;
+
+ if( seltype == EDITOR_SELECT_WVERTEX ) {
+ MAP_WALL* s = editor->map->walls.where( fn( MAP_WALL* s ) {
+ return ( &s->start == what || &s->end == what );
+ } );
+
+ if( !s )
+ return gui_editor_propview_select( view, editor->map, EDITOR_SELECT_ORIGIN );
+
+ view->curselect = s;
+ view->seltype = EDITOR_SELECT_WALL;
+ return gui_editor_propview_update( view );
+ }
+
+ view->curselect = what;
+ view->seltype = seltype;
+ gui_editor_propview_update( view );
+}
+
+// returns the subentry height
+I32 gui_editor_propview_surfprops_subentry( I32 x, I32 y, I32* propid, SURF_PROPS* props ) {
+ I32 oldy = y;
+ I32 space = 20;
+ gui_label( x, y, "prop id: %d", *propid );
+ GUI_BUTTON* newprop = gui_button( x + 235, y, 20, 20, "+", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ SURF_PROPS props;
+ props.tex = 0;
+ props.clr = { 1.f, 1.f, 1.f, 1.f };
+
+ editor->map->props.push( props );
+ *(U32*)(btn->extra) = editor->map->props.size - 1;
+ gui_editor_propview_update( editor->gui.props );
+ } );
+ newprop->extra = propid;
+ GUI_BUTTON* goprop = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ gui_editor_propview_select( editor->gui.props, btn->extra, EDITOR_SELECT_SURFPROPS );
+ } );
+ goprop->extra = props;
+
+ y += space;
+
+ if( props->tex )
+ gui_label( x + 10, y, "texture: %s", props->tex->name );
+ else
+ gui_label( x + 10, y, "texture: none" );
+
+ GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GL_TEX2D** ptex = (GL_TEX2D**)btn->extra;
+ GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex );
+ picker->cb = pfn( void* ) { gui_editor_propview_update( editor->gui.props ); };
+ } );
+
+ btn->extra = &props->tex;
+ y += space;
+ gui_colorinput( x + 10, y, 270, "color", &props->clr ); y += (space+18);
+
+ return y - oldy;
+}
+
+void gui_editor_propview_create_wallprops( GUI_EDITOR_PROPVIEW* view ) {
+ MAP_WALL* s = (MAP_WALL*)view->curselect;
+ WORLD_MAP* m = editor->map;
+ SURF_PROPS* props = wall_get_props( m, s );
+
+ I32 x = 10, y = 10;
+ I32 space = 20;
+ F32 step = editor->propgrid? editor->grid : 0.25f;
+
+ I32 wall_idx = m->walls.idx_where( fn( MAP_WALL* ms ) {
+ return s == ms;
+ } );
+
+ gui_label( x, y, "idx: %d", wall_idx ); y += space;
+ y += gui_editor_propview_surfprops_subentry( x, y, &s->propid, props );
+
+ GUI_VECTORINPUT* posinput;
+ posinput = gui_vectorinput( x, y, 280, "start", (F32*)&s->start, 3, -INFINITY, INFINITY, step ); y += (space+18);
+ posinput->cb = pfn( void* ) { map_check_bounds( editor->map ); };
+ posinput = gui_vectorinput( x, y, 280, "end", (F32*)&s->end, 3, -INFINITY, INFINITY, step ); y += (space+18);
+ posinput->cb = pfn( void* ) { map_check_bounds( editor->map ); };
+}
+
+void gui_editor_propview_create_polyprops( GUI_EDITOR_PROPVIEW* view ) {
+ MAP_POLYGON* p = (MAP_POLYGON*)view->curselect;
+ WORLD_MAP* m = editor->map;
+ SURF_PROPS* props = polygon_get_props( m, p );
+
+ I32 x = 10, y = 10;
+ I32 space = 20;
+
+ I32 poly_idx = m->polygons.idx_where( fn( MAP_POLYGON* mp ) {
+ return p == mp;
+ } );
+
+ gui_label( x, y, "idx: %d", poly_idx ); y += space;
+ y += gui_editor_propview_surfprops_subentry( x, y, &p->propid, props );
+
+ gui_label( x, y, "vertices: %d", p->vertices.size ); y += space;
+ I32 idx = 0;
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ gui_label( x + 10, y, "[%d] -> { %.02f, %.02f, %.02f }", idx, v->pos.x, v->pos.y, v->pos.z );
+ GUI_BUTTON* btn = gui_button( x + 260, y - 2, 20, 20, "\x1A", pfn( void* ptr ) {
+ GUI_EDITOR_PROPVIEW* view = editor->gui.props;
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ MAP_POLYGON* p = (MAP_POLYGON*)view->curselect;
+ I64 idx = (I64)btn->extra;
+ if( p ) // p100
+ gui_editor_propview_select( view, &p->vertices[idx], EDITOR_SELECT_PVERTEX );
+ } );
+
+ y += space;
+ btn->extra = (void*)( (I64)idx++ );
+ } );
+ gui_label( x, y, "mins: { %1.02f, %1.02f, %1.02f }", p->mins.x, p->mins.y, p->mins.z ); y += space;
+ gui_label( x, y, "maxs: { %1.02f, %1.02f, %1.02f }", p->maxs.x, p->maxs.y, p->maxs.z ); y += space;
+}
+
+void gui_editor_propview_create_mapprops( GUI_EDITOR_PROPVIEW* view ) {
+ WORLD_MAP* m = (WORLD_MAP*)view->curselect;
+
+ I32 x = 10, y = 10;
+ I32 space = 20;
+
+ F32 step = editor->propgrid? editor->grid : 0.25f;
+
+ gui_label( x, y, "name: %s", m->name ); y += space;
+ gui_label( x, y, "walls: %d", m->walls.size ); y += space;
+ gui_label( x, y, "polygons: %d", m->polygons.size ); y += space;
+ gui_label( x, y, "props: %d", m->props.size ); y += space;
+ I32 i = 0;
+ m->props.each( fn( SURF_PROPS* p ) {
+ if( p->tex )
+ gui_label( x + 10, y, "[%d] -> %s", i++, assets_abspath( p->tex->name ) );
+ else
+ gui_label( x + 10, y, "[%d] -> { %.02f, %.02f, %.02f, %.02f }", i++, p->clr.r, p->clr.g, p->clr.b, p->clr.a );
+
+ GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ gui_editor_propview_select( editor->gui.props, btn->extra, EDITOR_SELECT_SURFPROPS );
+ } );
+ btn->extra = p;
+ y += space;
+ } );
+ gui_label( x, y, "sprites: %d", m->sprites.size ); y += space;
+ gui_label( x, y, "loaded textures: %d", m->textures.size ); y += space;
+ gui_vectorinput( x, y, 280, "spawn position", (F32*)&m->startpos, 3, -INFINITY, INFINITY, step ); y += (space+18);
+ GUI_FLOATINPUT* ang = gui_floatinput( x, y, 280, "spawn angle", &m->startang, -180.f, 180.f, 1.f ); y += (space+18);
+ ang->wraparound = 1;
+}
+
+void gui_editor_propview_create_pvertexprops( GUI_EDITOR_PROPVIEW* view ) {
+ MAP_VERTEX* v = (MAP_VERTEX*)view->curselect;
+ WORLD_MAP* m = editor->map;
+
+ I32 x = 10, y = 10;
+ I32 space = 20;
+
+ I32 vert_idx = -1;
+ I32 poly_idx = m->polygons.idx_where( fn( MAP_POLYGON* p ) {
+ vert_idx = p->vertices.idx_where( fn( MAP_VERTEX* pv ) { return (pv == v); } );
+ return vert_idx != -1;
+ } );
+
+ F32 step = editor->propgrid? editor->grid : 0.25f;
+
+ gui_label( x, y, "idx: %d", vert_idx ); y += space;
+ gui_label( x, y, "polygon idx: %d", poly_idx );
+
+ gui_button( x + 260, y - 2, 20, 20, "\x1A", pfn( void* ) {
+ GUI_EDITOR_PROPVIEW* view = editor->gui.props;
+ MAP_VERTEX* v = (MAP_VERTEX*)view->curselect;
+ WORLD_MAP* m = editor->map;
+ MAP_POLYGON* polygon = m->polygons.where( fn( MAP_POLYGON* p ) {
+ I32 vert_idx = p->vertices.idx_where( fn( MAP_VERTEX* pv ) { return (pv == v); } );
+ return vert_idx != -1;
+ } );
+
+ if( polygon )
+ gui_editor_propview_select( view, polygon, EDITOR_SELECT_POLY );
+ } );
+ y += space;
+
+ GUI_VECTORINPUT* posinput = gui_vectorinput( x, y, 280, "position", (F32*)&v->pos, 3, -INFINITY, INFINITY, step ); y += (space+18);
+ posinput->cb = pfn( void* ptr ) { map_check_bounds( editor->map ); };
+ gui_vectorinput( x, y, 280, "texture coordinates", (F32*)&v->uv, 2, 0.f, 1.f, 0.005f, "xy", "%.03f" ); y += (space+18);
+ gui_colorinput( x, y, 280, "color", &v->clr ); y += (space+18);
+}
+
+void gui_editor_propview_create_spriteprops( GUI_EDITOR_PROPVIEW* view ) {
+ MAP_SPRITE* s = (MAP_SPRITE*)view->curselect;
+ WORLD_MAP* m = editor->map;
+
+ I32 x = 10, y = 10;
+ I32 space = 20;
+
+ I32 sprite_idx = m->sprites.idx_where( fn( MAP_SPRITE* ms ) {
+ return ms == s;
+ } );
+
+ F32 step = editor->propgrid? editor->grid : 0.25f;
+
+ gui_label( x, y, "idx: %d", sprite_idx ); y += space;
+ gui_vectorinput( x, y, 280, "position", (F32*)&s->pos, 3, -INFINITY, INFINITY, step ); y += (space+18);
+ gui_vectorinput( x, y, 280, "size", (F32*)&s->size, 2, 1.f, INFINITY, 1.f, "wh" ); y += (space+18);
+ gui_colorinput( x, y, 280, "color", &s->clr ); y += (space+18);
+ if( s->tex )
+ gui_label( x, y, "texture: %s", s->tex->name );
+ else
+ gui_label( x, y, "texture: none" );
+
+ GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GL_TEX2D** ptex = (GL_TEX2D**)btn->extra;
+ GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex );
+ picker->cb = pfn( void* ) { gui_editor_propview_update( editor->gui.props ); };
+ } );
+ btn->extra = &s->tex;
+ y += space;
+}
+
+void gui_editor_propview_create_surfprops( GUI_EDITOR_PROPVIEW* view ) {
+ SURF_PROPS* p = (SURF_PROPS*)view->curselect;
+ WORLD_MAP* m = editor->map;
+
+ I32 x = 10, y = 10;
+ I32 space = 20;
+
+ I32 i = m->props.idx_where( fn( SURF_PROPS* mp ) { return mp == p; } );
+ if( i == -1 )
+ return;
+
+ gui_label( x, y, "prop id: %d", i ); y += space;
+ if( p->tex )
+ gui_label( x, y, "texture: %s", assets_abspath( p->tex->name ) );
+ else
+ gui_label( x, y, "texture: none" );
+ GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GL_TEX2D** ptex = (GL_TEX2D**)btn->extra;
+ GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex );
+ picker->cb = pfn( void* ) { gui_editor_propview_update( editor->gui.props ); };
+ } );
+ btn->extra = &p->tex;
+ y += space;
+ gui_colorinput( x, y, 280, "color", &p->clr ); y += (space+18);
+}
+
+void gui_editor_propview_create_entprops( GUI_EDITOR_PROPVIEW* view ) {
+
+}
+
+void gui_editor_propview_update( GUI_EDITOR_PROPVIEW* view ) {
+ if( !editor->map ) return;
+
+ GUI_VIEW* target = gui_get_view();
+ defer({ if( target ) gui_set_view( target ); });
+
+ gui_empty_children( view->itemview );
+ gui_set_view( view->itemview );
+ view->itemview->initheld = 1;
+
+ switch( view->seltype ) {
+ case EDITOR_SELECT_WALL: gui_editor_propview_create_wallprops( view ); break;
+ case EDITOR_SELECT_POLY: gui_editor_propview_create_polyprops( view ); break;
+ case EDITOR_SELECT_ORIGIN: gui_editor_propview_create_mapprops( view ); break;
+ case EDITOR_SELECT_ENT: gui_editor_propview_create_entprops( view ); break;
+ case EDITOR_SELECT_PVERTEX: gui_editor_propview_create_pvertexprops( view ); break;
+ case EDITOR_SELECT_SPRITE: gui_editor_propview_create_spriteprops( view ); break;
+ case EDITOR_SELECT_SURFPROPS: gui_editor_propview_create_surfprops( view ); break;
+ default: break;
+ }
+}
+
+void gui_editor_propview_get_title( GUI_EDITOR_PROPVIEW* view, char* buf ) {
+ switch( view->seltype ) {
+ case EDITOR_SELECT_NONE: sprintf( buf, "properties: " ); break;
+ case EDITOR_SELECT_POLY: sprintf( buf, "polygon properties: " ); break;
+ case EDITOR_SELECT_ORIGIN: sprintf( buf, "map [%s] properties: ", editor->map->name ); break;
+ case EDITOR_SELECT_ENT: sprintf( buf, "entity properties: " ); break;
+ case EDITOR_SELECT_SPRITE: sprintf( buf, "sprite properties: " ); break;
+ case EDITOR_SELECT_PVERTEX: sprintf( buf, "vertex properties: " ); break;
+ case EDITOR_SELECT_SURFPROPS: sprintf( buf, "surface properties: " ); break;
+ case EDITOR_SELECT_WVERTEX:
+ case EDITOR_SELECT_WALL: sprintf( buf, "wall properties: " ); break;
+ }
+}
+
+void gui_editor_propview_draw_fn( void* ptr ) {
+ if( !editor->map ) return;
+
+ GUI_EDITOR_PROPVIEW* view = (GUI_EDITOR_PROPVIEW*)ptr;
+
+ I32 x = gui_relx( view );
+ I32 y = gui_rely( view );
+ I32 w = view->w;
+ I32 h = view->h;
+
+ char title[64];
+ gui_editor_propview_get_title( view, title );
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, title );
+ y += PROPVIEW_TITLE_OFFSET;
+
+ CLR col = gui_is_fg_window( view )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x, y, w, h, col );
+ gui_draw_frect( x+1, y+1, w-2, h-2, ui_clr.bg_sec );
+
+ view->itemview->draw_fn( view->itemview );
+}
+
+GUI_EDITOR_PROPVIEW* gui_editor_propview( I32 x, I32 y, I32 w, I32 h ) {
+ if( !gui_check_target() ) return 0;
+
+ GUI_EDITOR_PROPVIEW* view = new GUI_EDITOR_PROPVIEW;
+ view->x = x;
+ view->y = y;
+ view->w = w;
+ view->h = h;
+ view->xbound = view->w;
+ view->ybound = view->h + PROPVIEW_TITLE_OFFSET;
+ strcpy( view->name, "EDITOR_PROP_VIEW" );
+ view->draw_fn = gui_editor_propview_draw_fn;
+ view->input_fn = gui_base_input_fn;
+
+ view->curselect = 0;
+ view->seltype = 0;
+
+ GUI_VIEW* parent = gui_get_view();
+ parent->children.push( view );
+ view->parent = parent;
+
+ gui_set_view( view );
+ view->itemview = gui_view( 0, PROPVIEW_TITLE_OFFSET, w, h );
+
+ gui_set_view( parent );
+ return view;
+}
diff --git a/src/editor/texturepicker.cpp b/src/editor/texturepicker.cpp
new file mode 100644
index 0000000..a24e955
--- /dev/null
+++ b/src/editor/texturepicker.cpp
@@ -0,0 +1,356 @@
+#include "editor.h"
+#include "../game/assets.h"
+#include "../render/gl_2d.h"
+
+I32 TEXTUREVIEW_TOP_OFFSET = 20;
+I32 TEXTUREVIEW_BOTTOM_OFFSET = 28;
+const char* TEXTURE_EXTENSIONS[] = {
+ "png",
+ 0
+};
+
+I32 gui_editor_texturepicker_get_scrollheight( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ return 0;
+}
+
+void gui_editor_texturepicker_handle_scroll( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ I32 x = wnd->x;
+ I32 y = wnd->y + TEXTUREVIEW_TOP_OFFSET;
+ I32 w = wnd->w;
+ I32 h = wnd->h - TEXTUREVIEW_TOP_OFFSET - TEXTUREVIEW_BOTTOM_OFFSET;
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h;
+ if( !inbounds )
+ return;
+
+ U8 scroll = gui_mbutton_down( GUI_MBTNSCROLL );
+ gui_capture_scroll();
+
+ if( scroll == 1 )
+ wnd->scrolloff -= 20;
+}
+
+void gui_editor_texturepicker_trim_texname( GUI_EDITOR_TEXTUREPICKER* wnd, char* trimname ) {
+ I32 size = (wnd->w - 20) / wnd->rowcount - 10;
+ I32 tw;
+ while( 1 ) {
+ gui_draw_get_str_bounds( &tw, 0, FNT_JPN12, trimname );
+ if( tw >= size ) {
+ if( trimname[0] != '.' && trimname[1] != '.' && trimname[2] != '.' ) {
+ sprintf( trimname, "...%s", trimname );
+ }
+
+ for( U32 i = 3; !!trimname[i]; ++i )
+ trimname[i] = trimname[i + 1];
+ } else break;
+ }
+}
+
+void gui_editor_texturepicker_draw_file_str( I32 x, I32 y, const char* str, U8 issel ) {
+ if( !issel ) {
+ return gui_draw_str(
+ x, y,
+ ALIGN_C,
+ FNT_JPN12,
+ ui_clr.txt,
+ str
+ );
+ }
+
+ I32 w, h;
+ gui_draw_get_str_bounds( &w, &h, FNT_JPN12, str );
+ w += 2;
+
+ gui_draw_frect( x - w / 2, y + 1, w, h, ui_clr.border );
+ gui_draw_str( x, y, ALIGN_C, FNT_JPN12, ui_clr.bg_alt, str );
+}
+
+void gui_editor_texturepicker_draw_files( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ I32 x = wnd->x + 11;
+ I32 y = wnd->y + TEXTUREVIEW_TOP_OFFSET;
+ I32 w = wnd->w - 22;
+ I32 size = (w + 10) / wnd->rowcount - 10;
+
+ I32 colcnt = (I32)floorf( (F32)w / size );
+ CLR clr = gui_is_fg_window( wnd )? ui_clr.border : ui_clr.border_inactive;
+
+ U32 i = 0;
+ wnd->textures.each( fn( GL_TEX2D** ptr ) {
+ GL_TEX2D* tex = *ptr;
+
+ if( strlen( wnd->search ) > 0 && !strstr( tex->name, wnd->search ) )
+ return;
+
+ I32 tx = x + (i % colcnt) * (size + 10);
+ I32 ty = y + (i / colcnt) * (size + 18);
+
+ gui_draw_frect( tx - 1, ty - 1, size + 2, size + 2, clr );
+ gui_draw_frect( tx, ty, size, size, clr );
+ gl_2d_textured_frect(
+ _gui.gl2d_font,
+ { (F32)tx, (F32)ty },
+ { (F32)size, (F32)size },
+ tex,
+ CLR::WHITE()
+ );
+
+ char trimname[256];
+ strcpy( trimname, assets_abspath( tex->name ) );
+ gui_editor_texturepicker_trim_texname( wnd, trimname );
+
+ gui_editor_texturepicker_draw_file_str(
+ tx + size / 2,
+ ty + size + 1,
+ trimname,
+ wnd->curselect == tex
+ );
+
+ ++i;
+ } );
+}
+
+void gui_editor_texturepicker_draw_fn( void* ptr ) {
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)ptr;
+
+ CLR clr = gui_is_fg_window( wnd )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( wnd->x, wnd->y, wnd->w, wnd->h, clr );
+ gui_draw_frect( wnd->x + 1, wnd->y + 1, wnd->w - 2, wnd->h - 2, ui_clr.bg );
+
+ //gui_draw_str( wnd->x + 2, wnd->y + 2, ALIGN_LEFT, FNT_JPN12, ui_clr.txt, "texture list" );
+ gui_editor_texturepicker_draw_files( wnd );
+
+ if( !wnd->loaded ) {
+ gui_draw_str(
+ wnd->x + wnd->w - 2,
+ wnd->y,
+ ALIGN_R,
+ FNT_JPN12,
+ ui_clr.txt,
+ "loading... [%d/%d]", wnd->curload + 1, wnd->files.size
+ );
+ }
+
+ wnd->children.each( fn( GUI_BASE** childptr ) {
+ GUI_BASE* child = *childptr;
+ if( !child->enabled ) return;
+ if( child->draw_fn )
+ child->draw_fn( child );
+ } );
+}
+
+void gui_editor_textureview_delete_textures( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ wnd->textures.each( fn( GL_TEX2D** ptr ) {
+ GL_TEX2D* tex = *ptr;
+ gl_texture_destroy( _gui.gl2d->gl, tex );
+ } );
+
+ wnd->textures.clear();
+}
+
+void gui_editor_texturepicker_load_texture( void* ptr ) {
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)ptr;
+
+ FILE_ENTRY* f = &wnd->files[wnd->curload];
+ const char* name = assets_abspath( f->name );
+ GL_TEX2D* tex = gl_texture_from_file( _gui.gl2d->gl, name );
+ if( !tex ) {
+ dlog( "gui_editor_textureview_create_textures() : error creating texture %s", name );
+ return;
+ }
+ wnd->textures.push( tex );
+
+ if( ++wnd->curload < wnd->files.size )
+ gui_push_callback( wnd, gui_editor_texturepicker_load_texture );
+ else
+ wnd->loaded = 1;
+}
+
+void gui_editor_textureview_create_textures( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ wnd->curload = 0;
+ GL_TEX2D* emptytex = gl_texture_create( _gui.gl2d->gl, "" );
+ strcpy( emptytex->name, "none" );
+ emptytex->width = emptytex->height = 1;
+
+ wnd->textures.push( emptytex );
+
+ gui_push_callback( wnd, gui_editor_texturepicker_load_texture );
+}
+
+void gui_editor_textureview_accept( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ if( !wnd->curselect )
+ return;
+
+ U8 isnone = !strcmp( wnd->curselect->name, "none" );
+ wnd->textures.each( fn( GL_TEX2D** ptr ) {
+ GL_TEX2D* tex = *ptr;
+ if( !isnone && tex == wnd->curselect )
+ return;
+
+ gl_texture_destroy( _gui.gl2d->gl, tex );
+ } );
+
+ if( !isnone ) {
+ if( map_add_texture_ref( editor->map, wnd->curselect ) == STAT_ALREADYEXISTS ) {
+ const char* name = assets_abspath( wnd->curselect->name );
+ GL_TEX2D* tex = map_find_texture( editor->map, name );
+ *wnd->target = tex;
+
+ gl_texture_destroy( _gui.gl2d->gl, wnd->curselect );
+ } else {
+ *wnd->target = wnd->curselect;
+ }
+ } else {
+ *wnd->target = 0;
+ }
+
+ if( wnd->cb )
+ wnd->cb( wnd );
+
+ wnd->textures.clear();
+ gui_push_callback( wnd, pfn( void* p ) { gui_free( (GUI_BASE*)p ); } );
+}
+
+U8 gui_editor_texturepicker_input_files( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ I32 x = wnd->x + 11;
+ I32 y = wnd->y + TEXTUREVIEW_TOP_OFFSET;
+ I32 w = wnd->w - 22;
+ I32 size = (w + 10) / wnd->rowcount - 10;
+
+ I32 colcnt = (I32)floorf( (F32)w / size );
+ I32 i = 0;
+
+ U8 m1 = gui_mbutton_down( 0 );
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ wnd->textures.each( fn( GL_TEX2D** ptr ) {
+ GL_TEX2D* tex = *ptr;
+
+ // yea technically this is slow as fuck but whatever
+ if( strlen( wnd->search ) > 0 && !strstr( tex->name, wnd->search ) )
+ return;
+
+ I32 tx = x + (i % colcnt) * (size + 10);
+ I32 ty = y + (i / colcnt) * (size + 18);
+
+ U8 inbounds = ( (mx >= tx) && (mx < tx + size) && (my >= ty) && (my < ty + size + 18) );
+ if( inbounds && m1 && !wnd->heldselect ) {
+ wnd->heldselect = tex;
+ }
+ if( !m1 && wnd->heldselect == tex ) {
+ wnd->curselect = tex;
+ }
+
+ ++i;
+ } );
+
+ if( !m1 ) {
+ wnd->heldselect = 0;
+ }
+
+ return 0;
+}
+
+void gui_editor_texturepicker_input_fn( void* ptr ) {
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)ptr;
+
+ if( !gui_editor_texturepicker_input_files( wnd ) )
+ gui_base_input_fn( ptr );
+}
+
+void gui_editor_texturepicker_update_density( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ wnd->scrollheight = gui_editor_texturepicker_get_scrollheight( wnd );
+ sprintf( wnd->densitylabel->name, "density: %d", wnd->rowcount );
+}
+
+void gui_editor_textureview_create_bottom_row( GUI_EDITOR_TEXTUREPICKER* wnd ) {
+ I32 x = 10;
+
+ gui_label( x, 2, "filter:" );
+ GUI_TEXTBOX* tb = gui_textbox( x + 50, -15, 92, 20, "", 256 );
+ tb->cb = pfn( void* ptr ) {
+ GUI_TEXTBOX* tb = (GUI_TEXTBOX*)ptr;
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)tb->parent->parent;
+ strcpy( wnd->search, tb->value );
+ };
+ x += 150;
+
+ wnd->densitylabel = gui_label( x, 2, "density: %d", wnd->rowcount );
+ gui_button( x + 66, 0, 20, 20, "+", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)btn->parent->parent;
+ if( wnd->rowcount < 9 ) wnd->rowcount++;
+ gui_editor_texturepicker_update_density( wnd );
+ } );
+
+ gui_button( x + 91, 0, 20, 20, "-", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)btn->parent->parent;
+ if( wnd->rowcount > 3 ) wnd->rowcount--;
+ gui_editor_texturepicker_update_density( wnd );
+ } );
+
+ gui_button( wnd->w - 120, 0, 50, 20, "cancel", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)btn->parent->parent;
+ gui_editor_textureview_delete_textures( wnd );
+ gui_push_callback( wnd, pfn( void* p ) { gui_free( (GUI_BASE*)p ); } );
+ } );
+
+ gui_button( wnd->w - 60, 0, 50, 20, "ok", pfn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)btn->parent->parent;
+ gui_editor_textureview_accept( wnd );
+ } );
+}
+
+void gui_editor_texturepicker_init( void* ptr ) {
+ GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)ptr;
+
+ wnd->files = assets_get_files_by_ext( TEXTURE_EXTENSIONS );
+ gui_editor_textureview_create_textures( wnd );
+}
+
+GUI_EDITOR_TEXTUREPICKER* gui_editor_texturepicker( I32 x, I32 y, I32 w, I32 h, GL_TEX2D **target ) {
+ GUI_EDITOR_TEXTUREPICKER* wnd = new GUI_EDITOR_TEXTUREPICKER;
+ wnd->x = x;
+ wnd->y = y;
+ wnd->w = w;
+ wnd->h = h;
+ wnd->ontop = 1;
+ wnd->locked = 0;
+ wnd->draw_fn = gui_editor_texturepicker_draw_fn;
+ wnd->input_fn = gui_editor_texturepicker_input_fn;
+ strcpy( wnd->name, "TEXTURE_PICKER_WINDOW" );
+ wnd->cb = 0;
+
+ _gui.windows.push( wnd );
+ gui_set_window( wnd );
+
+ wnd->target = target;
+ wnd->rowcount = 3;
+ wnd->curselect = 0;
+ wnd->scrolloff = 0;
+ wnd->scrollheight = gui_editor_texturepicker_get_scrollheight( wnd );
+ wnd->search[0] = 0;
+
+ wnd->loaded = 0;
+ gui_push_callback( wnd, gui_editor_texturepicker_init );
+
+ GUI_VIEW* view = gui_get_view();
+
+ gui_set_view( 0 );
+ gui_view( 0, 0, w, 20 );
+ gui_title( "texture picker" );
+ gui_set_view( 0 );
+
+ gui_view( 0, h - TEXTUREVIEW_BOTTOM_OFFSET - 1, w, TEXTUREVIEW_BOTTOM_OFFSET );
+ gui_editor_textureview_create_bottom_row( wnd );
+
+ gui_set_view( view );
+
+ return wnd;
+}
diff --git a/src/editor/view2d.cpp b/src/editor/view2d.cpp
new file mode 100644
index 0000000..35fd208
--- /dev/null
+++ b/src/editor/view2d.cpp
@@ -0,0 +1,938 @@
+#include <math.h>
+#include "editor.h"
+#include "../render/gl_2d.h"
+
+#include "../game/objlist.h"
+
+const I32 EDITORVIEW_TITLE_OFFSET = 15;
+const I32 EDITORVIEW_GUTTERS_OFFSETX = 22;
+const I32 EDITORVIEW_GUTTERS_OFFSETY = 16;
+
+F32 gui_editor_2dview_calc_scale( GUI_EDITOR_2DVIEW* view ) {
+ WORLD_MAP* m = editor->map;
+
+ I32 w = view->w - EDITORVIEW_GUTTERS_OFFSETX - 1;
+ I32 h = view->h - EDITORVIEW_GUTTERS_OFFSETY - 1;
+
+ F32 mapw = m->maxs.x - m->mins.x;
+ F32 maph = m->maxs.y - m->mins.y;
+
+ F32 scale = min( w / mapw, h / maph );
+ return scale * view->scale;
+}
+
+VEC2 gui_editor_2dview_screen_to_world( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) {
+ WORLD_MAP* m = editor->map;
+
+ I32 _x = gui_relx( view ) + EDITORVIEW_GUTTERS_OFFSETX;
+ I32 _y = gui_rely( view ) + EDITORVIEW_TITLE_OFFSET + EDITORVIEW_GUTTERS_OFFSETY;
+ x -= _x; y -= _y;
+
+ F32 w = view->w - EDITORVIEW_GUTTERS_OFFSETX;
+ F32 h = view->h - EDITORVIEW_GUTTERS_OFFSETY;
+
+ F32 tx = x / w;
+ F32 ty = y / h;
+
+ F32 scaledx = m->mins.x + (m->maxs.x - m->mins.x) * tx;
+ F32 scaledy = m->mins.y + (m->maxs.y - m->mins.y) * ty;
+
+ if( w > h )
+ scaledx *= ( w / h );
+ if( w < h )
+ scaledy *= h / w;
+
+ scaledx /= view->scale;
+ scaledx += view->posx;
+ scaledy /= view->scale;
+ scaledy += view->posy;
+
+ return { scaledx, scaledy };
+}
+
+VEC2 gui_editor_2dview_world_to_screen( GUI_EDITOR_2DVIEW* view, VEC2 world ) {
+ WORLD_MAP* m = editor->map;
+ F32 scale = gui_editor_2dview_calc_scale( view );
+
+ F32 xoff = m->mins.x + view->posx;
+ F32 yoff = m->mins.y + view->posy;
+
+ F32 vx = (F32)gui_relx( view );
+ F32 vy = (F32)gui_rely( view );
+ vy += EDITORVIEW_TITLE_OFFSET + EDITORVIEW_GUTTERS_OFFSETY;
+ vx += EDITORVIEW_GUTTERS_OFFSETX;
+
+ F32 x = vx + (world.x - xoff) * scale;
+ F32 y = vy + (world.y - yoff) * scale;
+
+ return { x, y };
+}
+
+void gui_editor_2dview_select( GUI_EDITOR_2DVIEW* view, void* what, U8 seltype ) {
+ view->curselect = what;
+ view->seltype = seltype;
+}
+
+U8 gui_editor_2dview_is_gizmo_active( GUI_EDITOR_2DVIEW* view, void* what, U8 seltype ) {
+ GUI_EDITOR_PROPVIEW* props = editor->gui.props;
+ if( props->seltype == EDITOR_SELECT_WALL && (seltype == EDITOR_SELECT_WVERTEX) ) {
+ MAP_WALL* s = (MAP_WALL*)props->curselect;
+ if( what == &s->start || what == &s->end )
+ return 1;
+ }
+ else if( props->seltype == seltype && props->curselect == what ) {
+ return 1;
+ }
+
+ if( view->curdrag ) {
+ return what == view->curdrag && seltype == view->dragtype;
+ }
+ else {
+ return what == view->curselect && seltype == view->seltype;
+ }
+}
+
+void gui_editor_2dview_draw_gizmo( I32 x, I32 y, CLR clr, U8 selected = 0 ) {
+ const I32 GIZMO_SIZE = selected ? 10 : 6;
+ const I32 ihalf = GIZMO_SIZE / 2;
+
+ gui_draw_frect( x - ihalf - 1, y - ihalf - 1, GIZMO_SIZE, GIZMO_SIZE, { .7f, .7f, .7f, 1.f } );
+ gui_draw_frect( x - ihalf + 1, y - ihalf + 1, GIZMO_SIZE, GIZMO_SIZE, { 0.f, 0.f, 0.f, 1.f } );
+ gui_draw_frect( x - ihalf, y - ihalf, GIZMO_SIZE, GIZMO_SIZE, clr );
+}
+
+void gui_editor_2dview_draw_gutters( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) {
+ WORLD_MAP* m = editor->map;
+ F32 w = view->w;
+ F32 h = view->h;
+
+ gui_draw_str( x + 2, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "%1.f,%1.f", view->posx, view->posy );
+ for( U32 i = 1; i <= 5; ++i ) {
+ F32 tx = x + i * (w / 5);
+ F32 ty = y;
+
+ F32 tp = (tx - x) / w;
+
+ F32 scaledx = m->mins.x + (m->maxs.x - m->mins.x) * tp;
+ if( w > h )
+ scaledx *= ( w / h );
+ scaledx /= view->scale;
+ scaledx += view->posx;
+ gui_draw_str( tx - 2, ty, ALIGN_R, FNT_JPN12, ui_clr.txt, "%1.f", scaledx );
+ gui_draw_line( tx, ty, tx + 1, ty + 16, ui_clr.txt );
+ }
+
+ for( U32 i = 1; i <= 5; ++i ) {
+ F32 tx = x + 2;
+ F32 ty = y + i * (h / 5);
+
+ F32 tp = (ty - y) / h;
+
+ F32 scaledy = m->mins.y + (m->maxs.y - m->mins.y) * tp;
+ if( w < h )
+ scaledy *= h / w;
+ scaledy /= view->scale;
+ scaledy += view->posy;
+ gui_draw_str( tx, ty - 16, ALIGN_L, FNT_JPN12, ui_clr.txt, "%1.f", scaledy );
+ gui_draw_line( tx - 2, ty, tx + 20, ty, ui_clr.txt );
+ }
+}
+
+void gui_editor_2dview_draw_polygons( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) {
+ WORLD_MAP* m = editor->map;
+
+ F32 scale = gui_editor_2dview_calc_scale( view );
+ F32 xoff = m->mins.x + view->posx;
+ F32 yoff = m->mins.y + view->posy;
+
+ m->polygons.each( fn( MAP_POLYGON* p ) {
+ SURF_PROPS* props = polygon_get_props( m, p );
+ if( !editor->wireframe ) {
+ LIST<VERTEX> vertices;
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ VERTEX v2;
+ v2.uv = v->uv;
+ v2.pos.x = x + (v->pos.x - xoff) * scale;
+ v2.pos.y = y + (v->pos.y - yoff) * scale;
+ v2.clr = props->clr;
+ vertices.push( v2 );
+ } );
+
+ if( props->tex )
+ gl_textured_polygon( _gui.gl2d_font, vertices.data, vertices.size, props->tex );
+ else
+ gl_polygon( _gui.gl2d, vertices.data, vertices.size );
+ }
+ else {
+ for( U32 i = 0; i < p->vertices.size; ++i ) {
+ MAP_VERTEX* v1 = &p->vertices[i];
+ MAP_VERTEX* v2 = &p->vertices[(i+1) % p->vertices.size];
+
+ I32 x0 = x + (I32)( (v1->pos.x - xoff) * scale );
+ I32 y0 = y + (I32)( (v1->pos.y - yoff) * scale );
+ I32 x1 = x + (I32)( (v2->pos.x - xoff) * scale );
+ I32 y1 = y + (I32)( (v2->pos.y - yoff) * scale );
+
+ gui_draw_line( x0, y0, x1, y1, props->clr );
+ }
+ }
+
+ if( editor->tool != EDITOR_TOOL_POLY && editor->tool != EDITOR_TOOL_SELECT )
+ return;
+ // draw gizmos
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ I32 vx = (I32)( x + (v->pos.x - xoff) * scale );
+ I32 vy = (I32)( y + (v->pos.y - yoff) * scale );
+ U8 selected = gui_editor_2dview_is_gizmo_active( view, v, EDITOR_SELECT_PVERTEX )
+ || gui_editor_2dview_is_gizmo_active( view, p, EDITOR_SELECT_POLY );
+
+ gui_editor_2dview_draw_gizmo( vx, vy, CLR::CYAN(), selected );
+ } );
+ } );
+}
+
+void gui_editor_2dview_draw_walls( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) {
+ WORLD_MAP* m = editor->map;
+
+ F32 scale = gui_editor_2dview_calc_scale( view );
+ F32 xoff = m->mins.x + view->posx;
+ F32 yoff = m->mins.y + view->posy;
+
+ m->walls.each( fn( MAP_WALL* s ) {
+ SURF_PROPS* props = wall_get_props( m, s );
+
+ I32 x0 = (I32)( x + (s->start.x - xoff) * scale );
+ I32 y0 = (I32)( y + (s->start.y - yoff) * scale );
+ I32 x1 = (I32)( x + (s->end.x - xoff) * scale );
+ I32 y1 = (I32)( y + (s->end.y - yoff) * scale );
+
+ gui_draw_line( x0, y0, x1, y1, props->clr );
+ if( gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_WALL ) ) {
+ gui_draw_line( x0, y0 - 1, x1, y1 - 1, CLR::WHITE( .5f ) );
+ gui_draw_line( x0, y0 + 1, x1, y1 + 1, CLR::WHITE( .5f ) );
+ gui_draw_line( x0 - 1, y0, x1 - 1, y1, CLR::WHITE( .5f ) );
+ gui_draw_line( x0 + 1, y0, x1 + 1, y1, CLR::WHITE( .5f ) );
+ }
+ } );
+
+ if( editor->tool != EDITOR_TOOL_WALL && editor->tool != EDITOR_TOOL_SELECT )
+ return;
+
+ // gizmos
+ m->walls.each( fn( MAP_WALL* s ) {
+ I32 x0 = (I32)( x + (s->start.x - xoff) * scale );
+ I32 y0 = (I32)( y + (s->start.y - yoff) * scale );
+ I32 x1 = (I32)( x + (s->end.x - xoff) * scale );
+ I32 y1 = (I32)( y + (s->end.y - yoff) * scale );
+
+ U8 sel = gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_WALL );
+ U8 sel1 = gui_editor_2dview_is_gizmo_active( view, &s->start, EDITOR_SELECT_WVERTEX );
+ U8 sel2 = gui_editor_2dview_is_gizmo_active( view, &s->end, EDITOR_SELECT_WVERTEX );
+
+ gui_editor_2dview_draw_gizmo( x0, y0, CLR::GREEN(), sel || sel1 );
+ gui_editor_2dview_draw_gizmo( x1, y1, CLR::GREEN(), sel || sel2 );
+ } );
+}
+
+void gui_editor_2dview_draw_sprites( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) {
+ WORLD_MAP* m = editor->map;
+
+ F32 scale = gui_editor_2dview_calc_scale( view );
+ F32 xoff = m->mins.x + view->posx;
+ F32 yoff = m->mins.y + view->posy;
+
+ m->sprites.each( fn( MAP_SPRITE* s ) {
+ F32 wantedsize = editor->spritesize;
+ F32 aspect = s->size.x / s->size.y;
+ F32 w = (aspect > 1.0f)? wantedsize : wantedsize * aspect;
+ F32 h = (aspect < 1.0f)? wantedsize : wantedsize / aspect;
+ VEC2 pos = {
+ (F32)x + (s->pos.x - xoff) * scale,
+ (F32)y + (s->pos.y - yoff) * scale
+ };
+
+ U8 sel= gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_SPRITE );
+ if( sel ) {
+ gui_draw_rect( pos.x - w/2 - 1, pos.y - h/2 - 1, w + 2, h + 2, CLR::WHITE( 0.5f ) );
+ }
+
+ gl_2d_textured_frect(
+ _gui.gl2d_font,
+ { (F32)((I32)pos.x - w/2), (F32)((I32)pos.y - h/2) },
+ { (F32)w, (F32)h },
+ s->tex,
+ s->clr
+ );
+ } );
+}
+
+void gui_editor_2dview_draw_player( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) {
+ WORLD_MAP* m = editor->map;
+
+ if( !objl->pl )
+ return;
+
+ F32 scale = gui_editor_2dview_calc_scale( view );
+ F32 xoff = m->mins.x + view->posx;
+ F32 yoff = m->mins.y + view->posy;
+
+ VEC3 pos = objl->pl->pos;
+
+ VEC2 pos2d = {
+ (F32)x + (pos.x - xoff) * scale,
+ (F32)y + (pos.y - yoff) * scale
+ };
+
+ F32 yaw = objl->pl->rot.y;
+ VEC2 dir = m_radial_offset( yaw, 20.f );
+ VEC2 ray = pos2d + dir;
+
+ gui_draw_line( (I32)pos2d.x, (I32)pos2d.y, (I32)ray.x, (I32)ray.y, CLR::GREEN() );
+ gui_draw_frect( (I32)pos2d.x-4, (I32)pos2d.y-4, 8, 8, CLR::WHITE() );
+ gui_draw_frect( (I32)pos2d.x-3, (I32)pos2d.y-3, 6, 6, CLR::RED() );
+}
+
+void gui_editor_2dview_draw_origin( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) {
+ WORLD_MAP* m = editor->map;
+
+ F32 scale = gui_editor_2dview_calc_scale( view );
+ F32 xoff = m->mins.x + view->posx;
+ F32 yoff = m->mins.y + view->posy;
+
+ VEC3 pos = m->startpos;
+ I32 screenx = (I32)( x + (pos.x - xoff) * scale );
+ I32 screeny = (I32)( y + (pos.y - yoff) * scale );
+
+ if( gui_editor_2dview_is_gizmo_active( view, m, EDITOR_SELECT_ORIGIN ) ) {
+ I32 w, h;
+ gui_draw_get_str_bounds( &w, &h, FNT_JPN12, "(origin)" );
+ gui_draw_frect( screenx - w / 2 - 1, screeny + h - 1, w, 1, CLR::BLACK() );
+ gui_draw_frect( screenx - w / 2, screeny + h, w, 1, ui_clr.txt );
+ gui_draw_str( screenx - 1, screeny - 1, ALIGN_C, FNT_JPN12, CLR::WHITE( .5f ), "(origin)" );
+ }
+
+ gui_draw_str( screenx + 1, screeny + 1, ALIGN_C, FNT_JPN12, CLR::BLACK(), "(origin)" );
+ gui_draw_str( screenx, screeny, ALIGN_C, FNT_JPN12, ui_clr.txt, "(origin)" );
+}
+
+void gui_editor_2dview_draw_fn( void* ptr ) {
+ GUI_EDITOR_2DVIEW* view = (GUI_EDITOR_2DVIEW*)ptr;
+ if( !editor->map )
+ return;
+
+ I32 x = gui_relx( view );
+ I32 y = gui_rely( view );
+ I32 w = view->w;
+ I32 h = view->h;
+
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "2d view" );
+ y += EDITORVIEW_TITLE_OFFSET;
+
+ CLR col = gui_is_fg_window( view )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x-1, y-1, view->w+2, view->h+2, col );
+ gui_draw_frect( x, y, view->w, view->h, CLR::BLACK() );
+
+ gui_editor_2dview_draw_gutters( view, x, y );
+
+ U32 offx = EDITORVIEW_GUTTERS_OFFSETX;
+ U32 offy = EDITORVIEW_GUTTERS_OFFSETY;
+
+ gui_draw_push_clip( x + offx, y + offy, w - offx, h - offy );
+ gui_editor_2dview_draw_polygons( view, x + offx, y + offy );
+ gui_editor_2dview_draw_walls( view, x + offx, y + offy );
+ gui_editor_2dview_draw_sprites( view, x + offx, y + offy );
+ gui_editor_2dview_draw_player( view, x + offx, y + offy );
+ gui_editor_2dview_draw_origin( view, x + offx, y + offy );
+ gui_draw_pop_clip();
+}
+
+U8 gui_editor_2dview_input_drag( GUI_EDITOR_2DVIEW* view, U8 mouse ) {
+ if( !mouse ) {
+ view->dragheld = 0;
+ return 0;
+ }
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ I32 x = gui_relx( view );
+ I32 y = gui_rely( view ) + EDITORVIEW_TITLE_OFFSET;
+ I32 w = view->w - EDITORVIEW_GUTTERS_OFFSETX;
+ I32 h = view->h - EDITORVIEW_GUTTERS_OFFSETY;
+ x += EDITORVIEW_GUTTERS_OFFSETX;
+ y += EDITORVIEW_GUTTERS_OFFSETY;
+
+ U8 inbound = mx >= x && mx <= x + w && my >= y && my <= y + h;
+ if( !inbound && !view->dragheld )
+ return 0;
+
+ I32 moffx = mx - x;
+ I32 moffy = my - y;
+
+ if( !view->dragheld ) {
+ view->moffx = moffx;
+ view->moffy = moffy;
+ view->voffx = view->posx;
+ view->voffy = view->posy;
+ view->dragheld = 1;
+ return 1;
+ }
+
+ F32 iscale = 1.f / gui_editor_2dview_calc_scale( view );
+
+ view->posx = view->voffx - (moffx - view->moffx) * iscale;
+ view->posy = view->voffy - (moffy - view->moffy) * iscale;
+ return 1;
+}
+
+void gui_editor_2dview_input_tool_none( GUI_EDITOR_2DVIEW* view ) {
+ U8 mouse = gui_mbutton_down( 0 ) || gui_mbutton_down( 1 );
+
+ gui_editor_2dview_input_drag( view, mouse );
+}
+
+VEC2 gui_editor_2dview_input_get_drag_vec( GUI_EDITOR_2DVIEW* view ) {
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ F32 iscale = 1.f / gui_editor_2dview_calc_scale( view );
+ F32 dx = (mx - view->oldmx) * iscale + view->mremainx;
+ F32 dy = (my - view->oldmy) * iscale + view->mremainy;
+
+ F32 igridx = copysignf( floorf( fabsf( dx / editor->grid ) ), dx );
+ F32 igridy = copysignf( floorf( fabsf( dy / editor->grid ) ), dy );
+
+ return { editor->grid * igridx, editor->grid * igridy };
+}
+
+void gui_editor_2dview_input_select_onmove( GUI_EDITOR_2DVIEW* view ) {
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ F32 iscale = 1.f / gui_editor_2dview_calc_scale( view );
+ F32 dx = (mx - view->oldmx) * iscale + view->mremainx;
+ F32 dy = (my - view->oldmy) * iscale + view->mremainy;
+
+ F32 rmx = copysignf( fmodf( fabsf( dx ), editor->grid ), dx );
+ F32 rmy = copysignf( fmodf( fabsf( dy ), editor->grid ), dy );
+
+ view->oldmx = mx;
+ view->oldmy = my;
+ view->mremainx = rmx;
+ view->mremainy = rmy;
+ view->dragmoved = 1;
+
+ GUI_EDITOR_PROPVIEW* props = editor->gui.props;
+ // special case for dragging wall vertices, just always update
+ U8 iswallv = props->seltype == EDITOR_SELECT_WALL && view->dragtype == EDITOR_SELECT_WVERTEX;
+ if( props->curselect == view->curdrag || iswallv )
+ gui_editor_propview_update( editor->gui.props );
+}
+
+void gui_editor_2dview_input_select_drag_wall( GUI_EDITOR_2DVIEW* view ) {
+ MAP_WALL* s = (MAP_WALL*)view->curdrag;
+
+ s->start.x = m_snap_to_grid( s->start.x, editor->grid );
+ s->start.y = m_snap_to_grid( s->start.y, editor->grid );
+ s->end.x = m_snap_to_grid( s->end.x, editor->grid );
+ s->end.y = m_snap_to_grid( s->end.y, editor->grid );
+
+ VEC2 mv = gui_editor_2dview_input_get_drag_vec( view );
+ if( !is_zero( mv ) ) {
+ s->start += mv;
+ s->end += mv;
+ map_check_bounds( editor->map );
+
+ gui_editor_2dview_input_select_onmove( view );
+ }
+}
+
+void gui_editor_2dview_input_select_drag_vertex( GUI_EDITOR_2DVIEW* view ) {
+ VEC2* v = (VEC2*)view->curdrag;
+ v->x = m_snap_to_grid( v->x, editor->grid );
+ v->y = m_snap_to_grid( v->y, editor->grid );
+
+ VEC2 mv = gui_editor_2dview_input_get_drag_vec( view );
+ if( !is_zero( mv ) ) {
+ *v += mv;
+ map_check_bounds( editor->map );
+
+ gui_editor_2dview_input_select_onmove( view );
+ }
+}
+
+void gui_editor_2dview_input_select_drag_polygon( GUI_EDITOR_2DVIEW* view ) {
+ MAP_POLYGON* p = (MAP_POLYGON*)view->curdrag;
+
+ VEC2 mv = gui_editor_2dview_input_get_drag_vec( view );
+ if( !is_zero( mv ) ) {
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ v->pos.x = m_snap_to_grid( v->pos.x, editor->grid );
+ v->pos.y = m_snap_to_grid( v->pos.y, editor->grid );
+
+ v->pos.x += mv.x;
+ v->pos.y += mv.y;
+ } );
+
+ map_polygon_calc_bounds( p );
+ map_check_bounds( editor->map );
+ gui_editor_2dview_input_select_onmove( view );
+ }
+}
+
+void gui_editor_2dview_input_select_drag_sprite( GUI_EDITOR_2DVIEW* view ) {
+ MAP_SPRITE* s = (MAP_SPRITE*)view->curdrag;
+
+ s->pos.x = m_snap_to_grid( s->pos.x, editor->grid );
+ s->pos.y = m_snap_to_grid( s->pos.y, editor->grid );
+
+ VEC2 mv = gui_editor_2dview_input_get_drag_vec( view );
+ if( !is_zero( mv ) ) {
+ s->pos += mv;
+ gui_editor_2dview_input_select_onmove( view );
+ }
+}
+
+void gui_editor_2dview_input_select_drag_origin( GUI_EDITOR_2DVIEW* view ) {
+ WORLD_MAP* m = editor->map;
+
+ m->startpos.x = m_snap_to_grid( m->startpos.x, editor->grid );
+ m->startpos.y = m_snap_to_grid( m->startpos.y, editor->grid );
+
+ VEC2 mv = gui_editor_2dview_input_get_drag_vec( view );
+ if( !is_zero( mv ) ) {
+ m->startpos += mv;
+ gui_editor_2dview_input_select_onmove( view );
+ }
+}
+
+void gui_editor_2dview_input_select_ondrop( GUI_EDITOR_2DVIEW* view ) {
+ if( !view->dragmoved )
+ gui_editor_propview_select( editor->gui.props, view->curdrag, view->dragtype );
+
+ view->curdrag = 0;
+}
+
+void gui_editor_2dview_input_select_drag( GUI_EDITOR_2DVIEW* view ) {
+ U8 m1 = gui_mbutton_down( 0 );
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ if( !m1 ) {
+ if( view->held ) {
+ gui_editor_2dview_input_select_ondrop( view );
+ view->held = 0;
+ }
+ return;
+ }
+
+ if( !view->held ) {
+ view->curdrag = view->curselect;
+ view->dragtype = view->seltype;
+ view->oldmx = mx;
+ view->oldmy = my;
+ view->mremainx = view->mremainy = 0;
+ view->dragmoved = 0;
+ view->held = 1;
+ return;
+ }
+
+ switch( view->dragtype ) {
+ case EDITOR_SELECT_WALL: gui_editor_2dview_input_select_drag_wall( view ); break;
+ case EDITOR_SELECT_POLY: gui_editor_2dview_input_select_drag_polygon( view ); break;
+ case EDITOR_SELECT_SPRITE: gui_editor_2dview_input_select_drag_sprite( view ); break;
+ case EDITOR_SELECT_ORIGIN: gui_editor_2dview_input_select_drag_origin( view ); break;
+ case EDITOR_SELECT_WVERTEX:
+ case EDITOR_SELECT_PVERTEX:
+ gui_editor_2dview_input_select_drag_vertex( view ); break;
+ default: {
+ view->oldmx = mx;
+ view->oldmy = my;
+ } break;
+ }
+}
+
+void gui_editor_2dview_input_select_walls( GUI_EDITOR_2DVIEW* view ) {
+ WORLD_MAP* m = editor->map;
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ VEC2 mpos = { (F32)mx, (F32)my };
+
+ F32 mindist = 5.0001f;
+ for( U32 i = 0; i < m->walls.size; ++i ) {
+ MAP_WALL* w = &m->walls[i];
+
+ VEC2 w1 = { w->start.x, w->start.y };
+ VEC2 w2 = { w->end.x, w->end.y };
+ w1 = gui_editor_2dview_world_to_screen( view, w1 );
+ w2 = gui_editor_2dview_world_to_screen( view, w2 );
+
+ F32 d1 = vec_dist( mpos, w1 );
+ F32 d2 = vec_dist( mpos, w2 );
+ if( d1 < mindist && d1 < d2 ) {
+ gui_editor_2dview_select( view, &w->start, EDITOR_SELECT_WVERTEX );
+ mindist = d1;
+ continue;
+ }
+ else if( d2 < mindist ) {
+ gui_editor_2dview_select( view, &w->end, EDITOR_SELECT_WVERTEX );
+ mindist = d2;
+ continue;
+ }
+ // give priority to vertices
+ if( view->seltype == EDITOR_SELECT_WVERTEX )
+ continue;
+
+ F32 dist = m_dist_line_to_point( w1, w2, mpos );
+ if( dist < 3.f && dist < mindist ) {
+ gui_editor_2dview_select( view, w, EDITOR_SELECT_WALL );
+ mindist = dist;
+ }
+ }
+
+ if( !view->curselect && !view->held )
+ return;
+
+ gui_editor_2dview_input_select_drag( view );
+}
+
+void gui_editor_2dview_input_select_polygons( GUI_EDITOR_2DVIEW* view ) {
+ WORLD_MAP* m = editor->map;
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ VEC2 mpos = { (F32)mx, (F32)my };
+
+ F32 mindist = 5.0001f;
+ for( U32 i = 0; i < m->polygons.size; ++i ) {
+ MAP_POLYGON* p = &m->polygons[i];
+ LIST<VEC2> plist;
+
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ VEC2 world = { v->pos.x, v->pos.y };
+ VEC2 screen = gui_editor_2dview_world_to_screen( view, world );
+ F32 d = vec_dist( mpos, screen );
+ if( d < mindist ) {
+ gui_editor_2dview_select( view, v, EDITOR_SELECT_PVERTEX );
+ mindist = d;
+ }
+
+ plist.push( screen );
+ } );
+
+ if( view->seltype == EDITOR_SELECT_PVERTEX )
+ continue;
+
+ if( m_point_in_polygon( mpos, plist.data, plist.size ) )
+ gui_editor_2dview_select( view, p, EDITOR_SELECT_POLY );
+ }
+
+ if( !view->curselect && !view->held )
+ return;
+
+ gui_editor_2dview_input_select_drag( view );
+}
+
+void gui_editor_2dview_input_select_sprites( GUI_EDITOR_2DVIEW* view ) {
+ WORLD_MAP* m = editor->map;
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ VEC2 mpos = { (F32)mx, (F32)my };
+
+ F32 hsize = editor->spritesize * .5f;
+ F32 mindist = editor->spritesize;
+ for( U32 i = 0; i < m->sprites.size; ++i ) {
+ MAP_SPRITE* s = &m->sprites[i];
+
+ VEC2 world = { s->pos.x, s->pos.y };
+ VEC2 screen = gui_editor_2dview_world_to_screen( view, world );
+
+ if( mpos.x >= screen.x - hsize && mpos.x <= screen.x + hsize
+ && mpos.y >= screen.y - hsize && mpos.y <= screen.y + hsize )
+ {
+ F32 dist = vec_dist( mpos, screen );
+ if( dist < mindist ) {
+ mindist = dist;
+ gui_editor_2dview_select( view, s, EDITOR_SELECT_SPRITE );
+ }
+ }
+ }
+
+ if( !view->curselect && !view->held )
+ return;
+
+ gui_editor_2dview_input_select_drag( view );
+}
+
+void gui_editor_2dview_input_select_origin( GUI_EDITOR_2DVIEW* view ) {
+ WORLD_MAP* m = editor->map;
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ VEC2 mpos = { (F32)mx, (F32)my };
+ VEC2 world = { m->startpos.x, m->startpos.y };
+ VEC2 screen = gui_editor_2dview_world_to_screen( view, world );
+
+ I32 w, h;
+ gui_draw_get_str_bounds( &w, &h, FNT_JPN12, "(origin)" );
+
+ if( fabsf( (F32)mx - screen.x ) < (w * .5f + 2.f)
+ && fabsf( (F32)my - h * .5f - screen.y ) < (h * .5f + 2.f) ) {
+ gui_editor_2dview_select( view, m, EDITOR_SELECT_ORIGIN );
+ }
+
+ if( !view->curselect && !view->held )
+ return;
+
+ gui_editor_2dview_input_select_drag( view );
+}
+
+void gui_editor_2dview_input_tool_select( GUI_EDITOR_2DVIEW* view ) {
+ U8 m1 = gui_mbutton_down( 0 );
+ U8 m2 = !m1 && gui_mbutton_down( 1 );
+
+ if( gui_editor_2dview_input_drag( view, m2 ) ) {
+ return;
+ }
+
+ view->curselect = 0;
+ view->seltype = EDITOR_SELECT_NONE;
+ gui_editor_2dview_input_select_origin( view );
+ if( view->curselect ) return;
+ gui_editor_2dview_input_select_sprites( view );
+ if( view->curselect ) return;
+ gui_editor_2dview_input_select_walls( view );
+ if( view->curselect ) return;
+ gui_editor_2dview_input_select_polygons( view );
+}
+
+void gui_editor_2dview_input_scroll( GUI_EDITOR_2DVIEW* view ) {
+ U8 scroll = gui_mbutton_down( GUI_MBTNSCROLL );
+ gui_capture_scroll();
+
+ if( scroll == (U8)-1 && view->scale > 0.5f ) {
+ view->scale *= 0.75f;
+ }
+ else if( scroll == 1 && view->scale < 16.f ) {
+ view->scale *= 1.25f;
+ }
+}
+
+void gui_editor_2dview_input_tool_wall( GUI_EDITOR_2DVIEW* view ) {
+ U8 m1 = gui_mbutton_down( 0 );
+ if( !m1 ) {
+ view->held = 0;
+ return;
+ }
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my );
+ if( !view->held ) {
+ MAP_WALL neww;
+ neww.start = neww.end = {
+ m_snap_to_grid( world.x, editor->grid ),
+ m_snap_to_grid( world.y, editor->grid ),
+ 0
+ };
+ neww.end.z = 80.f;
+ neww.propid = 0;
+
+ I32 idx = editor->map->walls.size;
+ editor->map->walls.push( neww );
+ view->curdrag = &editor->map->walls[idx].end;
+ view->held = 1;
+
+ view->oldmx = mx;
+ view->oldmy = my;
+ view->mremainx = 0.0;
+ view->mremainy = 0.0;
+ return;
+ }
+
+ gui_editor_2dview_input_select_drag_vertex( view );
+}
+
+void gui_editor_2dview_input_tool_poly( GUI_EDITOR_2DVIEW* view ) {
+ U8 m1 = gui_mbutton_down( 0 );
+ if( !m1 ) {
+ view->held = 0;
+ return;
+ }
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my );
+ if( !view->held ) {
+ MAP_POLYGON newp;
+ newp.propid = 0;
+ newp.vertices.push( { .pos = { world.x, world.y, 0.f }, .uv = { 1, 0 } } );
+ newp.vertices.push( { .pos = { world.x, world.y, 0.f }, .uv = { 0, 0 } } );
+ newp.vertices.push( { .pos = { world.x, world.y, 0.f }, .uv = { 0, 1 } } );
+ newp.vertices.push( { .pos = { world.x, world.y, 0.f }, .uv = { 1, 1 } } );
+
+ map_polygon_calc_bounds( &newp );
+
+ I32 idx = editor->map->polygons.size;
+ editor->map->polygons.push( newp );
+ view->curdrag = &editor->map->polygons[idx];
+ view->held = 1;
+
+ view->oldmx = mx;
+ view->oldmy = my;
+ view->mremainx = 0.0;
+ view->mremainy = 0.0;
+ return;
+ }
+
+ MAP_POLYGON* p = (MAP_POLYGON*)view->curdrag;
+ MAP_VERTEX
+ *tl = &p->vertices[1],
+ *tr = &p->vertices[0],
+ *bl = &p->vertices[2],
+ *br = &p->vertices[3];
+
+ VEC3 start = tl->pos;
+ VEC3 end = br->pos;
+
+ for( U32 i = 0; i < 3; ++i ) {
+ start[i] = m_snap_to_grid( start[i], editor->grid );
+ end[i] = m_snap_to_grid( end[i], editor->grid );
+ }
+
+ VEC2 move = gui_editor_2dview_input_get_drag_vec( view );
+ if( !is_zero( move ) ) {
+ end += move;
+ tl->pos = { start.x, start.y, tl->pos.z };
+ tr->pos = { end.x, start.y, tr->pos.z };
+ bl->pos = { start.x, end.y, bl->pos.z };
+ br->pos = { end.x, end.y, br->pos.z };
+
+ map_polygon_calc_bounds( p );
+ gui_editor_2dview_input_select_onmove( view );
+ }
+}
+
+void gui_editor_2dview_input_tool_ent( GUI_EDITOR_2DVIEW* view ) {
+
+}
+
+void gui_editor_2dview_input_tool_sprite( GUI_EDITOR_2DVIEW* view ) {
+ U8 m1 = gui_mbutton_down( 0 );
+ if( !m1 ) {
+ view->held = 0;
+ return;
+ }
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my );
+ if( !view->held ) {
+ MAP_SPRITE news;
+ news.pos = { world.x, world.y, 20.f };
+ news.size = { 20.f, 20.f };
+ news.clr = { 1.f, 1.f, 1.f, 1.f };
+ news.tex = 0;
+
+ I32 idx = editor->map->walls.size;
+ editor->map->sprites.push( news );
+ view->curdrag = &editor->map->sprites[idx];
+ view->held = 1;
+
+ view->oldmx = mx;
+ view->oldmy = my;
+ view->mremainx = 0.0;
+ view->mremainy = 0.0;
+ return;
+ }
+
+ gui_editor_2dview_input_select_drag_sprite( view );
+}
+
+
+void gui_editor_2dview_input_tool_draw( GUI_EDITOR_2DVIEW* view ) {
+ U8 m1 = gui_mbutton_down( 0 );
+ U8 m2 = !m1 && gui_mbutton_down( 1 );
+
+ if( gui_editor_2dview_input_drag( view, m2 ) ) {
+ return;
+ }
+
+ switch( editor->tool ) {
+ case EDITOR_TOOL_WALL: return gui_editor_2dview_input_tool_wall( view );
+ case EDITOR_TOOL_POLY: return gui_editor_2dview_input_tool_poly( view );
+ case EDITOR_TOOL_ENT: return gui_editor_2dview_input_tool_ent( view );
+ case EDITOR_TOOL_SPRITE: return gui_editor_2dview_input_tool_sprite( view );
+ default: return;
+ }
+}
+
+void gui_editor_2dview_input_fn( void* ptr ) {
+ GUI_EDITOR_2DVIEW* view = (GUI_EDITOR_2DVIEW*)ptr;
+ if( !editor->map )
+ return;
+
+ I32 x = gui_relx( view );
+ I32 y = gui_rely( view ) + EDITORVIEW_TITLE_OFFSET;
+ I32 w = view->w;
+ I32 h = view->h;
+
+ U8 mouse = gui_mbutton_down( 0 ) || gui_mbutton_down( 1 );
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h;
+ if( inbounds )
+ gui_editor_2dview_input_scroll( view );
+
+ if( !mouse )
+ view->heldoutbounds = 0;
+ else if( mouse && !inbounds )
+ view->heldoutbounds = 1;
+
+ if( view->heldoutbounds )
+ return;
+
+ switch( editor->tool ) {
+ case EDITOR_TOOL_SELECT: return gui_editor_2dview_input_tool_select( view );
+ case EDITOR_TOOL_WALL:
+ case EDITOR_TOOL_POLY:
+ case EDITOR_TOOL_SPRITE:
+ case EDITOR_TOOL_ENT:
+ return gui_editor_2dview_input_tool_draw( view );
+ default: return gui_editor_2dview_input_tool_none( view );
+ }
+}
+
+GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h ) {
+ GUI_EDITOR_2DVIEW* view = new GUI_EDITOR_2DVIEW;
+ view->x = x;
+ view->y = y;
+ view->xbound = view->w = w;
+ view->h = h;
+ view->ybound = h + EDITORVIEW_TITLE_OFFSET;
+
+ view->draw_fn = gui_editor_2dview_draw_fn;
+ view->input_fn = gui_editor_2dview_input_fn;
+ strcpy( view->name, "EDITOR_2D_VIEW" );
+
+ view->scale = 1.f;
+
+ GUI_BASE* parent = gui_get_view();
+ if( !parent )
+ parent = gui_get_window();
+
+ parent->children.push( view );
+ view->parent = parent;
+
+ return view;
+}
diff --git a/src/editor/view3d.cpp b/src/editor/view3d.cpp
new file mode 100644
index 0000000..f9bd60e
--- /dev/null
+++ b/src/editor/view3d.cpp
@@ -0,0 +1,88 @@
+#include "editor.h"
+
+#include "../game/objlist.h"
+#include "../game/world/draw.h"
+#include "../game/world/bsp_draw.h"
+#include "../game.h"
+
+const I32 EDITORVIEW_TITLE_OFFSET = 15;
+
+void gui_editor_3dview_draw_showpos( GUI_EDITOR_3DVIEW* view ) {
+ I32 x = gui_relx( view );
+ I32 y = gui_rely( view ) + EDITORVIEW_TITLE_OFFSET;
+
+ VEC3 pos = objl->pl->pos;
+
+ gui_draw_str(
+ x + view->w - 2, y + 2,
+ ALIGN_R,
+ FNT_JPN12,
+ ui_clr.txt,
+ "pos: %.02f %.02f %.02f",
+ pos.x, pos.y, pos.z
+ );
+
+ gui_draw_str(
+ x + view->w - 2, y + 18,
+ ALIGN_R,
+ FNT_JPN12,
+ ui_clr.txt,
+ "rot: %.02f %.02f",
+ objl->pl->rot.y,
+ objl->pl->rot.x
+ );
+}
+
+void gui_editor_3dview_draw_fn( void* ptr ) {
+ GUI_EDITOR_3DVIEW* view = (GUI_EDITOR_3DVIEW*)ptr;
+
+ if( !objl->pl )
+ return;
+
+ I32 x = gui_relx( view );
+ I32 y = gui_rely( view );
+
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "3d view" );
+ y += EDITORVIEW_TITLE_OFFSET;
+
+ VEC2 wnd = { (F32)x, (F32)y };
+ VEC2 winsize = { (F32)view->w, (F32)view->h };
+
+ CLR col = gui_is_fg_window( view )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x-1, y-1, view->w+2, view->h+2, col );
+ gui_draw_frect( x, y, view->w, view->h, CLR::BLACK() );
+ if( editor->drawbsp && editor->map->bsp )
+ bsp_draw( editor->map->bsp, editor->game, wnd, winsize );
+ else
+ world_draw( editor->game, objl->world, wnd, winsize );
+
+ gui_editor_3dview_draw_showpos( view );
+}
+
+void gui_editor_3dview_input_fn( void* ptr ) {
+ if( !objl->pl )
+ return;
+
+ game_on_tick( editor->game );
+}
+
+GUI_EDITOR_3DVIEW* gui_editor_3dview( I32 x, I32 y, I32 w, I32 h ) {
+ GUI_EDITOR_3DVIEW* view = new GUI_EDITOR_3DVIEW;
+ view->x = x;
+ view->y = y;
+ view->xbound = view->w = w;
+ view->h = h;
+ view->ybound = h + EDITORVIEW_TITLE_OFFSET;
+
+ view->draw_fn = gui_editor_3dview_draw_fn;
+ view->input_fn = gui_editor_3dview_input_fn;
+ strcpy( view->name, "EDITOR_3D_VIEW" );
+
+ GUI_BASE* parent = gui_get_view();
+ if( !parent )
+ parent = gui_get_window();
+
+ parent->children.push( view );
+ view->parent = parent;
+ return view;
+}
diff --git a/src/game.cpp b/src/game.cpp
new file mode 100644
index 0000000..bd3c4c8
--- /dev/null
+++ b/src/game.cpp
@@ -0,0 +1,149 @@
+#include <unistd.h>
+#include "game.h"
+
+#include "game/world/draw.h"
+#include "render/gl_2d.h"
+#include "render/gl_3d.h"
+#include "render/gl_2d_font.h"
+
+#include "editor/editor.h"
+#include "game/assets.h"
+#include "game/vars.h"
+#include "game/objlist.h"
+#include "game/world/map.h"
+
+#include "gui.h"
+#include "render/gl_batch.h"
+#include "util.h"
+
+
+GAME_DATA* game_init( GL_DATA* gl ) {
+ GAME_DATA* game = (GAME_DATA*)malloc( sizeof( GAME_DATA ) );
+ memset( game, 0, sizeof(GAME_DATA) );
+ game->gl = gl;
+
+ VEC2 screensize = { (F32)gl->canvas_size[0], (F32)gl->canvas_size[1] };
+
+ game->shaders.gl2d = gl_2d_init( gl, screensize, "2d" );
+ game->shaders.gl2d_texcoord = gl_2d_init( gl, screensize, "2d_texcoord" );
+ game->shaders.gl3d = gl_3d_init( gl, screensize, "3d" );
+
+ game_create_batches( game );
+
+ assets_init( game );
+ assets_on_frame( game );
+ varl = vars_init();
+ objl = objl_init();
+
+#if IS_EDITOR
+ game->editor = editor_create( game );
+#else
+ gui_create( game );
+ game->state.ingame = 1;
+#endif
+
+ return game;
+}
+
+void game_create_batches( GAME_DATA *game ) {
+ game->render.batch_3d = gl_batch_create( game->gl, game->shaders.gl3d, &gl_3d_batch_setup );
+}
+
+#ifdef DEBUG
+void game_draw_fpsoverlay( GAME_DATA* game ) {
+ GL_DATA* gl = game->gl;
+ char buf[256];
+ sprintf( buf, "fps: %1.3f (%2.5f ms) %dx%d", gl->fps, gl->frametime, canvas[0], canvas[1] );
+
+ VEC2 dim = gl_font_dim( game->assets.font, buf );
+ gl_font_draw(
+ game->assets.font,
+ game->shaders.gl2d_texcoord,
+ s_tr({ -dim.x - 30.f, dim.y + 5.f }),
+ buf,
+ (CLR){ 1.f, 1.f, 1.f, 1.f }
+ );
+}
+
+void game_draw_fontoverlay( GAME_DATA* game ) {
+ for( U32 i = 0; i < 255; ++i ) {
+ char str[64];
+ sprintf( str, "%d:%c", i, (char)i );
+ gl_font_draw(
+ game->assets.jpn12,
+ game->shaders.gl2d_texcoord,
+ s_tl({ 10.f + ( i % 18 ) * 33,
+ 280.f + floorf( i / 18.f ) * 14.f }),
+ str,
+ CLR::WHITE()
+ );
+ }
+}
+#endif
+
+WORLD_MAP* game_load_map( GAME_DATA* game, const char* mapname ) {
+ WORLD_MAP* m = map_from_file( game, mapname );
+ if( !m ) {
+ dlog( "game_load_map() : failed to load file %s", mapname );
+ return 0;
+ }
+
+ objl_load_world( game, m );
+ game->state.map = m;
+ return m;
+}
+
+void game_unload_map( GAME_DATA* game ) {
+ map_free( game, game->state.map );
+ game->state.map = 0;
+
+ objl_clear( 1 );
+}
+
+void game_draw( GAME_DATA* game ) {
+ if( !objl->pl || !objl->world ) {
+ game_load_map( game, "../assets/maps/test.hmap" );
+ }
+
+ VEC2 window = { 270.f, 25.f };
+ VEC2 winsize = { 480.f, 360.f };
+ gl_2d_rect( game->shaders.gl2d, window, winsize, (CLR){ 0.3f, 0.3f, 0.3f, 1.f } );
+ world_draw( game, objl->world, window, winsize );
+}
+
+void game_main_loop( GAME_DATA* game ) {
+ GL_DATA* gl = game->gl;
+ GL_SHADER_PROGRAM* gl2d = game->shaders.gl2d;
+
+ if( !assets_on_frame( game ) )
+ return;
+
+ gl_beginframe( gl );
+ gl_2d_frect( gl2d, s_tl(), s_br(), { 0.f, 0.f, 0.f, 1.f } );
+
+ if( game->state.ingame ) {
+ game_on_tick( game );
+ game_draw( game );
+ }
+
+ gui_onframe( game );
+
+#ifdef DEBUG
+ game_draw_fpsoverlay( game );
+#endif
+
+ if( !OK( gl_endframe( gl ) ) )
+ exit( 0 );
+}
+
+void game_on_tick( GAME_DATA* game ) {
+ U64 tick = u_tick();
+ if( tick - game->state.last_tick > (U64)(TICK_INTERVAL * 10000) ) {
+ player_input( game, objl->pl );
+ game->state.last_tick = tick;
+ }
+}
+
+void game_destroy( GAME_DATA *game ) {
+ free( game );
+}
diff --git a/src/game.h b/src/game.h
new file mode 100644
index 0000000..b0307a1
--- /dev/null
+++ b/src/game.h
@@ -0,0 +1,60 @@
+#pragma once
+#define IS_EDITOR 1
+
+#include "game/assets.h"
+#include "render/gl.h"
+#include "render/gl_3d.h"
+
+#ifdef IS_EDITOR
+#include "editor/editor.h"
+#else
+#include "gui.h"
+#endif
+
+// HARDCODE BOSS
+const U32 TICKS_PER_SEC = 64;
+const F32 TICK_INTERVAL = 1.0f / (F32)TICKS_PER_SEC;
+
+typedef struct {
+ GL_SHADER_PROGRAM* gl2d;
+ GL_SHADER_PROGRAM* gl2d_texcoord;
+
+ struct GL_3D* gl3d;
+} GAME_SHADERS;
+
+typedef struct {
+ GL_BATCH3D* batch_3d;
+} GAME_RENDER;
+
+typedef struct {
+ U8 ingame;
+ WORLD_MAP* map;
+ U64 last_tick;
+} GAME_STATE;
+
+struct GAME_DATA {
+ GL_DATA* gl;
+ GAME_SHADERS shaders;
+ GAME_ASSETS assets;
+
+ GAME_STATE state;
+ GAME_RENDER render;
+
+#ifdef IS_EDITOR
+ GAME_EDITOR* editor;
+#endif
+};
+
+extern void ui_draw_background( GAME_DATA* game, VEC2 pos, VEC2 dim, F32 progress, CLR col );
+
+extern GAME_DATA* game_init( GL_DATA* gl );
+extern void game_create_batches( GAME_DATA* game );
+extern void game_main_loop( GAME_DATA* game );
+extern void game_on_tick( GAME_DATA* game );
+extern void game_destroy( GAME_DATA* game );
+
+// loads map and populates entities
+extern WORLD_MAP* game_load_map( GAME_DATA* game, const char* mapname );
+
+// unloads map and clears entity list including local player (no more level transitions can be made)
+extern void game_unload_map( GAME_DATA* game );
diff --git a/src/game/assets.cpp b/src/game/assets.cpp
new file mode 100644
index 0000000..7d755c0
--- /dev/null
+++ b/src/game/assets.cpp
@@ -0,0 +1,78 @@
+#include "assets.h"
+
+#include "../game.h"
+#include "../render/gl_2d_font.h"
+
+void assets_init( GAME_DATA *game ) {
+ game->assets.test = gl_texture_create( game->gl, "assets/test.png" );
+ game->assets.fonts_init = 0;
+}
+
+void assets_create_fonts( struct GAME_DATA *game ) {
+ dlog( "creating fonts\n" );
+
+ game->gl->fonts.each( fn( GL_FONT** font ) {
+ gl_font_destroy( game->gl, *font );
+ });
+
+ game->gl->fonts.clear();
+ GAME_ASSETS* a = &game->assets;
+ a->font = gl_font_create( game->gl, "cour.ttf", 16 );
+ a->jpn12 = gl_font_create( game->gl, "jpn12.ttf", 12 );
+ a->jpn16 = gl_font_create( game->gl, "jpn16.ttf", 16 );
+}
+
+U8 assets_on_frame( struct GAME_DATA *game ) {
+ thread_mutex_lock( &font_mutex );
+ if( !game->assets.fonts_init ) {
+ assets_create_fonts( game );
+ game->assets.fonts_init = 1;
+ thread_mutex_unlock( &font_mutex );
+ return 0;
+ }
+
+ thread_mutex_unlock( &font_mutex );
+ return 1;
+}
+
+LIST<FILE_ENTRY> assets_get_files_by_ext_dir( const char** extensions, const char* dirname ) {
+ LIST<FILE_ENTRY> ret;
+ LIST<FILE_ENTRY> dir = dir_get_entries( dirname );
+
+ dir.each( fn( FILE_ENTRY* e ) {
+ char fullpath[256];
+ sprintf( fullpath, "%s/%s", dirname, e->name );
+ if( e->dir ) {
+ LIST<FILE_ENTRY> subdir = assets_get_files_by_ext_dir( extensions, fullpath );
+ subdir.each( fn( FILE_ENTRY* se ) { ret.push( *se ); } );
+ return;
+ }
+
+ U32 len = strlen( e->name );
+ for( U32 i = 0; !!extensions[i]; ++i ) {
+ const char* ext = extensions[i];
+ U32 extlen = strlen( ext );
+
+ if( extlen < len && e->name[len - extlen - 1] == '.' ) {
+ if( !strcmp( e->name + len - extlen, ext ) ) {
+ FILE_ENTRY ne{ .dir = 0 };
+ strcpy( ne.name, fullpath );
+ ret.push( ne );
+ }
+ }
+ }
+ } );
+
+ return ret;
+}
+
+LIST<FILE_ENTRY> assets_get_files_by_ext( const char** extensions ) {
+ return assets_get_files_by_ext_dir( extensions, "../assets" );
+}
+
+const char* assets_abspath( const char* filename ) {
+ while( !strncmp( filename, "../assets/", 10 ) )
+ filename += 10;
+
+ return filename;
+}
diff --git a/src/game/assets.h b/src/game/assets.h
new file mode 100644
index 0000000..d0fe270
--- /dev/null
+++ b/src/game/assets.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "../util/typedef.h"
+#include "../util/file.h"
+
+
+typedef struct {
+ struct GL_FONT* font;
+ struct GL_FONT* jpn12;
+ struct GL_FONT* jpn16;
+ struct GL_TEX2D* test;
+
+ U8 fonts_init;
+} GAME_ASSETS;
+
+extern void assets_init( struct GAME_DATA* game );
+extern void assets_create_fonts( struct GAME_DATA* game );
+// extensions is a null-terminated array of null-terminated strings.
+extern LIST<FILE_ENTRY> assets_get_files_by_ext_dir( const char** extensions, const char* dir );
+// extensions is a null-terminated array of null-terminated strings.
+extern LIST<FILE_ENTRY> assets_get_files_by_ext( const char** extensions );
+extern const char* assets_abspath( const char* filepath );
+
+extern U8 assets_on_frame( struct GAME_DATA* game );
diff --git a/src/game/object.cpp b/src/game/object.cpp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/game/object.cpp
diff --git a/src/game/object.h b/src/game/object.h
new file mode 100644
index 0000000..8191ab7
--- /dev/null
+++ b/src/game/object.h
@@ -0,0 +1,73 @@
+#pragma once
+#include "../util/allocator.h"
+#include "../util/vector.h"
+#include "../util/fnv.h"
+
+typedef void ( *OBJECT_DEINIT_FN )( struct OBJECT* obj );
+inline const char* obj_classid_to_name( U32 classid );
+
+enum ObjectClass_t {
+ OBJCLASS_NONE = 0,
+ OBJCLASS_WORLD,
+ OBJCLASS_TRIGGER,
+ OBJCLASS_PLAYER,
+ OBJCLASS_BASENPC,
+};
+
+static const char* OBJCLASS_NAMES[] = {
+ "none (invalid - FIXME)",
+ "world",
+ "trigger",
+ "player",
+ "base_npc",
+};
+
+// todo: come up with a good way to make these static
+struct OBJECT_PROP {
+ OBJECT_PROP( const char* name, void* value, U32 size, LIST<OBJECT_PROP*>* list, void* parent ) {
+ this->name = name;
+ this->size = size;
+ this->hash = fnv1a( name );
+ this->offset = (U32)( (U8*)value - (U8*)parent );
+ list->push( this );
+ }
+
+ const char* name;
+ FNV1A hash;
+ U32 offset;
+ U32 size;
+};
+
+#define OBJVAR( type, name ) \
+ type name; \
+ OBJECT_PROP __##name##_props{ #name, (void*)&name, sizeof(type), &this->props, this }
+
+#define OBJVAR_STR( name, len ) \
+ char name##[len]; \
+ OBJECT_PROP __##name##_props{ #name, (void*)name, len, &this->props, this }
+
+struct OBJECT {
+ static const U32 CLASSID = OBJCLASS_NONE;
+
+ U32 classid;
+ U8 keeponlevel{};
+ char name[64];
+
+ OBJECT* parent;
+ LIST<OBJECT*> children{};
+ LIST<OBJECT_PROP*> props{};
+ OBJECT_DEINIT_FN deinit_fn{};
+
+ OBJVAR( VEC3, pos );
+ OBJVAR( VEC3, rot );
+ OBJVAR( I32, idx );
+};
+
+inline const char* obj_classid_to_name( U32 classid ) {
+ if( classid > (sizeof(OBJCLASS_NAMES) / sizeof(OBJCLASS_NAMES[0])) ) {
+ dlog( "obj_classid_to_name() : invalid classid passed (%d)", classid );
+ return "INVALIDCLASSID";
+ }
+
+ return OBJCLASS_NAMES[classid];
+}
diff --git a/src/game/objlist.cpp b/src/game/objlist.cpp
new file mode 100644
index 0000000..01ce49b
--- /dev/null
+++ b/src/game/objlist.cpp
@@ -0,0 +1,44 @@
+#include "objlist.h"
+#include "world/world.h"
+#include "world/map.h"
+
+OBJECT_LIST* objl;
+
+OBJECT_LIST* objl_init() {
+ OBJECT_LIST* ret = new OBJECT_LIST;
+ ret->world = 0;
+ ret->pl = 0;
+
+ return ret;
+}
+
+void objl_clear( U8 delete_player ) {
+ objl->objects.each( fn( OBJECT** ptr ) {
+ OBJECT* obj = *ptr;
+
+ if( !delete_player && obj == objl->pl )
+ return;
+
+ if( obj->deinit_fn )
+ obj->deinit_fn( obj );
+ delete obj;
+ } );
+
+ objl->objects.clear();
+ objl->world = 0;
+ if( delete_player )
+ objl->pl = 0;
+}
+
+void objl_load_world( GAME_DATA* game, WORLD_MAP* map ) {
+ objl_clear();
+
+ objl->world = world_create( map );
+ if( !objl->pl )
+ objl->pl = player_create( map->startpos, map->startang );
+
+ if( !OK( world_populate_entities( objl->world ) ) ) {
+ objl_clear();
+ dlog( "objl_load_world() : error populating entities\n" );
+ }
+}
diff --git a/src/game/objlist.h b/src/game/objlist.h
new file mode 100644
index 0000000..f8a46d7
--- /dev/null
+++ b/src/game/objlist.h
@@ -0,0 +1,122 @@
+#pragma once
+
+#include "player.h"
+#include "world/world.h"
+
+const U32 OBJ_LIST_MAX = 8192;
+
+struct OBJECT_LIST {
+ template <typename T>
+ T* add( const char* name ) {
+ if( objects.size >= OBJ_LIST_MAX ) {
+ dlog( "OBJECT_LIST::add() : object list is full" );
+ return 0;
+ }
+
+ for( I32 i = 0; i < objects.size; ++i ) {
+ if( !strcmp( name, objects[i]->name ) ) {
+ dlog( "OBJECT_LIST::add() : object %s already exists", name );
+ return 0;
+ }
+ }
+
+ T* newt = new T;
+ newt->classid = T::CLASSID;
+ strcpy( newt->name, name );
+ objects.push( newt );
+ return newt;
+ }
+
+ template < typename T >
+ T* add( const T& obj ) {
+ for( I32 i = 0; i < objects.size; ++i ) {
+ if( !strcmp( obj.name, objects[i]->name ) ) {
+ dlog( "OBJECT_LIST::add() : object %s already exists", obj.name );
+ return 0;
+ }
+ }
+
+ T* newt = new T( obj );
+ newt->classid = T::CLASSID;
+ objects.push( newt );
+ return newt;
+ }
+
+ void remove( OBJECT* obj ) {
+ I32 idx = objects.idx_of( obj );
+ if( idx != -1 )
+ free( objects[idx] );
+ }
+
+ void remove( const char* obj ) {
+ for( I32 i = 0; i < objects.size; ++i ) {
+ if( !strcmp( obj, objects[i]->name ) ) {
+ free( objects[i] );
+ return;
+ }
+ }
+ }
+
+ template < typename T = OBJECT >
+ T* find( const char* name ) {
+ for( I32 i = 0; i < objects.size; ++i ) {
+ if( !strcmp( name, objects[i]->name ) )
+ return objects[i];
+ }
+
+ return 0;
+ }
+
+ void each( LIST<OBJECT*>::ON_EACH_FN func ) {
+ objects.each( func );
+ }
+
+ WORLD* world;
+ PLAYER* pl;
+ LIST<OBJECT*> objects;
+};
+
+extern OBJECT_LIST* objl;
+
+extern OBJECT_LIST* objl_init();
+extern void objl_clear( U8 delete_player = 0 );
+extern void objl_load_world( GAME_DATA* game, WORLD_MAP* map );
+
+
+template < typename T >
+inline T* obj_add( const char* name ) {
+ if( !objl ) {
+ dlog( "obj_add() : objl null\n" );
+ return 0;
+ }
+
+ return objl->add<T>( name );
+}
+
+template < typename T >
+inline T* obj_add( T obj ) {
+ T* newt = new T( obj );
+ newt = objl->add( newt );
+ return newt;
+}
+
+template < typename T >
+inline T* obj_add( OBJECT* obj, const char* name ) {
+ T* newt = objl->add<T>( name );
+ if( !newt )
+ return 0;
+
+ newt->parent = obj;
+ obj->children.push( newt );
+ return newt;
+}
+
+template < typename T >
+inline T* obj_add( OBJECT* obj, T newobj ) {
+ T* newt = objl->add( newobj );
+ if( !newt )
+ return 0;
+
+ newt->parent = obj;
+ obj->children.push( newt );
+}
diff --git a/src/game/physics/movement.h b/src/game/physics/movement.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/game/physics/movement.h
diff --git a/src/game/player.cpp b/src/game/player.cpp
new file mode 100644
index 0000000..87786df
--- /dev/null
+++ b/src/game/player.cpp
@@ -0,0 +1,82 @@
+#include "../game.h"
+#include "SDL_scancode.h"
+#include "objlist.h"
+#include "world/trace.h"
+#include "player.h"
+
+F32 PLAYER_HULL_HEIGHT = 50.f;
+F32 PLAYER_HULL_WIDTH = 32.f;
+
+PLAYER* player_create( VEC3 origin, F32 yaw ) {
+ PLAYER* p = obj_add<PLAYER>( "localplayer" );
+ p->pos = origin;
+ p->rot = { 0, yaw, 0 };
+ p->health = 100;
+ p->keeponlevel = 1;
+ p->velocity = {};
+
+ p->mins = {
+ -PLAYER_HULL_WIDTH * .5f,
+ -PLAYER_HULL_WIDTH * .5f,
+ 0
+ };
+
+ p->maxs = {
+ PLAYER_HULL_WIDTH * .5f,
+ PLAYER_HULL_WIDTH * .5f,
+ PLAYER_HULL_HEIGHT
+ };
+
+ return p;
+}
+
+void player_input( GAME_DATA* game, PLAYER* p ) {
+ F32* yaw = &p->rot.y;
+ VEC3* pos = &p->pos;
+
+ if( input.keys[(U8)'a'] )
+ *yaw -= 360.f * TICK_INTERVAL * 0.5f;
+ if( input.keys[(U8)'d'] )
+ *yaw += 360.f * TICK_INTERVAL * 0.5f;
+
+ if( input.keys[SDL_SCANCODE_UP] ) {
+ if( p->rot.x < 89.f )
+ p->rot.x += 360.f * TICK_INTERVAL * 0.5f;
+ }
+ if( input.keys[SDL_SCANCODE_DOWN] ) {
+ if( p->rot.x > -89.f )
+ p->rot.x -= 360.f * TICK_INTERVAL * 0.5f;
+ }
+
+ *yaw = remainderf( *yaw, 360.f );
+
+
+ VEC3 wishmove;
+ VEC3 dir = m_radial_offset( *yaw, 70.f );
+ if( input.keys[(U8)'w'] ) {
+ wishmove = dir * TICK_INTERVAL;
+ }
+ if( input.keys[(U8)'s'] ) {
+ wishmove = dir * -TICK_INTERVAL;
+ }
+
+ // todo : should never be false
+ if( vec_len( wishmove ) > BSP_TRACE_EPSILON && game->state.map->bsp ) {
+ BSP_TRACE tr{};
+ AABB aabb{};
+ tr.in_start = *pos;
+ tr.in_end = *pos + wishmove * 2.f;
+ aabb.min = p->mins;
+ aabb.max = p->maxs;
+
+ bsp_trace( &tr, game->state.map->bsp, aabb );
+ if( !tr.hit )
+ *pos += wishmove;
+ else
+ *pos += wishmove * tr.frac;
+ }
+}
+
+VEC3 player_get_view_pos( PLAYER* p ) {
+ return VEC3( p->pos.x, p->pos.y, p->pos.z + p->eyeoffset );
+}
diff --git a/src/game/player.h b/src/game/player.h
new file mode 100644
index 0000000..71b1c02
--- /dev/null
+++ b/src/game/player.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "object.h"
+
+struct PLAYER_INPUT {
+
+};
+
+struct PLAYER : OBJECT {
+ static const U32 CLASSID = OBJCLASS_PLAYER;
+ U32 health;
+ F32 fov{72.f};
+ F32 eyeoffset{40.f};
+
+ VEC3 mins;
+ VEC3 maxs;
+
+ VEC3 velocity;
+};
+
+extern PLAYER* player_create( VEC3 origin, F32 yaw );
+extern void player_input( struct GAME_DATA* game, PLAYER* player );
+extern VEC3 player_get_view_pos( PLAYER* player );
diff --git a/src/game/raycast.cpp b/src/game/raycast.cpp
new file mode 100644
index 0000000..c82a3ed
--- /dev/null
+++ b/src/game/raycast.cpp
@@ -0,0 +1,162 @@
+#include <cmath>
+#include <math.h>
+
+#include "raycast.h"
+#include "../game.h"
+
+#include "../render/gl_2d.h"
+
+#include "objlist.h"
+#include "player.h"
+#include "world/world.h"
+#include "world/map.h"
+
+const U32 RAY_DEPTH_MAX = 8;
+
+U8 line_intersects( VEC3 ray_start, VEC2 ray_dir, MAP_WALL* s, VEC3* p, F32* t ) {
+ VEC2 v1 = VEC2{ ray_start.x, ray_start.y } - VEC2{ s->start.x, s->start.y };
+ VEC2 v2 = VEC2{ s->end.x, s->end.y } - VEC2{ s->start.x, s->start.y };
+ VEC2 v3 = { -ray_dir.y, ray_dir.x };
+
+ F32 dot = vec_dot( v2, v3 );
+ if( fabsf(dot) < 0.001f )
+ return 0;
+
+ F32 t1 = vec_cross( v2, v1 ) / dot;
+ F32 t2 = vec_dot( v1, v3 ) / dot;
+
+ if( t1 >= 0.0f && ( t2 >= 0.0f && t2 <= 1.0f ) ) {
+ *t = t1;
+ *p = ray_start + VEC3{ ray_dir } * (*t);
+ return 1;
+ }
+
+ return 0;
+}
+
+F32 ray_calc_wall_hit_dir( VEC3 start, VEC3 end, F32 ang ) {
+ VEC3 walld = start - end;
+ vec_normalize( &walld );
+ F32 wallang = atan2f( walld.y, walld.x );
+ wallang += ang;
+ if( wallang < 0.f ) wallang += 2.f * M_PI;
+ if( wallang > 2.f * M_PI ) wallang -= 2.f * M_PI;
+ return wallang;
+}
+
+LIST<RAY_HITDATA> ray_trace_list( VEC3 start, F32 ang, U32 max_iter ) {
+ WORLD* w = objl->world;
+ if( !w ) return {};
+ LIST<RAY_HITDATA> hits;
+
+ F32 rayang = m_deg2rad( ang );
+ VEC2 raydir = { cos( rayang ), sin( rayang ) };
+ VEC3 minp;
+
+ for( U32 i = 0; i < w->map->walls.size; ++i ) {
+ VEC3 p;
+ F32 dist;
+ if( line_intersects( start, raydir, &w->map->walls[i], &p, &dist ) ) {
+ dist = vec_dist( start, p );
+
+ VEC3 start = w->map->walls[i].start;
+ VEC3 end = w->map->walls[i].end;
+ F32 hitang = ray_calc_wall_hit_dir( start, end, ang );
+ hits.push( RAY_HITDATA{ p, dist, hitang, TH_WALL, i } );
+ if( hits.size > max_iter )
+ break;
+ }
+ }
+
+ return hits;
+}
+
+U32 ray_trace( TRACE* tr, F32 ang ) {
+ F32 rayang = m_deg2rad( ang );
+ VEC3 start = tr->startpos;
+ VEC3 raydir = { cos( rayang ), sin( rayang ), 0.f };
+ raydir *= RAY_DEPTH_MAX;
+
+ return ray_trace( tr, raydir );
+}
+
+U32 ray_trace( TRACE* tr, VEC3 end ) {
+ WORLD* w = objl->world;
+ if( !w ) return TRACE_HIT_NONE;
+ WORLD_MAP* map = w->map;
+
+ VEC3 start = tr->startpos;
+ VEC3 diff = end - start;
+ F32 trdist = vec_lensq( diff );
+ vec_normalize( &diff );
+ VEC2 raydir = { diff.x, diff.y };
+ F32 ang = atan2f( raydir.y, raydir.x );
+
+ F32 mindist = INFINITY;
+ VEC3 minp;
+ U32 hitwall = TRACE_HIT_NONE;
+
+ for( U32 i = 0; i < map->walls.size; ++i ) {
+ VEC3 p;
+ F32 dist;
+
+ if( line_intersects( start, raydir, &map->walls[i], &p, &dist ) ) {
+ if( dist <= trdist && dist < mindist ) {
+ mindist = dist;
+ minp = p;
+ hitwall = i;
+ }
+ }
+ }
+
+ if( hitwall != -1 ) {
+ tr->endpos = { minp.x, minp.y, 0.0f };
+ tr->hitwall = hitwall;
+
+ VEC3 start = map->walls[hitwall].start;
+ VEC3 end = map->walls[hitwall].end;
+ tr->hitang = ray_calc_wall_hit_dir( start, end, ang );
+ return hitwall;
+ }
+
+ tr->hitwall = -1;
+ tr->fract = -1.f;
+ tr->endpos = { start.x + RAY_DEPTH_MAX * raydir.x, start.y + RAY_DEPTH_MAX * raydir.y, 0.0f };
+ return TRACE_HIT_NONE;
+}
+
+void draw_player( GAME_DATA* game, PLAYER* pl ) {
+ GL_PROGRAM* gl2d = game->shaders.gl2d;
+ VEC2 xy = { pl->pos.x, pl->pos.y };
+
+ gl_2d_frect( gl2d, xy - VEC2{ 5.f, 5.f }, { 10.f, 10.f }, CLR::RED() );
+}
+
+void ray_draw_world2d( GAME_DATA* game, WORLD* world ) {
+ for( U32 i = 0; i < world->map->polygons.size; ++i ) {
+ MAP_POLYGON* p = &world->map->polygons[i];
+
+ LIST<VERTEX> verts{};
+ SURF_PROPS* props = polygon_get_props( world->map, p );
+ p->vertices.each( fn( MAP_VERTEX* mv ) {
+ VERTEX v{};
+ v.pos = VEC2{ mv->pos.x, mv->pos.y };
+ v.uv = mv->uv;
+ v.clr = props->clr;
+ verts.push( v );
+ } );
+
+ if( props->tex )
+ gl_textured_polygon( game->shaders.gl2d_texcoord, verts.data, verts.size, props->tex );
+ else
+ gl_polygon( game->shaders.gl2d, verts.data, verts.size );
+ }
+
+ for( U32 i = 0; i < world->map->walls.size; ++i ) {
+ MAP_WALL* s = &world->map->walls[i];
+ SURF_PROPS* p = &world->map->props[s->propid];
+ gl_2d_line( game->shaders.gl2d, VEC2( s->start.x, s->start.y ), VEC2( s->end.x, s->end.y ), p->clr );
+ }
+
+ draw_player( game, objl->pl );
+}
diff --git a/src/game/raycast.h b/src/game/raycast.h
new file mode 100644
index 0000000..5920e09
--- /dev/null
+++ b/src/game/raycast.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "../util.h"
+
+const static U32 RAY_ITER_MAX = 10;
+const static U32 TRACE_HIT_NONE = (U32)-1;
+
+enum TraceHitType_t {
+ TH_NONE,
+ TH_WALL,
+ TH_CEILING,
+ TH_FLOOR
+};
+
+enum TraceFilter_t {
+ TF_NONE = 0x0,
+ TF_WALLS = 0x1,
+ TF_SPRITES = 0x2,
+ TF_ENTITIES = 0x4,
+
+ TF_WORLD = TF_WALLS | TF_SPRITES,
+ TF_ALL = TF_WALLS | TF_SPRITES | TF_ENTITIES,
+};
+
+struct TRACE {
+ /* input */
+ VEC3 startpos;
+ /* output*/
+ VEC3 endpos;
+ /* output */
+ F32 fract;
+ /* output */
+ F32 hitang;
+ /* output */
+ U32 hitwall;
+ /* input */
+ U32 filter;
+};
+
+struct RAY_HITDATA {
+ VEC3 point;
+ F32 dist;
+ F32 ang;
+
+ U8 type;
+ U32 obj_idx;
+};
+
+extern LIST<RAY_HITDATA> ray_trace_list( VEC3 start, F32 ang, U32 max_iter = RAY_ITER_MAX );
+extern LIST<RAY_HITDATA> ray_trace_list( VEC3 start, VEC3 end, U32 max_iter = RAY_ITER_MAX );
+
+extern U32 ray_trace( TRACE* tr, F32 ang );
+extern U32 ray_trace( TRACE* tr, VEC3 end );
+extern U32 ray_trace( TRACE* tr, VEC3 start, F32 ang );
+extern U32 ray_trace( TRACE* tr, VEC3 start, VEC3 end );
+
+extern VEC2 project_vertex(
+ VEC3 vertex_pos,
+ VEC3 player_pos,
+ F32 player_angle_deg,
+ F32 fov_deg,
+ VEC2 window,
+ VEC2 winsize,
+ U8* in_view = 0
+);
diff --git a/src/game/vars.cpp b/src/game/vars.cpp
new file mode 100644
index 0000000..d7b1f9b
--- /dev/null
+++ b/src/game/vars.cpp
@@ -0,0 +1,115 @@
+#include "vars.h"
+#include "../util.h"
+#include <string.h>
+
+VAR_LIST* varl;
+
+VAR_LIST* vars_init() {
+ VAR_LIST* ret = new VAR_LIST;
+ return ret;
+}
+
+CVAR* var_new( const char* name, F32 v ) {
+ if( !varl ) {
+ dlog( "var_new() : error: varl null\n" );
+ return 0;
+ }
+
+ CVAR* ret = new CVAR;
+ memset( ret, 0 , sizeof( CVAR ) );
+ ret->type = CVAR_TYPE_FLOAT;
+ strcpy( ret->name, name );
+ ret->fl_v = v;
+
+ varl->vars.push( ret );
+ return ret;
+}
+
+CVAR* var_new( const char* name, I32 i ) {
+ if( !varl ) {
+ dlog( "var_new() : error: varl null\n" );
+ return 0;
+ }
+
+ CVAR* ret = new CVAR;
+ memset( ret, 0 , sizeof( CVAR ) );
+ ret->type = CVAR_TYPE_INT;
+ strcpy( ret->name, name );
+ ret->i_v = i;
+
+ varl->vars.push( ret );
+ return ret;
+}
+
+CVAR* var_new( const char* name, CLR c ) {
+ if( !varl ) {
+ dlog( "var_new() : error: varl null\n" );
+ return 0;
+ }
+
+ CVAR* ret = new CVAR;
+ memset( ret, 0 , sizeof( CVAR ) );
+ ret->type = CVAR_TYPE_COLOR;
+ strcpy( ret->name, name );
+ ret->clr_v = c;
+
+ varl->vars.push( ret );
+ return ret;
+}
+
+CVAR* var_new( const char* name, CVAR_FUNC func ) {
+ if( !varl ) {
+ dlog( "var_new() : error: varl null\n" );
+ return 0;
+ }
+
+ CVAR* ret = new CVAR;
+ memset( ret, 0 , sizeof( CVAR ) );
+ ret->type = CVAR_TYPE_FN;
+ strcpy( ret->name, name );
+ ret->fn_v = func;
+
+ varl->vars.push( ret );
+ return ret;
+}
+
+CVAR* var_new( const char* name, const char* v ) {
+ if( !varl ) {
+ dlog( "var_new() : error: varl null\n" );
+ return 0;
+ }
+
+ CVAR* ret = new CVAR;
+ memset( ret, 0, sizeof( CVAR ) );
+ ret->type = CVAR_TYPE_STRING;
+ strcpy( ret->name, name );
+
+ varl->vars.push( ret );
+ return ret;
+}
+
+CVAR* var_find( const char* name ) {
+ if( !varl ) {
+ dlog( "var_new() : error: varl null\n" );
+ return 0;
+ }
+
+ U32 len = strlen( name );
+ for( U32 i = 0; i < varl->vars.size; ++i ) {
+ U32 len2 = strlen( varl->vars[i]->name );
+ if( strncmp( varl->vars[i]->name, name, min( len, len2 ) ) == 0 ) {
+ return varl->vars[i];
+ }
+ }
+
+ return 0;
+}
+
+I32 var_call( CVAR *cmdvar, const char *cmdline ) {
+ if( cmdvar->type != CVAR_TYPE_FN ) {
+ dlog( "var_call() : error: not a function\n" );
+ return -1;
+ }
+
+ return cmdvar->fn_v( cmdvar, cmdline );
+}
diff --git a/src/game/vars.h b/src/game/vars.h
new file mode 100644
index 0000000..395c470
--- /dev/null
+++ b/src/game/vars.h
@@ -0,0 +1,50 @@
+#pragma once
+#include "../util.h"
+
+enum CvarTypes_t {
+ CVAR_TYPE_INT,
+ CVAR_TYPE_FLOAT,
+ CVAR_TYPE_COLOR,
+ CVAR_TYPE_STRING,
+ CVAR_TYPE_FN,
+ CVAR_TYPE_LAST
+};
+
+typedef I32(*CVAR_FUNC)( struct CVAR* self, const char* cmdline );
+
+struct CVAR {
+ char name[100];
+ U8 type;
+ union {
+ F32 fl_v;
+ I32 i_v;
+ CLR clr_v;
+ CVAR_FUNC fn_v;
+ char str_v[64];
+ };
+
+ union {
+ F32 fl_v;
+ I32 i_v;
+ CLR clr_v;
+ CVAR_FUNC fn_v;
+ char str_v[64];
+ } default_v;
+};
+
+struct VAR_LIST {
+ LIST<CVAR*> vars;
+};
+
+extern VAR_LIST* vars_init();
+
+extern CVAR* var_new( const char* name, F32 v );
+extern CVAR* var_new( const char* name, I32 v );
+extern CVAR* var_new( const char* name, CLR v );
+extern CVAR* var_new( const char* name, const char* v );
+extern CVAR* var_new( const char* name, CVAR_FUNC fn );
+
+extern CVAR* var_find( const char* name );
+extern I32 var_call( CVAR* cmdvar, const char* cmdline );
+
+extern VAR_LIST* varl;
diff --git a/src/game/world/bsp.cpp b/src/game/world/bsp.cpp
new file mode 100644
index 0000000..9aa94d7
--- /dev/null
+++ b/src/game/world/bsp.cpp
@@ -0,0 +1,662 @@
+#include "bsp.h"
+#include "../../util/math.h"
+#include "map.h"
+#include <climits>
+
+const F32 BSP_PORTAL_PLANE_SIZE = 64000.f;
+
+struct MAP_TRI {
+ MAP_VERTEX p0, p1, p2;
+};
+
+void bsp_face_gen_render_verts( BSP* bsp, BSP_FACE* f ) {
+ LIST<VERTEX3D> vertices;
+ vertices.reserve( f->verts.size );
+
+ SURF_PROPS* props = map_get_props( bsp->map, f->propid );
+
+ f->verts.each( fn( MAP_VERTEX* v ) {
+ VERTEX3D v3d;
+ v3d.pos = { v->pos.x, v->pos.z, v->pos.y };
+ v3d.uv = v->uv;
+ v3d.clr = v->clr * props->clr;
+ v3d.sampler = 0;
+ vertices.push( v3d );
+ } );
+
+ f->render_verts = triangle_fan_to_list( vertices.data, vertices.size );
+}
+
+void bsp_gen_render_vertices( BSP* bsp ) {
+ bsp->faces.each( fn( BSP_FACE* f ) {
+ bsp_face_gen_render_verts( bsp, f );
+ } );
+}
+
+LIST<BSP_PLANE> bsp_map_get_wall_planes( WORLD_MAP* map ) {
+ LIST<BSP_PLANE> ret{};
+
+ auto calc_wall_plane = fn( MAP_WALL* w ) {
+ VEC3 dir = w->end - w->start;
+ VEC3 normal = vec_normalize( { -dir.y, dir.x, 0.0f } );
+ F32 dist = vec_dot( normal, w->start );
+
+ BSP_PLANE p = { normal, dist };
+ for( U32 i = 0; i < ret.size; ++i ) {
+ if( ret[i] == p )
+ return;
+ }
+
+ ret.push( p );
+ };
+
+ map->walls.each( calc_wall_plane );
+ for( U32 i = 0; i < 4; ++i )
+ calc_wall_plane( &map->skybox.walls[i] );
+
+ return ret;
+}
+
+
+LIST<MAP_TRI> bsp_map_triangulate_planes( WORLD_MAP* map ) {
+ LIST<MAP_TRI> ret;
+
+ auto triangulate_plane = fn( MAP_POLYGON* p ) {
+ MAP_VERTEX* start = &p->vertices[0];
+ MAP_VERTEX* last = &p->vertices[1];
+ for( U32 i = 2; i < p->vertices.size; ++i ) {
+ MAP_TRI tri = MAP_TRI{
+ *start,
+ *last,
+ p->vertices[i]
+ };
+
+ last = &p->vertices[i];
+ ret.push( tri );
+ }
+ };
+
+ map->polygons.each( triangulate_plane );
+ triangulate_plane( &map->skybox.polygons[0] );
+ triangulate_plane( &map->skybox.polygons[1] );
+
+ return ret;
+}
+
+LIST<BSP_PLANE> bsp_map_get_tri_planes( WORLD_MAP* map ) {
+ LIST<BSP_PLANE> ret;
+ LIST<MAP_TRI> tris = bsp_map_triangulate_planes( map );
+
+ tris.each( fn( MAP_TRI* tri ) {
+ VEC3 cross = vec_cross( tri->p1.pos - tri->p0.pos, tri->p2.pos - tri->p0.pos );
+ VEC3 normal = vec_normalize( cross );
+ F32 dist = vec_dot( normal, tri->p0.pos );
+
+ BSP_PLANE p = { normal, dist };
+ for( U32 i = 0; i < ret.size; ++i ) {
+ if( ret[i] == p )
+ return;
+ }
+
+ ret.push( p );
+ } );
+
+ return ret;
+}
+
+LIST<BSP_PLANE> bsp_map_get_planes( WORLD_MAP* map ) {
+ LIST<BSP_PLANE> ret;
+
+ LIST<BSP_PLANE> walls = bsp_map_get_wall_planes( map );
+ LIST<BSP_PLANE> floors = bsp_map_get_tri_planes( map );
+
+ ret.push_list( walls );
+ ret.push_list( floors );
+
+ // probably unnecessary, slow as fuck
+ // remove any dupes in case a polygon ends up the same as a wall
+ // unlikely but dont wanna run into the edge case
+ // this wont run that much anyway
+ // maybe remove later
+ for( U32 i = 0; i < ret.size; ++i ) {
+ BSP_PLANE* p = &ret[i];
+ for( U32 i2 = 0; i2 < ret.size; ++i2 ) {
+ if( i == i2 )
+ continue;
+
+ if( ret.data[i2] == *p ) {
+ ret.erase( i );
+ --i;
+ }
+ }
+ }
+
+ return ret;
+}
+
+
+LIST<BSP_FACE> bsp_map_faces_from_walls( WORLD_MAP* map ) {
+ LIST<BSP_FACE> ret{};
+
+ auto triangulate_wall = fn( MAP_WALL* w ) {
+ SURF_PROPS* p = wall_get_props( map, w );
+ VEC3 points[] = {
+ { w->start.x, w->start.y, w->start.z },
+ { w->end.x, w->end.y, w->start.z },
+ { w->end.x, w->end.y, w->start.z + w->end.z },
+ { w->start.x, w->start.y, w->start.z + w->end.z },
+ };
+
+ VEC2 uvs[] = {
+ { w->uvstart.x, 1.f - w->uvend.y },
+ { 1.f - w->uvend.x, 1.f - w->uvend.y },
+ { 1.f - w->uvend.x, w->uvstart.y },
+ { w->uvstart.x, w->uvstart.y },
+ };
+
+ MAP_VERTEX vertices[4];
+ for( U32 i = 0; i < 4; ++i ) {
+ vertices[i] = {
+ .pos = points[i],
+ .uv = uvs[i],
+ .clr = p->clr,
+ };
+ }
+
+ BSP_FACE f1{ .propid = w->propid };
+ f1.verts.push( vertices[2] );
+ f1.verts.push( vertices[1] );
+ f1.verts.push( vertices[0] );
+
+ BSP_FACE f2{ .propid = w->propid };
+ f2.verts.push( vertices[3] );
+ f2.verts.push( vertices[2] );
+ f2.verts.push( vertices[0] );
+
+ ret.push( f1 );
+ ret.push( f2 );
+ };
+
+ map->walls.each( triangulate_wall );
+ for( U32 i = 0; i < 4; ++i ) triangulate_wall( &map->skybox.walls[i] );
+
+ return ret;
+}
+
+LIST<BSP_FACE> bsp_map_faces_from_planes( WORLD_MAP* map ) {
+ LIST<BSP_FACE> ret;
+
+ auto triangulate_plane = fn( MAP_POLYGON* p ) {
+ MAP_VERTEX* start = &p->vertices[0];
+ MAP_VERTEX* last = &p->vertices[1];
+ for( U32 i = 2; i < p->vertices.size; ++i ) {
+ BSP_FACE f = { .propid = p->propid };
+ f.verts.push( *start );
+ f.verts.push( *last );
+ f.verts.push( p->vertices[i] );
+
+ ret.push( f );
+ last = &p->vertices[i];
+ }
+ };
+
+ map->polygons.each( triangulate_plane );
+ triangulate_plane( &map->skybox.polygons[0] );
+ triangulate_plane( &map->skybox.polygons[1] );
+
+ return ret;
+}
+
+LIST<BSP_FACE> bsp_map_get_faces( WORLD_MAP* map ) {
+ LIST<BSP_FACE> ret;
+
+ LIST<BSP_FACE> walls = bsp_map_faces_from_walls( map );
+ LIST<BSP_FACE> floors = bsp_map_faces_from_planes( map );
+
+ ret.emplace_list( walls );
+ ret.emplace_list( floors );
+
+ return ret;
+}
+
+inline I32 point_classify( const BSP_PLANE& plane, const VEC3& point ) {
+ F32 side = bsp_plane_dist( plane, point );
+ if( side > BSP_NORM_EPSILON )
+ return BSP_SIDE_FRONT;
+ else if( side < -BSP_NORM_EPSILON )
+ return BSP_SIDE_BACK;
+ else
+ return BSP_SIDE_ON;
+}
+
+inline I32 polygon_classify(
+ const BSP_PLANE& plane,
+ const BSP_FACE* face,
+ I32* out_fr = 0,
+ I32* out_bk = 0
+) {
+ I32 cf = 0, cb = 0;
+ for( U32 i = 0; i < face->verts.size; ++i ) {
+ I32 side = point_classify( plane, face->verts.data[i].pos );
+ if( side & BSP_SIDE_FRONT )
+ ++cf;
+ else if( side & BSP_SIDE_BACK )
+ ++cb;
+ }
+
+ if( out_fr )
+ *out_fr = cf;
+ if( out_bk )
+ *out_bk = cb;
+
+ if( cf && cb ) return BSP_SIDE_SPAN;
+ if( cf ) return BSP_SIDE_FRONT;
+ if( cb ) return BSP_SIDE_BACK;
+ return BSP_SIDE_ON;
+}
+
+STAT polygon_split( const BSP_PLANE& plane, const BSP_FACE* in, BSP_FACE* out_fr, BSP_FACE* out_bk ) {
+ LIST<MAP_VERTEX> front, back;
+ STAT stat = STAT_ERR;
+
+ for( U32 i = 0; i < in->verts.size; ++i ) {
+ U32 i2 = (i+1) % in->verts.size;
+ MAP_VERTEX* a = &in->verts.data[i];
+ MAP_VERTEX* c = &in->verts.data[i2];
+
+ F32 da = bsp_plane_dist( plane, a->pos );
+ F32 dc = bsp_plane_dist( plane, c->pos );
+
+ U8 afront = da > BSP_NORM_EPSILON;
+ U8 aback = da < -BSP_NORM_EPSILON;
+ U8 cfront = dc > BSP_NORM_EPSILON;
+ U8 cback = dc < -BSP_NORM_EPSILON;
+
+ if( !aback ) front.push( *a );
+ if( !afront ) back.push( *a );
+
+ if( (afront && cback) || (aback && cfront) ) {
+ F32 t = m_clamp( da / (da - dc), 0.f, 1.f );
+ VEC3 isec = a->pos + (c->pos - a->pos) * t;
+ VEC2 uv = a->uv + (c->uv - a->uv) * t;
+ CLR clr = a->clr + (c->clr - a->clr) * t;
+ MAP_VERTEX v = { .pos = isec, .uv = uv, .clr = clr };
+ front.push( v );
+ back.push( v );
+ }
+ }
+
+ if( front.size > 2 ) {
+ out_fr->propid = in->propid;
+ out_fr->verts = front;
+ stat = STAT_OK;
+ }
+ if( back.size > 2 ) {
+ out_bk->propid = in->propid;
+ out_bk->verts = back;
+ stat = STAT_OK;
+ }
+
+ return stat;
+}
+
+I32 bsp_splitter_cost( const BSP_PLANE& plane, const LIST<BSP_FACE>& faces ) {
+ I32 splits = 0, front = 0, back = 0;
+ for( U32 i = 0; i < faces.size; ++i ) {
+ I32 side = polygon_classify( plane, &faces.data[i], 0, 0 );
+ if( side == BSP_SIDE_SPAN ) ++splits;
+ else if( side == BSP_SIDE_FRONT ) ++front;
+ else if( side == BSP_SIDE_BACK ) ++back;
+ else { ++front; ++back; }
+ }
+
+ I32 diff = ( front > back ) ? front - back : back - front;
+ return splits * 8 + diff;
+}
+
+I32 bsp_calc_splitter_index( const LIST<BSP_PLANE>& planes, const LIST<BSP_FACE>& faces ) {
+ I32 best = -1;
+ I32 bestc = INT_MAX;
+
+ for( U32 i = 0; i < planes.size; ++i ) {
+ I32 cost = bsp_splitter_cost( planes.data[i], faces );
+ if( cost < bestc ) {
+ best = i;
+ bestc = cost;
+ }
+ }
+
+ return best;
+}
+
+U8 bsp_is_convex_cluster( const LIST<BSP_FACE>& faces ) {
+ for( U32 i = 0; i < faces.size; ++i ) {
+ VEC3 e1 = faces.data[i].verts[1].pos - faces.data[i].verts[0].pos;
+ VEC3 e2 = faces.data[i].verts[2].pos - faces.data[i].verts[0].pos;
+ VEC3 norm = vec_normalize( vec_cross( e1, e2 ) );
+ F32 dist = vec_dot( norm, faces.data[i].verts[0].pos );
+ BSP_PLANE plane{ norm, dist };
+ for( U32 i2 = 0; i2 < faces.size; ++i2 ) {
+ if( i == i2 )
+ continue;
+
+ if( polygon_classify( plane, &faces.data[i2], 0, 0 ) == BSP_SIDE_SPAN )
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+I32 bsp_insert_leaf( BSP* bsp, LIST<BSP_FACE>& faces ) {
+ BSP_LEAF leaf{};
+ leaf.first = bsp->faces.size;
+ leaf.count = faces.size;
+ leaf.cluster = -1; // todo: pvs
+ bsp->faces.push_list( faces );
+ bsp->leaves.push( leaf );
+ return ~( (I32)bsp->leaves.size - 1 );
+}
+
+I32 bsp_build_nodes(
+ BSP* bsp,
+ LIST<BSP_PLANE>& planes,
+ LIST<BSP_FACE>& faces,
+ I32 depth ) {
+ if( faces.size == 0 )
+ return bsp_insert_leaf( bsp, faces );
+ if( depth > BSP_DEPTH_MAX || bsp_is_convex_cluster( faces ) )
+ return bsp_insert_leaf( bsp, faces );
+
+ I32 splitter_idx = bsp_calc_splitter_index( planes, faces );
+ if( splitter_idx < 0 ) return bsp_insert_leaf( bsp, faces );
+
+ BSP_PLANE split = planes.data[splitter_idx];
+
+ LIST<BSP_FACE> front, back, coplane;
+ for( U32 i = 0; i < faces.size; ++i ) {
+ BSP_FACE* f = &faces.data[i];
+ I32 frontc = 0, backc = 0;
+ I32 side = polygon_classify( split, f, &frontc, &backc );
+ if( side == BSP_SIDE_FRONT )
+ front.push( *f );
+ else if( side == BSP_SIDE_BACK )
+ back.push( *f );
+ else if( side == BSP_SIDE_ON )
+ coplane.push( *f );
+ else {
+ BSP_FACE fr{}, bk{};
+ if( OK( polygon_split( split, f, &fr, &bk ) ) ) {
+ if( fr.verts.size > 2 ) front.push( fr );
+ if( bk.verts.size > 2 ) back.push( bk );
+ }
+ }
+ }
+
+ BSP_NODE node{};
+ node.plane = split;
+ I32 nidx = bsp->nodes.size;
+ bsp->nodes.push( node );
+
+ LIST<BSP_PLANE> child_planes;
+ child_planes.reserve( planes.size > 0 ? planes.size - 1 : 0 );
+ for( I32 i = 0; i < planes.size; ++i ) {
+ if( i != splitter_idx ) child_planes.push( planes.data[i] );
+ }
+
+ I32 front_child = bsp_build_nodes( bsp, child_planes, front, depth + 1 );
+ if( coplane.size ) {
+ I32 coplane_leaf = bsp_insert_leaf( bsp, coplane );
+ BSP_NODE glue{};
+ glue.plane = split;
+ glue.front = front_child;
+ glue.back = coplane_leaf;
+ front_child = bsp->nodes.size;
+ bsp->nodes.push( glue );
+ }
+
+ I32 back_child = bsp_build_nodes( bsp, child_planes, back, depth + 1 );
+
+ bsp->nodes.data[nidx].front = front_child;
+ bsp->nodes.data[nidx].back = back_child;
+ return nidx;
+}
+
+inline void bsp_plane_basis( const VEC3& norm, VEC3* u, VEC3* v ) {
+ VEC3 a = fabsf( norm.x ) > 0.9f ? VEC3{ 0, 1, 0 } : VEC3{ 1, 0, 0 };
+ *u = vec_normalize( vec_cross( a, norm ) );
+ *v = vec_normalize( vec_cross( norm, *v ) );
+}
+
+BSP_WINDING bsp_winding_create( BSP_PLANE* plane ) {
+ BSP_WINDING w{};
+ VEC3 n = plane->normal, nu = n * -1.f;
+ VEC3 u, v;
+ bsp_plane_basis( n, &u, &v );
+
+ VEC3 c = n * plane->dist;
+
+ F32 s = BSP_PORTAL_PLANE_SIZE;
+ w.points.reserve( 4 );
+ w.points.push( c + ( u + v ) * s );
+ w.points.push( c + ( nu + v ) * s );
+ w.points.push( c + ( nu - v ) * s );
+ w.points.push( c + ( u - v ) * s );
+
+ return w;
+}
+
+
+F32 signed_dist_point_to_plane( const BSP_PLANE& plane, const VEC3& point ) {
+ return vec_dot( plane.normal, point ) - plane.dist;
+}
+
+U8 bsp_clip_winding( BSP_WINDING* w, const BSP_PLANE& plane, U8 keep_front ) {
+ if( w->points.size < 3 )
+ return 0;
+
+ LIST<VEC3> out;
+ out.reserve( w->points.size + 2 );
+
+ for( U32 i = 0; i < w->points.size; ++i ) {
+ VEC3* a = &w->points.data[i];
+ VEC3* b = &w->points.data[(i + 1) % w->points.size];
+
+ F32 da = signed_dist_point_to_plane( plane, *a );
+ F32 db = signed_dist_point_to_plane( plane, *b );
+
+ U8 ain = keep_front ? ( da >= -BSP_NORM_EPSILON ) : ( da <= BSP_NORM_EPSILON );
+ U8 bin = keep_front ? ( db >= -BSP_NORM_EPSILON ) : ( db <= BSP_NORM_EPSILON );
+
+ if( ain )
+ out.push( *a );
+
+ if( ain != bin ) {
+ F32 t = da / (da - db);
+ t = m_clamp( t, 0.f, 1.f );
+ VEC3 p = *a + (*b - *a) * t;
+ out.push( p );
+ }
+ }
+
+ if( out.size < 3 ) {
+ w->points.clear();
+ return 0;
+ }
+
+ w->points = out;
+ return 1;
+}
+
+U8 bsp_clip_winding_to_set( BSP_WINDING* w, LIST<BSP_PLANE>* planes ) {
+ for( U32 i = 0; i < planes->size; ++i ) {
+ if( !bsp_clip_winding( w, planes->data[i], 1 ) )
+ return 0;
+ }
+
+ return 1;
+}
+
+U8 bsp_winding_intersect_plane( BSP_PLANE* plane, BSP_WINDING* wa, BSP_WINDING* wb, BSP_WINDING* out ) {
+ if( wa->points.size < 3 || wb->points.size < 3 )
+ return 0;
+
+ BSP_WINDING w = *wa;
+
+ for( U32 i = 0; i < wb->points.size; ++i ) {
+ VEC3* b1 = &wb->points[i];
+ VEC3* b2 = &wb->points[(i + 1) % wb->points.size];
+
+ VEC3 edge = *b2 - *b1;
+
+ VEC3 inward_norm = vec_normalize( vec_cross( plane->normal, edge ) );
+ F32 d = vec_dot( inward_norm, *b1 );
+
+ BSP_PLANE horip{ inward_norm, d };
+ if( !bsp_clip_winding( &w, horip, 1 ) )
+ return 0;
+ }
+
+ *out = w;
+ return 1;
+}
+
+void bsp_gather_volumes_for_node(
+ BSP* bsp,
+ I32 node_idx,
+ const LIST<BSP_PLANE>& parents,
+ U8 go_front,
+ LIST<BSP_PLANE>* out
+) {
+ *out = parents;
+ BSP_NODE* n = &bsp->nodes.data[node_idx];
+ BSP_PLANE newp = n->plane;
+ if( !go_front ) {
+ newp.normal = newp.normal * -1;
+ newp.dist = -newp.dist;
+ }
+
+ out->push( newp );
+}
+
+void bsp_clip_portal_to_subtree(
+ BSP* bsp,
+ I32 idx,
+ BSP_WINDING w,
+ U8 in_front,
+ LIST<BSP_PORTAL_NODE>* nodes
+) {
+ if( bsp_is_leaf( idx ) ) {
+ BSP_PORTAL_NODE newn{ .leaf = bsp_leaf_index( idx ), .w = w };
+ nodes->push( newn );
+ return;
+ }
+
+ BSP_NODE* n = &bsp->nodes.data[idx];
+ BSP_WINDING wf = w, wb = w;
+
+ if( !bsp_clip_winding( in_front ? &wf : &wb, n->plane, in_front ) )
+ return;
+
+ if( in_front ) {
+ bsp_clip_portal_to_subtree( bsp, n->front, wf, 1, nodes );
+ } else {
+ bsp_clip_portal_to_subtree( bsp, n->back, wb, 0, nodes );
+ }
+}
+
+void bsp_build_portals( BSP* bsp ) {
+ if( bsp->root < 0 )
+ return;
+
+ LIST<BSP_PORTAL_STACK_FRAME> stack;
+ stack.push( BSP_PORTAL_STACK_FRAME{ bsp->root, LIST<BSP_PLANE>{} } );
+
+ // todo: multithread this
+ while( stack.size ) {
+ BSP_PORTAL_STACK_FRAME frame = stack.data[stack.size - 1];
+ stack.pop();
+
+ if( bsp_is_leaf( frame.idx ) )
+ continue;
+
+ BSP_NODE* n = &bsp->nodes.data[frame.idx];
+
+ BSP_WINDING wind = bsp_winding_create( &n->plane );
+ BSP_WINDING bounded = wind;
+ if( bsp_clip_winding_to_set( &bounded, &frame.planes ) ) {
+ LIST<BSP_PORTAL_NODE> fr_nodes, bk_nodes;
+
+ bsp_clip_portal_to_subtree( bsp, n->front, bounded, 1, &fr_nodes );
+ bsp_clip_portal_to_subtree( bsp, n->back, bounded, 1, &bk_nodes );
+
+ for( U32 ifr = 0; ifr < fr_nodes.size; ++ifr ) {
+ for( U32 ibk = 0; ibk < bk_nodes.size; ++ibk ) {
+ BSP_WINDING intersect;
+ if( bsp_winding_intersect_plane( &n->plane, &fr_nodes.data[ifr].w, &bk_nodes.data[ibk].w, &intersect ) ) {
+ if( intersect.points.size > 3 ) {
+ BSP_PORTAL p;
+ p.w = intersect;
+ p.plane = n->plane;
+ p.front = bsp_leaf_index( fr_nodes.data[ifr].leaf );
+ p.back = bsp_leaf_index( bk_nodes.data[ibk].leaf );
+ bsp->portals.push( p );
+ }
+ }
+ }
+ }
+ }
+
+ LIST<BSP_PLANE> fr, bk;
+ bsp_gather_volumes_for_node( bsp, frame.idx, frame.planes, 1, &fr );
+ bsp_gather_volumes_for_node( bsp, frame.idx, frame.planes, 0, &bk );
+
+ BSP_NODE* n1 = &bsp->nodes.data[frame.idx];
+ if( !bsp_is_leaf( n1->front ) ) stack.push( BSP_PORTAL_STACK_FRAME{ n1->front, fr } );
+ if( !bsp_is_leaf( n1->back ) ) stack.push( BSP_PORTAL_STACK_FRAME{ n1->back, bk } );
+ }
+
+ for( U32 i = 0; i < bsp->leaves.size; ++i ) {
+ bsp->leaves.data[i].cluster = (I32)i;
+ }
+}
+
+void pvs_set( BSP_BITSET* pvs, U32 count, U32 cluster, U32 seen ) {
+ U32 wp = (count + 31) / 32;
+ U32 base = cluster * wp;
+
+ U32 w = seen / 32, b = seen % 32;
+ while( base + w >= pvs->size ) pvs->push( 0 );
+ pvs->data[base + w] |= (1u << b);
+}
+
+U8 pvs_get( BSP_BITSET* pvs, U32 count, U32 cluster, U32 seen ) {
+ U32 wp = (count + 31) / 32;
+ U32 base = cluster * wp;
+
+ U32 w = seen / 32, b = seen % 32;
+ if( base + w >= pvs->size ) return 0;
+
+ return ( pvs->data[base + w] >> b) & 1u;
+}
+
+BSP* bsp_build_map( WORLD_MAP* m ) {
+ BSP* bsp = new BSP;
+ bsp->map = m;
+ LIST<BSP_FACE> faces = bsp_map_get_faces( m );
+ LIST<BSP_PLANE> planes = bsp_map_get_planes( m );
+
+ bsp->root = bsp_build_nodes( bsp, planes, faces, 0 );
+ U32 vertc = 0;
+ m->polygons.each( fn( MAP_POLYGON* p ) { vertc += p->vertices.size; } );
+ for( U32 i = 0; i < bsp->faces.size; ++i ) {
+ bsp->faces.data[i].id = i;
+ }
+ bsp_gen_render_vertices( bsp );
+ return bsp;
+}
+
+void bsp_free( BSP* bsp ) {
+ delete bsp;
+}
+
diff --git a/src/game/world/bsp.h b/src/game/world/bsp.h
new file mode 100644
index 0000000..439fce1
--- /dev/null
+++ b/src/game/world/bsp.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "map.h"
+#include "../../render/gl_3d.h"
+
+const F32 BSP_NORM_EPSILON = 0.0001f;
+const F32 BSP_DIST_EPSILON = 0.01f;
+const I32 BSP_DEPTH_MAX = 1024;
+struct BSP_PLANE {
+ VEC3 normal;
+ F32 dist;
+
+ const bool operator==( const BSP_PLANE& other ) const {
+ return fabsf( normal.x - other.normal.x ) < BSP_NORM_EPSILON &&
+ fabsf( normal.y - other.normal.y ) < BSP_NORM_EPSILON &&
+ fabsf( normal.z - other.normal.z ) < BSP_NORM_EPSILON &&
+ fabsf( dist - other.dist ) < BSP_DIST_EPSILON;
+ }
+};
+
+enum BspSide_t {
+ BSP_SIDE_FRONT = 1,
+ BSP_SIDE_BACK = 2,
+ BSP_SIDE_ON = 4,
+ BSP_SIDE_SPAN = 8,
+};
+
+struct BSP_FACE {
+ I32 propid;
+ I32 id;
+ LIST<MAP_VERTEX> verts{};
+ LIST<VERTEX3D> render_verts{};
+};
+
+struct BSP_NODE {
+ BSP_PLANE plane;
+ I32 front;
+ I32 back;
+};
+
+struct BSP_LEAF {
+ U32 first;
+ U32 count;
+ I32 cluster;
+};
+
+struct BSP_WINDING {
+ LIST<VEC3> points;
+};
+
+struct BSP_PORTAL {
+ BSP_WINDING w; // winding
+ BSP_PLANE plane;
+ I32 front;
+ I32 back;
+};
+
+struct BSP_CLUSTER {
+ I32 first;
+ I32 count;
+ I32 pvs_off;
+};
+
+struct BSP_PORTAL_STACK_FRAME {
+ I32 idx;
+ LIST<BSP_PLANE> planes;
+};
+
+struct BSP_PORTAL_NODE {
+ I32 leaf;
+ BSP_WINDING w;
+};
+
+struct BSP_BITSET : public LIST<U32> {};
+
+struct BSP {
+ LIST<BSP_NODE> nodes;
+ LIST<BSP_LEAF> leaves;
+ LIST<BSP_FACE> faces;
+
+ // >= 0 = node, < 0 = leaf
+ I32 root;
+ WORLD_MAP* map;
+
+ LIST<BSP_PORTAL> portals;
+ LIST<BSP_CLUSTER> clusters;
+ BSP_BITSET pvs_bits;
+};
+
+extern LIST<BSP_PLANE> bsp_map_get_planes( WORLD_MAP* map );
+extern LIST<BSP_FACE> bsp_map_get_faces( WORLD_MAP* map );
+extern BSP* bsp_build_map( WORLD_MAP* map );
+extern void bsp_free( BSP* bsp );
+
+
+inline U8 bsp_is_leaf( I32 child ) { return child < 0; }
+inline I32 bsp_leaf_index( I32 child ) { return ~child; }
+inline F32 bsp_plane_dist( const BSP_PLANE& p, const VEC3& v ) { return vec_dot( p.normal, v ) - p.dist; }
+inline SURF_PROPS* bsp_face_get_props( WORLD_MAP* w, BSP_FACE* s ) {
+ return s->propid < 0 ?
+ map_props_get_special( w, s->propid ) :
+ &w->props[s->propid];
+}
diff --git a/src/game/world/bsp_draw.cpp b/src/game/world/bsp_draw.cpp
new file mode 100644
index 0000000..30e6731
--- /dev/null
+++ b/src/game/world/bsp_draw.cpp
@@ -0,0 +1,124 @@
+#include "bsp_draw.h"
+#include "../../game.h"
+#include "../../render/gl_3d.h"
+#include "../objlist.h"
+#include "map.h"
+#include "trace.h"
+
+
+void bsp_draw_leaf( BSP* bsp, GAME_DATA* game, I32 leafidx ) {
+ BSP_LEAF* leaf = &bsp->leaves.data[leafidx];
+ // todo : handle transparency
+
+ for( U32 i = 0; i < leaf->count; ++i ) {
+ BSP_FACE* f = &bsp->faces.data[leaf->first + i];
+ U8 face_transparent = 0;
+ if( !face_transparent ) {
+ LIST<VERTEX3D> verts = f->render_verts;
+ SURF_PROPS* props = bsp_face_get_props( game->state.map, f );
+ gl_3d_polygon( game->render.batch_3d, verts.data, verts.size, props->tex );
+ }
+ }
+
+ // todo : sort by depth
+ for( U32 i = 0; i < leaf->count; ++i ) {
+ BSP_FACE* f = &bsp->faces.data[leaf->first + i];
+ U8 face_transparent = 0;
+ if( face_transparent ) {
+ LIST<VERTEX3D> verts = f->render_verts;
+ SURF_PROPS* props = bsp_face_get_props( game->state.map, f );
+ gl_3d_polygon( game->render.batch_3d, verts.data, verts.size, props->tex );
+ }
+ }
+}
+
+U8 leaf_visible( I32 leaf_idx ) {
+ // todo : pvs
+ return 1;
+}
+
+I32 bsp_point_leaf( BSP* bsp, const VEC3& p ) {
+ I32 n = bsp->root;
+ for( ;; ) {
+ if( bsp_is_leaf( n ) ) return bsp_leaf_index( n );
+ BSP_NODE* node = &bsp->nodes.data[n];
+ F32 dist = bsp_plane_dist( node->plane, p );
+ n = (dist >= 0.f)? node->front : node->back;
+ }
+}
+
+void bsp_traverse_draw( BSP* bsp, GAME_DATA* game, I32 node, VEC3 campos ) {
+ if( bsp_is_leaf( node ) ) {
+ I32 i = bsp_leaf_index( node );
+ return bsp_draw_leaf( bsp, game, i );
+ }
+
+ BSP_NODE* n = &bsp->nodes.data[node];
+ F32 dist = bsp_plane_dist( n->plane, campos );
+
+ I32 near = (dist >= 0.f)? n->front : n->back;
+ I32 far = (dist >= 0.f)? n->back : n->front;
+
+ // todo : set up vertex buffer, draw all in one call
+ // todo todo : batch different shader calls separately
+
+ bsp_traverse_draw( bsp, game, near, campos );
+ bsp_traverse_draw( bsp, game, far, campos );
+}
+
+void bsp_draw_sprites( BSP* bsp, GAME_DATA* game ) {
+ LIST<MAP_SPRITE>* sprites = &game->state.map->sprites;
+ for( U32 i = 0; i < sprites->size; ++i ) {
+ MAP_SPRITE* sprite = &sprites->data[i];
+ BSP_TRACE t;
+ t.in_start = player_get_view_pos( objl->pl );
+ t.in_end = sprite->pos;
+
+ bsp_trace( &t, bsp );
+ if( !t.hit ) {
+ VEC3 angle = vec_angles( sprite->pos, t.in_start );
+ gl_3d_plane(
+ game->shaders.gl3d,
+ sprite->pos,
+ sprite->size,
+ { -angle.x + 90.f, angle.y - 90.f },
+ sprite->tex,
+ sprite->clr
+ );
+ }
+ }
+}
+
+void bsp_draw( BSP* bsp, GAME_DATA* game, VEC2 window, VEC2 winsize ) {
+ gl_set_viewport( game->gl, window, winsize );
+ PLAYER* pl = objl->pl;
+ F32 fov = pl->fov;
+ gl_3d_projection_setup(
+ game->shaders.gl3d,
+ { pl->pos.x, pl->pos.y, pl->pos.z + pl->eyeoffset },
+ fov,
+ pl->rot.y,
+ pl->rot.x,
+ 1.f,
+ 9999.f,
+ winsize
+ );
+
+ gl_set_clip( game->gl, window, winsize );
+ gl_batch_empty( game->render.batch_3d );
+ glEnable( GL_DEPTH_TEST );
+ glEnable( GL_CULL_FACE );
+ // ???
+ glFrontFace( GL_CW );
+ glDepthFunc( GL_LESS );
+ bsp_traverse_draw( bsp, game, bsp->root, objl->pl->pos );
+ gl_batch_draw( game->render.batch_3d );
+
+ glDisable( GL_DEPTH_TEST );
+ glDisable( GL_CULL_FACE );
+ bsp_draw_sprites( bsp, game );
+ gl_reset_clip( game->gl );
+
+ VEC2 canvas = { (F32)game->gl->canvas_size[0], (F32)game->gl->canvas_size[1] };
+ gl_set_viewport( game->gl, {}, canvas );
+}
diff --git a/src/game/world/bsp_draw.h b/src/game/world/bsp_draw.h
new file mode 100644
index 0000000..048fa61
--- /dev/null
+++ b/src/game/world/bsp_draw.h
@@ -0,0 +1,4 @@
+#include "bsp.h"
+
+extern void bsp_draw_leaf( BSP* bsp, I32 leafidx );
+extern void bsp_draw( BSP* bsp, GAME_DATA* game, VEC2 window, VEC2 winsize );
diff --git a/src/game/world/draw.cpp b/src/game/world/draw.cpp
new file mode 100644
index 0000000..9269a21
--- /dev/null
+++ b/src/game/world/draw.cpp
@@ -0,0 +1,216 @@
+#include "draw.h"
+#include "map.h"
+
+#include "../../game.h"
+#include "../../render/gl_3d.h"
+
+#include "../objlist.h"
+#include "../raycast.h"
+
+VEC2 world_project_pos(
+ GAME_DATA* game,
+ VEC3 pos
+) {
+ GL_3D* gl3d = (GL_3D*)game->shaders.gl3d;
+
+ VEC4 clip = { pos.x, pos.z, pos.y, 1.f };
+ VEC4 ndc;
+
+ mat4_mul_vec4( &gl3d->projmat, &clip, &ndc );
+
+ if( ndc.w <= 0 )
+ return { -INFINITY, -INFINITY };
+
+ ndc.x /= ndc.w;
+ ndc.y /= ndc.w;
+
+ VEC2 screen = {
+ ( ndc.x * 0.5f + 0.5f ) * gl3d->winsize.x,
+ ( -( ndc.y * 0.5f ) + 0.5f ) * gl3d->winsize.y
+ };
+
+ return screen;
+}
+
+void world_draw_wall(
+ GAME_DATA* game,
+ WORLD* world,
+ VERTEX3D* verts,
+ MAP_WALL* s
+) {
+ SURF_PROPS* p = wall_get_props( world->map, s );
+ gl_3d_polygon( game->shaders.gl3d, verts, 4, p->tex );
+}
+
+void world_draw_walls( GAME_DATA* game, WORLD* world ) {
+ world->map->walls.each( fn( MAP_WALL* s ) {
+ SURF_PROPS* props = wall_get_props( world->map, s );
+ VEC3 start = s->start;
+ VEC3 end = s->end;
+
+ VERTEX3D verts[4]{};
+ verts[0].pos = { start.x, start.z, start.y };
+ verts[1].pos = { start.x, start.z + end.z, start.y };
+ verts[2].pos = { end.x, start.z + end.z, end.y };
+ verts[3].pos = { end.x, start.z, end.y };
+
+ verts[0].uv = { 0, 1 };
+ verts[1].uv = { 0, 0 };
+ verts[2].uv = { 1, 0 };
+ verts[3].uv = { 1, 1 };
+
+ verts[0].clr = verts[1].clr = verts[2].clr = verts[3].clr = props->clr;
+
+ world_draw_wall( game, world, verts, s );
+ } );
+}
+
+void world_draw_polygon(
+ GAME_DATA* game,
+ WORLD* world,
+ MAP_POLYGON* p,
+ LIST<VERTEX3D>* verts
+) {
+ SURF_PROPS* props = polygon_get_props( world->map, p );
+
+ gl_3d_polygon(
+ game->shaders.gl3d,
+ verts->data,
+ verts->size,
+ props->tex
+ );
+}
+
+void world_draw_polygons( GAME_DATA* game, WORLD* world ) {
+ for( U32 i = 0; i < world->map->polygons.size; ++i ) {
+ MAP_POLYGON* p = &world->map->polygons[i];
+ SURF_PROPS* props = polygon_get_props( world->map, p );
+
+ LIST<VERTEX3D> proj_verts;
+ U8 visc = p->vertices.size; // todo: visc
+ p->vertices.each( fn( MAP_VERTEX* mv ) {
+ VERTEX3D v;
+ v.pos = { mv->pos.x, mv->pos.z, mv->pos.y };
+ v.uv = mv->uv;
+ v.clr = mv->clr * props->clr;
+ v.sampler = 0;
+ proj_verts.push( v );
+ } );
+
+ if( proj_verts.size > 2 && visc > 0 ) {
+ world_draw_polygon( game, world, p, &proj_verts );
+ }
+ }
+}
+
+U8 sprite_is_occluded(
+ VEC3 player_pos,
+ MAP_SPRITE* sprite,
+ WORLD* world
+) {
+ VEC3 ray_dir = sprite->pos - player_pos;
+ F32 dist_to_sprite = vec_len2d( ray_dir );
+
+ vec_normalize( &ray_dir );
+
+ TRACE trace;
+ trace.startpos = player_pos;
+ U32 hit = ray_trace( &trace, sprite->pos );
+
+ F32 dist = vec_len( trace.endpos - player_pos );
+
+ return hit != TRACE_HIT_NONE && dist < dist_to_sprite;
+}
+
+void world_draw_sprite(
+ GAME_DATA* game,
+ MAP_SPRITE* sprite,
+ VEC3 player_pos,
+ WORLD* world
+ ) {
+ if( sprite_is_occluded( player_pos, sprite, world ) )
+ return;
+
+ VEC3 angle = vec_angles( sprite->pos, player_pos );
+ gl_3d_plane(
+ game->shaders.gl3d,
+ sprite->pos,
+ sprite->size,
+ { -angle.x + 90.f, angle.y - 90.f },
+ sprite->tex,
+ sprite->clr
+ );
+}
+
+void world_draw_sprites(
+ GAME_DATA* game,
+ WORLD* world
+) {
+ VEC3 player_pos = player_get_view_pos( objl->pl );
+ for( U32 i = 0; i < world->map->sprites.size; ++i ) {
+ MAP_SPRITE* sprite = &world->map->sprites[i];
+ world_draw_sprite( game, sprite, player_pos, world );
+ }
+}
+
+void world_draw( GAME_DATA* game, WORLD* world, VEC2 window, VEC2 winsize ) {
+ PLAYER* pl = objl->pl;
+ VEC2 start = { pl->pos.x, pl->pos.y };
+
+ F32 fov = pl->fov;
+
+ gl_set_viewport( game->gl, window, winsize );
+ gl_3d_projection_setup(
+ game->shaders.gl3d,
+ { pl->pos.x, pl->pos.y, pl->pos.z + pl->eyeoffset },
+ fov,
+ pl->rot.y,
+ pl->rot.x,
+ 1.f,
+ 9999.f,
+ winsize
+ );
+
+ gl_set_clip( game->gl, window, winsize );
+ glEnable( GL_DEPTH_TEST );
+ glDepthFunc( GL_LESS );
+ world_draw_polygons( game, world );
+ world_draw_walls( game, world );
+ glDisable( GL_DEPTH_TEST );
+ world_draw_sprites( game, world );
+ gl_reset_clip( game->gl );
+
+ VEC2 canvas = { (F32)game->gl->canvas_size[0], (F32)game->gl->canvas_size[1] };
+ gl_set_viewport( game->gl, {}, canvas );
+}
+
+VEC2 world_draw_project_point(
+ VEC3 vertex_pos,
+ VEC3 player_pos,
+ F32 player_angle_deg,
+ F32 fov_deg,
+ VEC2 window,
+ VEC2 winsize,
+ U8* in_view
+) {
+ F32 rx = vertex_pos.x;
+ F32 ry = vertex_pos.y;
+
+ F32 dist = sqrtf( rx*rx + ry*ry );
+ if( dist < 0.001f )
+ return { -99999.0f, -99999.0f };
+
+ F32 ang = atan2f( ry, rx );
+ F32 half_fov_rad = m_deg2rad( fov_deg * 0.5f );
+ if( in_view ) *in_view = (ang >= -half_fov_rad && ang <= half_fov_rad);
+
+ F32 cosang = cosf( ang );
+ F32 normalized_x = (ang + half_fov_rad) / (2.f * half_fov_rad);
+ F32 screen_x = window.x + normalized_x * winsize.x;
+
+ F32 screen_y_center = window.y + winsize.y * 0.5f;
+ F32 vz = ( vertex_pos.z - player_pos.z );
+ F32 screen_y = screen_y_center + (vz * winsize.y) / (dist * -cosang);
+
+ return { screen_x, screen_y };
+}
diff --git a/src/game/world/draw.h b/src/game/world/draw.h
new file mode 100644
index 0000000..6297a5e
--- /dev/null
+++ b/src/game/world/draw.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "../../util/vector.h"
+
+extern VEC2 world_project_pos( struct GAME_DATA* game, VEC3 pos );
+
+extern void world_draw_walls( struct GAME_DATA* game, struct WORLD* world );
+extern void world_draw_sprites( struct GAME_DATA* game, struct WORLD* world );
+extern void world_draw_polygons( struct GAME_DATA* game, struct WORLD* world );
+extern void world_draw( struct GAME_DATA* game, struct WORLD* world, VEC2 window, VEC2 winsize );
+extern VEC2 world_draw_project_point(
+ VEC3 vertex_pos,
+ VEC3 player_pos,
+ F32 player_angle_deg,
+ F32 fov_deg,
+ VEC2 window,
+ VEC2 winsize,
+ U8* in_view = 0
+);
diff --git a/src/game/world/map.cpp b/src/game/world/map.cpp
new file mode 100644
index 0000000..aba283d
--- /dev/null
+++ b/src/game/world/map.cpp
@@ -0,0 +1,640 @@
+#include <cfloat>
+
+#include "map.h"
+#include "../../game.h"
+#include "../assets.h"
+#include "bsp.h"
+
+const F32 SKYBOX_OFFSET = 24.f;
+
+MAP_TEXTURE_ENTRY* map_load_texture( WORLD_MAP* m, GAME_DATA* game, const char* name ) {
+ FNV1A hash = fnv1a( name );
+ MAP_TEXTURE_ENTRY** pentry = m->textures.where( fn( MAP_TEXTURE_ENTRY** t ) { return (*t)->hash == hash; } );
+ if( pentry ) {
+ return *pentry;
+ }
+
+ GL_TEX2D* tex = gl_texture_from_file( game->gl, name );
+ if( !tex ) {
+ dlog( "map_load_texture() : failed to load texture %s\n", name );
+ return 0;
+ }
+
+ MAP_TEXTURE_ENTRY* entry = new MAP_TEXTURE_ENTRY;
+ entry->tex = tex;
+ entry->hash = fnv1a( name );
+ strcpy( entry->name, name );
+
+ m->textures.push( entry );
+ return entry;
+}
+
+STAT map_add_texture_ref( WORLD_MAP* map, GL_TEX2D *tex ) {
+ const char* name = assets_abspath( tex->name );
+ FNV1A hash = fnv1a( name );
+
+ I32 idx = map->textures.idx_where( fn( MAP_TEXTURE_ENTRY** e ) {
+ return (*e)->hash == hash;
+ } );
+
+ if( idx != -1 )
+ return STAT_ALREADYEXISTS;
+
+ MAP_TEXTURE_ENTRY* entry = new MAP_TEXTURE_ENTRY;
+ entry->tex = tex;
+ entry->hash = hash;
+ strcpy( entry->name, name );
+
+ return STAT_OK;
+}
+
+GL_TEX2D* map_find_texture( WORLD_MAP* map, const char* name ) {
+ FNV1A hash = fnv1a( name );
+ MAP_TEXTURE_ENTRY** entry = map->textures.where( fn( MAP_TEXTURE_ENTRY** e ) {
+ return (*e)->hash == hash;
+ } );
+
+ if( entry )
+ return (*entry)->tex;
+
+ return 0;
+}
+
+void map_polygon_calc_vertex_normals( MAP_POLYGON* p ) {
+ for( U32 i = 0; i < p->vertices.size; i += 3 ) {
+ MAP_VERTEX* v1 = &p->vertices[i];
+ MAP_VERTEX* v2 = &p->vertices[(i + 1) % p->vertices.size];
+ MAP_VERTEX* v3 = &p->vertices[(i + 2) % p->vertices.size];
+
+ VEC3 d1 = v2->pos - v1->pos;
+ VEC3 d2 = v3->pos - v1->pos;
+ VEC3 n = vec_cross( d1, d2 );
+ vec_normalize( &n );
+
+ v1->normal = v2->normal = v3->normal = n;
+ }
+}
+
+STAT map_polygon_verts_from_section( WORLD_MAP* m, MAP_POLYGON* p, CFG_SECTION* vertices, U32 vertc ) {
+ char vertsec[256] = { 0 };
+ for( U32 i = 0; i < vertc; ++i ) {
+ MAP_VERTEX vert;
+
+ sprintf( vertsec, "%d", i );
+ CFG_SECTION* v = cfg_section( vertices, vertsec );
+ if( !v ) {
+ dlog( "map_polygon_verts_from_section() : missing vertex %d in polygon with propid %d in map %s\n", i, p->propid, m->name );
+ continue;
+ }
+
+ CFG_VEC3* pos = cfg_vec3( v, "pos" );
+ CFG_VEC2* uv = cfg_vec2( v, "uv" );
+ CFG_CLR* clr = cfg_clr( v, "clr" );
+
+ if( !pos ) {
+ dlog( "map_polygon_verts_from_section() : missing pos for vertex %d in polygon with propid %d in map %s\n", i, p->propid, m->name );
+ return STAT_ERR;
+ }
+
+ vert.pos = pos->value;
+ if( uv ) vert.uv = uv->value;
+ if( clr ) vert.clr = clr->value;
+
+ p->vertices.push( vert );
+ }
+
+ if( !p->vertices.size ) {
+ dlog( "map_polygon_verts_from_section() : invalid vertex count for polygon with propid %d in map %s\n", p->propid, m->name );
+ return STAT_ERR;
+ }
+
+ map_polygon_calc_vertex_normals( p );
+ return STAT_OK;
+}
+
+void map_polygon_calc_bounds( MAP_POLYGON* p ) {
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ p->mins.x = min( v->pos.x, p->mins.x );
+ p->mins.y = min( v->pos.y, p->mins.y );
+ p->mins.z = min( v->pos.z, p->mins.z );
+
+ p->maxs.x = max( v->pos.x, p->maxs.x );
+ p->maxs.y = max( v->pos.y, p->maxs.y );
+ p->maxs.z = max( v->pos.z, p->maxs.z );
+ } );
+}
+
+STAT map_polygons_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* polygons, U32 polyc ) {
+ if( !polyc ) {
+ dlog( "map_polygons_from_section() : no polygons in %s\n", m->name );
+ return STAT_OK;
+ }
+
+ U32 parsec = 0;
+ char polysec[256] = { 0 };
+ for( U32 i = 0; i < polyc; ++i ) {
+ MAP_POLYGON poly;
+
+ sprintf( polysec, "%d", i );
+ CFG_SECTION* p = cfg_section( polygons, polysec );
+ if( !p ) {
+ dlog( "map_polygons_from_section() : missing polygon %d in %s\n", i, m->name );
+ continue;
+ }
+
+ CFG_INT* propid = cfg_int( p, "propid" );
+ if( !propid ) {
+ dlog( "map_polygons_from_section() : missing propid for polygon %d in map %s\n", i, m->name );
+ continue;
+ }
+
+ poly.propid = propid->value;
+ CFG_INT* polytype = cfg_int( p, "type" );
+ if( !polytype ) {
+ dlog( "map_polygons_from_section() : missing polygon type for polygon %d in %s\n", i, m->name );
+ continue;
+ }
+
+ poly.type = (U8)polytype->value;
+
+ CFG_INT* vertc = cfg_int( p, "vertcount" );
+ if( !vertc || !vertc->value ) {
+ dlog( "map_polygons_from_section() : missing vertex count for polygon %d in %s\n", i, m->name );
+ continue;
+ }
+
+ CFG_SECTION* vertices = cfg_section( p, "vertices" );
+ if( !vertices ) {
+ dlog( "map_polygons_from_section() : missing vertices for polygon %d in %s", i, m->name );
+ continue;
+ }
+
+ if( !OK( map_polygon_verts_from_section( m, &poly, vertices, vertc->value ) ) )
+ continue;
+
+ map_polygon_calc_bounds( &poly );
+ m->polygons.push( poly );
+ ++parsec;
+ }
+
+ return parsec > 0 ? STAT_OK : STAT_ERR;
+}
+
+STAT map_walls_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* walls, U32 wallc ) {
+ if( !wallc ) {
+ dlog( "map_walls_from_section() : no walls in %s\n", m->name );
+ return STAT_OK;
+ }
+
+ U32 parsec = 0;
+ char wallsec[256] = { 0 };
+ for( U32 i = 0; i < wallc; i++ ) {
+ MAP_WALL seg;
+
+ sprintf( wallsec, "%d", i );
+ CFG_SECTION* w = cfg_section( walls, wallsec );
+ if( !w ) {
+ dlog( "map_walls_from_section() : missing wall %d in %s\n", i, m->name );
+ continue;
+ }
+
+ CFG_VEC3* start = cfg_vec3( w, "start" );
+ CFG_VEC3* end = cfg_vec3( w, "end" );
+ CFG_INT* prop = cfg_int( w, "propid" );
+
+ if( !start || !end || !prop ) {
+ dlog( "map_walls_from_section() : bad wall definition in %s idx %d [%p %p %p]\n", m->name, i, start, end, prop );
+ continue;
+ }
+
+ seg.start = start->value;
+ seg.end = end->value;
+ seg.propid = prop->value;
+ m->walls.push( seg );
+
+ ++parsec;
+ }
+
+ return parsec > 0 ? STAT_OK : STAT_ERR;
+}
+
+
+STAT map_props_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* props, U32 propc ) {
+ if( !propc ) {
+ dlog( "map_props_from_section() : no props in %s\n", m->name );
+ return STAT_ERR;
+ }
+
+ U32 parsec = 0;
+ char propsec[256] = { 0 };
+ for( U32 i = 0; i < propc; ++i ) {
+ SURF_PROPS prop;
+
+ sprintf( propsec, "%d", i );
+ CFG_SECTION* p = cfg_section( props, propsec );
+ if( !p ) {
+ dlog( "map_props_from_section() : missing prop %d in %s\n", i, m->name );
+ continue;
+ }
+
+ CFG_CLR* clr = cfg_clr( p, "clr" );
+ CFG_STR* tex = cfg_str( p, "tex" );
+
+ CLR propclr = (!!clr)? clr->value : CLR{ 1.f, 1.f, 1.f, 1.f };
+ GL_TEX2D* proptex = 0;
+ if( tex ) {
+ MAP_TEXTURE_ENTRY* entry = map_load_texture( m, g, tex->str );
+ if( !entry ) continue;
+
+ proptex = entry->tex;
+ }
+
+ prop.clr = propclr;
+ prop.tex = proptex;
+ m->props.push( prop );
+
+ ++parsec;
+ }
+
+ return parsec > 0 ? STAT_OK : STAT_ERR;
+}
+
+STAT map_sprites_from_section( WORLD_MAP* m, GAME_DATA* g, CFG_SECTION* sprites, U32 spritec ) {
+ if( !spritec ) {
+ dlog( "map_sprites_from_section() : no sprites in %s\n", m->name );
+ return STAT_OK;
+ }
+
+ U32 parsec = 0;
+ char spritesec[256] = { 0 };
+ for( U32 i = 0; i < spritec; ++i ) {
+ MAP_SPRITE sprite;
+
+ sprintf( spritesec, "%d", i );
+ CFG_SECTION* s = cfg_section( sprites, spritesec );
+ if( !s ) {
+ dlog( "map_sprites_from_section() : missing sprite %d in %s\n", i, m->name );
+ continue;
+ }
+
+ CFG_CLR* clr = cfg_clr( s, "clr" );
+ CFG_STR* tex = cfg_str( s, "tex" );
+ CFG_VEC3* pos = cfg_vec3( s, "pos" );
+ CFG_VEC2* size = cfg_vec2( s, "size" );
+
+ if( !tex || !pos || !size ) {
+ dlog( "map_sprites_from_section() : invalid sprite %d in %s\n", i, m->name );
+ continue;
+ }
+
+ CLR spriteclr = (!!clr)? clr->value : CLR{ 1.f, 1.f, 1.f, 1.f };
+ MAP_TEXTURE_ENTRY* texentry = map_load_texture( m, g, tex->str );
+ GL_TEX2D* spritetex = texentry->tex;
+
+ sprite.tex = spritetex;
+ sprite.pos = pos->value;
+ sprite.size = size->value;
+ sprite.clr = spriteclr;
+ m->sprites.push( sprite );
+
+ ++parsec;
+ }
+
+ return parsec > 0 ? STAT_OK : STAT_ERR;
+}
+
+STAT map_skybox_from_section( CFG_SECTION* s, GAME_DATA* g, WORLD_MAP* m ) {
+ CFG_SECTION* props = cfg_section( s, "props" );
+ if( !props )
+ return STAT_ERR;
+
+ CFG_CLR* clr = cfg_clr( props, "clr" );
+ CFG_STR* tex = cfg_str( props, "tex" );
+
+ CLR skyclr;
+ GL_TEX2D* skytex;
+ if( !tex ) {
+ skyclr = (!!clr)? clr->value : CLR::BLACK();
+ skytex = 0;
+ }
+ else {
+ MAP_TEXTURE_ENTRY* texentry = map_load_texture( m, g, tex->str );
+ skyclr = (!!clr)? clr->value : CLR::WHITE();
+ if( !texentry || !texentry->tex ) {
+ dlog( "map_skybox_from_section() : could not load skybox texture '%s'", tex->str );
+ return STAT_ERR;
+ }
+ else {
+ skytex = texentry->tex;
+ }
+ }
+
+
+ m->skybox.props = SURF_PROPS{ .tex = skytex, .clr = skyclr };
+ return STAT_OK;
+}
+
+MAP_SKYBOX map_default_skybox() {
+ return {
+ .props = { .tex = 0, .clr = CLR::BLACK() }
+ };
+}
+
+// generates 4 walls and 2 polygons to enclose the map.
+void map_gen_skybox( WORLD_MAP* m ) {
+ VEC3 mins = m->mins - VEC3( SKYBOX_OFFSET, SKYBOX_OFFSET, SKYBOX_OFFSET );
+ VEC3 maxs = m->maxs + VEC3( SKYBOX_OFFSET, SKYBOX_OFFSET, SKYBOX_OFFSET );
+
+ m->skybox.walls[0] = {
+ .start = { mins.x, mins.y, mins.z },
+ .end = { maxs.x, mins.y, maxs.z - mins.z },
+ .propid = MAPPROP_SKYBOX
+ };
+ m->skybox.walls[1] = {
+ .start = { maxs.x, mins.y, mins.z },
+ .end = { maxs.x, maxs.y, maxs.z - mins.z },
+ .propid = MAPPROP_SKYBOX
+ };
+ m->skybox.walls[2] = {
+ .start = { maxs.x, maxs.y, mins.z },
+ .end = { mins.x, maxs.y, maxs.z - mins.z },
+ .propid = MAPPROP_SKYBOX
+ };
+ m->skybox.walls[3] = {
+ .start = { mins.x, maxs.y, mins.z },
+ .end = { mins.x, mins.y, maxs.z - mins.z },
+ .propid = MAPPROP_SKYBOX
+ };
+
+ VEC2 floor[] = {
+ { mins.x, mins.y },
+ { maxs.x, mins.y },
+ { maxs.x, maxs.y },
+ { mins.x, maxs.y }
+ };
+ VEC2 floor_uv[] = {
+ { 0, 0 },
+ { 1, 0 },
+ { 1, 1 },
+ { 0, 1 }
+ };
+
+ m->skybox.polygons[0] = m->skybox.polygons[1] = {
+ .propid = MAPPROP_SKYBOX
+ };
+
+ for( U32 i = 0; i < 4; ++i ) {
+ m->skybox.polygons[0].vertices.push( {
+ .pos = { floor[i].x, floor[i].y, mins.z },
+ .uv = floor_uv[i], // todo: fix
+ .clr = CLR::WHITE()
+ } );
+
+ m->skybox.polygons[1].vertices.push( {
+ .pos = { floor[i].x, floor[i].y, maxs.z },
+ .uv = floor_uv[i], // todo: fix
+ .clr = CLR::WHITE()
+ } );
+ }
+}
+
+void map_calc_bounds( WORLD_MAP* m ) {
+ m->mins = { FLT_MAX, FLT_MAX, FLT_MAX };
+ m->maxs = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
+ m->walls.each( fn( MAP_WALL* l ) {
+ m->mins.x = min( min( l->start.x, l->end.x ), m->mins.x );
+ m->mins.y = min( min( l->start.y, l->end.y ), m->mins.y );
+ m->mins.z = min( min( l->start.z, l->start.x + l->end.z ), m->mins.z );
+
+ m->maxs.x = max( max( l->start.x, l->end.x ), m->maxs.x );
+ m->maxs.y = max( max( l->start.y, l->end.y ), m->maxs.y );
+ m->maxs.z = max( max( l->start.z, l->start.x + l->end.z ), m->maxs.z );
+ } );
+
+ m->polygons.each( fn( MAP_POLYGON* p ) {
+ m->mins.x = min( p->mins.x, m->mins.x );
+ m->mins.y = min( p->mins.y, m->mins.y );
+ m->mins.z = min( p->mins.z, m->mins.z );
+
+ m->maxs.x = max( p->maxs.x, m->maxs.x );
+ m->maxs.y = max( p->maxs.y, m->maxs.y );
+ m->maxs.z = max( p->maxs.z, m->maxs.z );
+ } );
+}
+
+void map_check_bounds( WORLD_MAP* m ) {
+ VEC3 mins = { FLT_MAX, FLT_MAX, FLT_MAX };
+ VEC3 maxs = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
+ m->walls.each( fn( MAP_WALL* l ) {
+ mins.x = min( min( l->start.x, l->end.x ), mins.x );
+ mins.y = min( min( l->start.y, l->end.y ), mins.y );
+ mins.z = min( min( l->start.z, l->start.x + l->end.z ), mins.z );
+
+ maxs.x = max( max( l->start.x, l->end.x ), maxs.x );
+ maxs.y = max( max( l->start.y, l->end.y ), maxs.y );
+ maxs.z = max( max( l->start.z, l->start.x + l->end.z ), maxs.z );
+ } );
+
+ m->polygons.each( fn( MAP_POLYGON* p ) {
+ mins.x = min( p->mins.x, mins.x );
+ mins.y = min( p->mins.y, mins.y );
+ mins.z = min( p->mins.z, mins.z );
+
+ maxs.x = max( p->maxs.x, maxs.x );
+ maxs.y = max( p->maxs.y, maxs.y );
+ maxs.z = max( p->maxs.z, maxs.z );
+ } );
+
+ if( vec_dist( mins, m->mins ) > 0.01f || vec_dist( maxs, m->maxs ) > 0.01f ) {
+ m->mins = mins;
+ m->maxs = maxs;
+ map_gen_skybox( m );
+ }
+}
+
+// todo: too long -- extract parts into funcs
+WORLD_MAP* map_from_file( GAME_DATA* game, const char* path ) {
+ CFG_SECTION* root = cfg_load( path );
+
+ ddef( const char* errstr = "map_from_file() : %s - missing %s section\n" );
+ if( !root ) { dlog( "map_from_file() : error opening file %s\n", path ); return 0; }
+ defer( cfg_free( root ); );
+
+ WORLD_MAP* m = new WORLD_MAP;
+ const char* name = file_path_last_of( path );
+ strcpy( m->name, name );
+
+ CFG_SECTION* s = cfg_section( root, "map" );
+ if( !s ) { dlog( errstr, "map", path ); delete m; return 0; }
+
+ CFG_SECTION* props = cfg_section( s, "props" );
+ if( !props ) { dlog( errstr, path, "props" ); delete m; return 0; }
+
+ CFG_SECTION* walls = cfg_section( s, "walls" );
+ if( !walls ) { dlog( errstr, path, "walls" ); delete m; return 0; }
+
+ CFG_SECTION* polys = cfg_section( s, "polygons" );
+ if( !polys ) { dlog( errstr, path, "polygons" ); delete m; return 0; }
+
+ CFG_INT* propc = cfg_int( s, "propcount" );
+ if( !propc ) { dlog( errstr, path, "propcount" ); delete m; return 0; }
+
+ CFG_INT* wallc = cfg_int( s, "wallcount" );
+ if( !wallc ) { dlog( errstr, path, "wallcount" ); delete m; return 0; }
+
+ CFG_INT* polyc = cfg_int( s, "polycount" );
+ if( !polyc ) { dlog( errstr, path, "polycount" ); delete m; return 0; }
+
+ if( !OK( map_props_from_section( m, game, props, propc->value ) ) ) { delete m; return 0; }
+ if( !OK( map_walls_from_section( m, game, walls, wallc->value ) ) ) { delete m; return 0; }
+ if( !OK( map_polygons_from_section( m, game, polys, polyc->value ) ) ) { delete m; return 0; }
+
+ CFG_INT* spritec = cfg_int( s, "spritecount" );
+ if( spritec ) {
+ CFG_SECTION* sprites = cfg_section( s, "sprites" );
+ if( !sprites ) { dlog( errstr, path, "sprites" ); delete m; return 0; }
+ if( !OK( map_sprites_from_section( m, game, sprites, spritec->value ) ) ) { delete m; return 0; }
+ }
+
+ CFG_VEC3* startpos = cfg_vec3( s, "startpos" );
+ if( !startpos ) {
+ dlog( errstr, path, "startpos" );
+ m->startpos = { 0, 0, 0 };
+ }
+ else {
+ m->startpos = startpos->value;
+ }
+
+ CFG_FLOAT* startang = cfg_float( s, "startang" );
+ if( !startang ) {
+ dlog( errstr, path, "startang" );
+ m->startang = 0;
+ }
+ else {
+ m->startang = startang->value;
+ }
+
+ map_calc_bounds( m );
+ CFG_SECTION* skybox = cfg_section( s, "skybox" );
+ if( !skybox ) {
+ dlog( errstr, path, "skybox" );
+ dlog( "using default skybox" );
+ m->skybox = map_default_skybox();
+ }
+ else {
+ if( !OK( map_skybox_from_section( skybox, game, m ) ) ) {
+ dlog( errstr, path, "skybox" );
+ dlog( "using default skybox" );
+ m->skybox = map_default_skybox();
+ }
+ }
+ map_gen_skybox( m );
+ return m;
+}
+
+void map_serialize_info( CFG_SECTION* s, WORLD_MAP* map ) {
+ cfg_int( "wallcount", s, map->walls.size );
+ cfg_int( "polycount", s, map->polygons.size );
+ cfg_int( "propcount", s, map->props.size );
+ cfg_int( "spritecount", s, map->sprites.size );
+ cfg_vec3( "startpos", s, map->startpos );
+ cfg_float( "startang", s, map->startang );
+}
+
+void map_serialize_walls( CFG_SECTION* s, WORLD_MAP* map ) {
+ CFG_SECTION* walls = cfg_section_new( "walls", s );
+ char name[64];
+
+ U32 i = 0;
+ map->walls.each( fn( MAP_WALL* seg ) {
+ sprintf( name, "%d", i++ );
+ CFG_SECTION* wallsec = cfg_section_new( name, walls );
+ cfg_vec3( "start", wallsec, seg->start );
+ cfg_vec3( "end", wallsec, seg->end );
+ cfg_int( "propid", wallsec, seg->propid );
+ } );
+}
+
+void map_serialize_polygons( CFG_SECTION* s, WORLD_MAP* map ) {
+ CFG_SECTION* polygons = cfg_section_new( "polygons", s );
+ char name[64];
+
+ U32 i = 0;
+ map->polygons.each( fn( MAP_POLYGON* p ) {
+ sprintf( name, "%d", i++ );
+ CFG_SECTION* polygonsec = cfg_section_new( name, polygons );
+ cfg_int( "vertcount", polygonsec, p->vertices.size );
+ cfg_int( "propid", polygonsec, p->propid );
+ cfg_int( "type", polygonsec, p->type );
+
+ CFG_SECTION* vertices = cfg_section_new( "vertices", polygonsec );
+ U32 i2 = 0;
+ p->vertices.each( fn( MAP_VERTEX* v ) {
+ sprintf( name, "%d", i2++ );
+ CFG_SECTION* vertsec = cfg_section_new( name, vertices );
+ cfg_vec3( "pos", vertsec, v->pos );
+ cfg_vec2( "uv", vertsec, v->uv );
+ cfg_clr( "clr", vertsec, v->clr );
+ } );
+ } );
+}
+
+void map_serialize_sprites( CFG_SECTION* s, WORLD_MAP* map ) {
+ CFG_SECTION* sprites = cfg_section_new( "sprites", s );
+ char name[64];
+
+ U32 i = 0;
+ map->sprites.each( fn( MAP_SPRITE* s ) {
+ sprintf( name, "%d", i++ );
+ CFG_SECTION* spritesec = cfg_section_new( name, sprites );
+ cfg_vec3( "pos", spritesec, s->pos );
+ cfg_vec2( "size", spritesec, s->size );
+ cfg_clr( "clr", spritesec, s->clr );
+ if( s->tex ) {
+ const char* endp = assets_abspath( s->tex->name );
+ cfg_str( "tex", spritesec, endp, 256 );
+ }
+ } );
+}
+
+void map_serialize_props( CFG_SECTION* s, WORLD_MAP* map ) {
+ CFG_SECTION* props = cfg_section_new( "props", s );
+ char name[64];
+
+ U32 i = 0;
+ map->props.each( fn( SURF_PROPS* p ) {
+ sprintf( name, "%d", i++ );
+ CFG_SECTION* propsec = cfg_section_new( name, props );
+ cfg_clr( "clr", propsec, p->clr );
+ if( p->tex ) {
+ const char* endp = assets_abspath( p->tex->name );
+ cfg_str( "tex", propsec, endp, 256 );
+ }
+ } );
+}
+
+
+CFG_SECTION* map_serialize( WORLD_MAP* map ) {
+ CFG_SECTION* root = cfg_section_new( "root", 0 );
+ CFG_SECTION* s = cfg_section_new( "map", root );
+
+ map_serialize_info( s, map );
+ map_serialize_props( s, map );
+ map_serialize_walls( s, map );
+ map_serialize_polygons( s, map );
+ map_serialize_sprites( s, map );
+
+ return s;
+}
+
+void map_free( GAME_DATA* game, WORLD_MAP* m ) {
+ if( !m ) return;
+ if( m->bsp ) bsp_free( m->bsp );
+
+ m->textures.each( fn( MAP_TEXTURE_ENTRY** e ) {
+ gl_texture_destroy( game->gl, (*e)->tex );
+ delete *e;
+ } );
+
+ delete m;
+}
diff --git a/src/game/world/map.h b/src/game/world/map.h
new file mode 100644
index 0000000..29022db
--- /dev/null
+++ b/src/game/world/map.h
@@ -0,0 +1,137 @@
+#pragma once
+
+#include "../../util/allocator.h"
+#include "../../util/vector.h"
+#include "../../util/color.h"
+#include "../../util/fnv.h"
+
+#include <string.h>
+
+enum MapPropId_t {
+ MAPPROP_SKYBOX = -1,
+};
+
+struct SURF_PROPS {
+ struct GL_TEX2D* tex;
+ CLR clr;
+};
+
+struct MAP_VERTEX {
+ VEC3 pos;
+ VEC3 normal;
+ VEC2 uv;
+ CLR clr;
+};
+
+enum MapPolygonType_t {
+ MPT_FLOOR,
+ MPT_CEILING
+};
+
+struct MAP_POLYGON {
+ MAP_POLYGON& operator=( const MAP_POLYGON& other ) {
+ memcpy( this, &other, sizeof(*this) );
+ vertices = other.vertices;
+
+ return *this;
+ }
+
+ LIST<MAP_VERTEX> vertices;
+
+ VEC3 mins;
+ VEC3 maxs;
+ U8 type;
+ I32 propid;
+};
+
+struct MAP_WALL {
+ VEC3 start;
+ VEC3 end;
+
+ VEC2 uvstart;
+ VEC2 uvend;
+
+ I32 propid;
+};
+
+struct MAP_TEXTURE_ENTRY {
+ char name[256];
+ struct GL_TEX2D* tex;
+ FNV1A hash;
+};
+
+struct MAP_SPRITE {
+ VEC3 pos;
+ VEC2 size;
+ CLR clr;
+ GL_TEX2D* tex;
+};
+
+struct MAP_ENTITY {
+ U32 classid;
+ LIST<struct OBJECT_PROP*> props;
+};
+
+struct MAP_SKYBOX {
+ SURF_PROPS props;
+ MAP_WALL walls[4];
+ MAP_POLYGON polygons[2];
+};
+
+struct WORLD_MAP {
+ LIST<MAP_WALL> walls;
+ LIST<MAP_POLYGON> polygons;
+ LIST<MAP_SPRITE> sprites;
+ LIST<SURF_PROPS> props;
+ MAP_SKYBOX skybox;
+ F32 w;
+ F32 h;
+
+ VEC3 mins;
+ VEC3 maxs;
+ char name[256];
+
+ VEC3 startpos;
+ F32 startang;
+
+ struct BSP* bsp{};
+
+ LIST<MAP_TEXTURE_ENTRY*> textures;
+};
+
+extern WORLD_MAP* map_from_file( struct GAME_DATA* game, const char* filename );
+extern struct CFG_SECTION* map_serialize( WORLD_MAP* map );
+extern STAT map_add_texture_ref( WORLD_MAP* map, GL_TEX2D* tex );
+extern GL_TEX2D* map_find_texture( WORLD_MAP* map, const char* name );
+extern void map_free( struct GAME_DATA* game, WORLD_MAP* map );
+
+// checks mins/maxs and recreates skybox if needed
+extern void map_check_bounds( WORLD_MAP* map );
+extern void map_calc_bounds( WORLD_MAP* map );
+extern void map_polygon_calc_bounds( MAP_POLYGON* p );
+
+
+inline SURF_PROPS* map_props_get_special( WORLD_MAP* w, I32 prop ) {
+ switch( prop ) {
+ case MAPPROP_SKYBOX: return &w->skybox.props;
+ };
+ dlog( "map_props_get_special(): unknown special prop %d\n", prop );
+ return 0;
+}
+
+inline SURF_PROPS* map_get_props( WORLD_MAP* m, I32 idx ) {
+ return idx < 0 ?
+ map_props_get_special( m, idx ) :
+ &m->props[idx];
+}
+
+inline SURF_PROPS* wall_get_props( WORLD_MAP* w, MAP_WALL* s ) {
+ return s->propid < 0 ?
+ map_props_get_special( w, s->propid ) :
+ &w->props[s->propid];
+}
+inline SURF_PROPS* polygon_get_props( WORLD_MAP* w, MAP_POLYGON* s ) {
+ return s->propid < 0 ?
+ map_props_get_special( w, s->propid ) :
+ &w->props[s->propid];
+}
diff --git a/src/game/world/trace.cpp b/src/game/world/trace.cpp
new file mode 100644
index 0000000..b3a0041
--- /dev/null
+++ b/src/game/world/trace.cpp
@@ -0,0 +1,393 @@
+#include <cfloat>
+
+#include "trace.h"
+#include "../../util/math.h"
+#include "bsp.h"
+
+// i have no idea how this works tbh i pasted this
+// todo : backface culling (return 0 if d < BSP_TRACE_EPSILON)
+U8 bsp_segment_intersects_tri(
+ const VEC3& va, const VEC3& vb,
+ const VEC3& v0, const VEC3& v1, const VEC3& v2,
+ F32* fract,
+ VEC2* out_uv = 0
+) {
+ VEC3 dir = vb - va;
+ VEC3 e1 = v1 - v0,
+ e2 = v2 - v0;
+
+ VEC3 p = vec_cross( dir, e2 );
+ F32 d = vec_dot( e1, p );
+
+ if( fabsf( d ) < BSP_TRACE_EPSILON ) {
+ // parallel
+ return 0;
+ }
+
+ F32 inv = 1.0f / d;
+ VEC3 ivec = va - v0;
+ F32 u = vec_dot( ivec, p ) * inv;
+ if( u < -BSP_TRACE_EPSILON || u > 1.0f + BSP_TRACE_EPSILON ) {
+ // outside
+ return 0;
+ }
+
+ VEC3 q = vec_cross( ivec, e1 );
+ F32 v = vec_dot( dir, q ) * inv;
+ if( v < -BSP_TRACE_EPSILON || u + v > 1.0f + BSP_TRACE_EPSILON ) {
+ // outside
+ return 0;
+ }
+
+ F32 t = vec_dot( e2, q ) * inv;
+ if( t < -BSP_TRACE_EPSILON || t > 1.0f + BSP_TRACE_EPSILON ) {
+ // outside
+ return 0;
+ }
+
+ if( fract ) *fract = m_clamp( t, 0.f, 1.f );
+ if( out_uv ) {
+ *out_uv = { u, v };
+ }
+
+ return 1;
+}
+
+U8 bsp_trace_leaf_segment( BSP_TRACE* trace, BSP* bsp, I32 leafidx, const VEC3& va, const VEC3& vb ) {
+ BSP_LEAF* leaf = &bsp->leaves.data[leafidx];
+ U8 didhit = 0;
+ F32 best = FLT_MAX;
+
+ for( U32 i = 0; i < leaf->count; ++i ) {
+ BSP_FACE* face = &bsp->faces.data[leaf->first + i];
+
+ // shouldnt happen
+ if( face->verts.size < 3 )
+ continue;
+
+ for( U32 t0 = 1; t0 + 1 < face->verts.size; ++t0 ) {
+ VEC3 v0 = face->verts[0].pos;
+ VEC3 v1 = face->verts[t0].pos;
+ VEC3 v2 = face->verts[t0 + 1].pos;
+
+ // todo : skip func for props
+
+ F32 t;
+ VEC2 uv;
+
+ if( !bsp_segment_intersects_tri( va, vb, v0, v1, v2, &t, &uv ) )
+ continue;
+
+ if( t < best ) {
+ best = t;
+ trace->hit = didhit = 1;
+ trace->frac = t;
+ trace->point = va + (vb - va) * t;
+ trace->normal = vec_normalize( vec_cross( v1 - v0, v2 - v0 ) );
+ trace->propid = face->propid;
+ trace->leafid = leafidx;
+ trace->faceid = leaf->first + i;
+ }
+ }
+ }
+
+ return didhit;
+}
+
+U8 bsp_trace_segment( BSP_TRACE* trace, BSP* bsp, I32 nodeidx, const VEC3& va, const VEC3& vb ) {
+ if( bsp_is_leaf( nodeidx ) ) {
+ I32 i = bsp_leaf_index( nodeidx );
+ return bsp_trace_leaf_segment( trace, bsp, i, va, vb );
+ }
+
+ BSP_NODE* node = &bsp->nodes.data[nodeidx];
+ F32 da = bsp_plane_dist( node->plane, va );
+ F32 db = bsp_plane_dist( node->plane, vb );
+
+ if( da > BSP_TRACE_EPSILON && db > BSP_TRACE_EPSILON )
+ return bsp_trace_segment( trace, bsp, node->front, va, vb );
+ if( da < -BSP_TRACE_EPSILON && db < -BSP_TRACE_EPSILON )
+ return bsp_trace_segment( trace, bsp, node->back, va, vb );
+
+ VEC3 dir = vb - va;
+ F32 denom = vec_dot( node->plane.normal, dir );
+ I32 near = (da >= 0.f) ? node->front : node->back;
+ I32 far = (da >= 0.f) ? node->back : node->front;
+ if( fabsf( denom ) < BSP_TRACE_EPSILON ) {
+ if( bsp_trace_segment( trace, bsp, near, va, vb ) )
+ return 1;
+ return bsp_trace_segment( trace, bsp, far, va, vb );
+ }
+
+ F32 t = (node->plane.dist - vec_dot( node->plane.normal, va ) ) / denom;
+ t = m_clamp( t, 0.f, 1.f );
+
+ VEC3 iplane = va + dir * t;
+ if( bsp_trace_segment( trace, bsp, near, va, iplane ) )
+ return 1;
+
+ if( t <= BSP_TRACE_EPSILON || t >= 1.0f - BSP_TRACE_EPSILON ) {
+ return bsp_trace_segment( trace, bsp, far, iplane, vb );
+ }
+
+ return bsp_trace_segment( trace, bsp, far, iplane, vb );
+}
+
+// nudge the trace ends slightly to prevent exact-coplanar infinite recursion.
+// quake does this every recursion divided up by the length of the trace
+// should not matter in the end
+void bsp_trace_nudge( BSP_TRACE* trace ) {
+ VEC3 dir = trace->in_end - trace->in_start;
+ VEC3 nudge = dir * BSP_TRACE_EPSILON;
+ trace->in_start += nudge;
+ trace->in_end -= nudge;
+}
+
+U8 bsp_trace( BSP_TRACE* trace, BSP* bsp, VEC3 start, VEC3 end ) {
+ if( !trace ) {
+ dlog( "bsp_trace() : null trace\n" );
+ abort();
+ }
+
+ trace->in_start = start;
+ trace->in_end = end;
+
+ return bsp_trace( trace, bsp );
+}
+
+U8 bsp_trace( BSP_TRACE* trace, BSP* bsp ) {
+ if( !trace ) {
+ dlog( "bsp_trace() : null trace\n" );
+ abort();
+ }
+
+ trace->hit = 0;
+
+ bsp_trace_nudge( trace );
+ VEC3 start = trace->in_start;
+ VEC3 end = trace->in_end;
+
+ return bsp_trace_segment( trace, bsp, bsp->root, start, end );
+}
+
+inline VEC3 bsp_face_get_normal( const BSP_FACE* f ){
+ const VEC3 v0 = f->verts.data[0].pos;
+ const VEC3 v1 = f->verts.data[1].pos;
+ const VEC3 v2 = f->verts.data[2].pos;
+ return vec_normalize( vec_cross( v1 - v0, v2 - v0 ) );
+}
+
+inline U8 point_in_inflated_poly(
+ const VEC3& point,
+ const BSP_FACE* face,
+ const AABB& hull,
+ const VEC3& norm
+){
+ VEC3 center{};
+ U32 c = face->verts.size;
+ for( U32 i = 0; i < c; ++i )
+ center = center + face->verts.data[i].pos;
+ if( c ) center = center * (1.0f / c);
+
+ for( U32 i = 0; i < c; ++i ) {
+ VEC3 a = face->verts.data[i].pos;
+ VEC3 b = face->verts.data[(i+1)%c].pos;
+ VEC3 in = vec_cross( norm, b - a );
+ F32 len = vec_len( in );
+ if( len <= 0.00001f ) continue;
+ in *= (1.0f/ len);
+ if( vec_dot( center - a, in ) < 0.0f )
+ in = in * -1.0f;
+
+ F32 dist = vec_dot( point - a, in );
+ VEC2 feet = aabb_extend_at_feet( hull, in );
+ F32 pos_r = feet.x, neg_r = feet.y;
+ F32 expand = norm.z >= 0 ? neg_r : pos_r;
+ if( dist < -expand - BSP_TRACE_EPSILON )
+ return 0;
+ }
+ return 1;
+}
+
+
+// big fat ass
+U8 bsp_trace_sweep_aabb_to_face(
+ BSP_TRACE* trace,
+ const AABB& hull,
+ BSP_FACE* face,
+ const VEC3& start,
+ const VEC3& end,
+ U32 face_idx
+) {
+ if( face->verts.size < 3 )
+ return 0;
+
+ VEC3 norm = bsp_face_get_normal( face );
+ F32 d = vec_dot( norm, face->verts[0].pos );
+ VEC2 feet = aabb_extend_at_feet( hull, norm );
+ F32 pos_r = feet.x, neg_r = feet.y;
+ F32 radius = norm.z >= 0 ? neg_r : pos_r;
+
+ VEC3 dir = end - start;
+ F32 s0 = vec_dot( norm, trace->in_start ) - d;
+ F32 ndir = vec_dot( norm, trace->in_end - trace->in_start );
+
+ if( fabsf( s0 ) <= radius + BSP_TRACE_EPSILON ) {
+ if( point_in_inflated_poly( start, face, hull, norm ) ) {
+ trace->hit = 1;
+ trace->frac = 0.f;
+ trace->point = start;
+ trace->normal = norm;
+ trace->propid = face->propid;
+ trace->faceid = face_idx;
+ return 1;
+ }
+ }
+
+ if( fabsf( ndir ) < BSP_TRACE_EPSILON )
+ return 0;
+
+ F32 t_enter = (-radius - s0) / ndir;
+ F32 t_exit = ( radius - s0) / ndir;
+ if( t_enter > t_exit ) { F32 tmp=t_enter; t_enter=t_exit; t_exit=tmp; }
+
+ if( t_exit < 0.f - BSP_TRACE_EPSILON || t_enter > 1.f + BSP_TRACE_EPSILON )
+ return 0;
+
+ F32 t = m_clamp( t_enter, 0.f, 1.f );
+ VEC3 step = start + dir * t;
+
+ if( !point_in_inflated_poly( step, face, hull, norm ) )
+ return 0;
+
+ trace->hit = 1;
+ trace->frac = t;
+ trace->point = step;
+ trace->normal = norm;
+ trace->propid = face->propid;
+ trace->faceid = face_idx;
+ return 1;
+}
+
+U8 bsp_trace_sweep_aabb_to_leaf(
+ BSP* bsp,
+ BSP_TRACE* trace,
+ const AABB& hull,
+ const VEC3& start,
+ const VEC3& end,
+ I32 leaf_idx
+) {
+ BSP_LEAF* leaf = &bsp->leaves.data[leaf_idx];
+ U8 hit = 0;
+ F32 best = FLT_MAX;
+
+ BSP_TRACE besthit = *trace;
+
+ for( U32 i = 0; i < leaf->count; ++i ) {
+ U32 face_idx = leaf->first + i;
+ BSP_FACE* f = &bsp->faces.data[face_idx];
+
+ if( f->propid == MAPPROP_SKYBOX )
+ continue;
+
+ BSP_TRACE tr = *trace;
+ if( !bsp_trace_sweep_aabb_to_face( &tr, hull, f, start, end, face_idx ) )
+ continue;
+
+ if( tr.frac < best ) {
+ best = tr.frac;
+ besthit = tr;
+ besthit.leafid = leaf_idx;
+ hit = 1;
+ }
+ }
+
+ if( hit ) {
+ *trace = besthit;
+ trace->hit = 1;
+ }
+ return hit;
+}
+
+U8 bsp_trace_sweep_aabb(
+ BSP_TRACE* trace,
+ BSP* bsp,
+ const AABB& hull,
+ const VEC3& start,
+ const VEC3& end,
+ I32 node_idx
+) {
+ if( bsp_is_leaf( node_idx ) ) {
+ I32 i = bsp_leaf_index( node_idx );
+ return bsp_trace_sweep_aabb_to_leaf( bsp, trace, hull, start, end, i );
+ }
+
+ BSP_NODE* node = &bsp->nodes.data[node_idx];
+ VEC3 n = node->plane.normal;
+ F32 d = node->plane.dist;
+
+ F32 s_dist = vec_dot( n, start ) - d;
+ F32 e_dist = vec_dot( n, end ) - d;
+ F32 offset = aabb_support_radius( hull, n );
+
+ if( s_dist > offset && e_dist > offset )
+ return bsp_trace_sweep_aabb( trace, bsp, hull, start, end, node->front );
+
+ if( s_dist < -offset && e_dist < -offset )
+ return bsp_trace_sweep_aabb( trace, bsp, hull, start, end, node->back );
+
+ VEC3 dir = end - start;
+ F32 denom = ( e_dist - s_dist );
+
+ if( fabsf(denom) < BSP_TRACE_EPSILON ) {
+ I32 near = (s_dist >= 0.f) ? node->front : node->back;
+ I32 far = (s_dist >= 0.f) ? node->back : node->front;
+ if( bsp_trace_sweep_aabb( trace, bsp, hull, start, end, near ) )
+ return 1;
+ return bsp_trace_sweep_aabb( trace, bsp, hull, start, end, far );
+ }
+
+ F32 tin = ( offset - s_dist) / denom;
+ F32 tout = (-offset - s_dist) / denom;
+ if( tin > tout ) { F32 tmp = tin; tin = tout; tout = tmp; }
+
+ tin = m_clamp( tin, 0.f, 1.f );
+ tout = m_clamp( tout , 0.f, 1.f );
+
+ VEC3 mid = start + dir * tin;
+ I32 near = (s_dist > 0.f) ? node->front : node->back;
+ I32 far = (s_dist > 0.f) ? node->back : node->front;
+
+ if( bsp_trace_sweep_aabb( trace, bsp, hull, start, mid, near ) )
+ return 1;
+
+ if( tin <= BSP_TRACE_EPSILON || tin >= 1.f - BSP_TRACE_EPSILON ) {
+ F32 nudge = (denom >= 0.f ? 1.f : -1.f) * BSP_TRACE_EPSILON * 4.f;
+ VEC3 nudged = mid + dir * nudge;
+ return bsp_trace_sweep_aabb( trace, bsp, hull, nudged, end, far );
+ }
+
+ return bsp_trace_sweep_aabb(trace, bsp, hull, mid, end, far );
+}
+
+U8 bsp_trace( BSP_TRACE* trace, BSP* bsp, AABB hull ) {
+ if( !trace || !bsp )
+ return 0;
+
+ trace->hit = 0;
+ bsp_trace_nudge( trace );
+ VEC3 start = trace->in_start;
+ VEC3 end = trace->in_end;
+
+ U8 ret = bsp_trace_sweep_aabb( trace, bsp, hull, start, end, bsp->root );
+ return ret;
+}
+
+U8 bsp_trace( BSP_TRACE* trace, BSP* bsp, AABB hull, VEC3 start, VEC3 end ) {
+ if( !trace || !bsp )
+ return 0;
+
+ trace->in_start = start;
+ trace->in_end = end;
+ return bsp_trace( trace, bsp, hull );
+}
diff --git a/src/game/world/trace.h b/src/game/world/trace.h
new file mode 100644
index 0000000..8a0f4eb
--- /dev/null
+++ b/src/game/world/trace.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "bsp.h"
+#include "../../util/aabb.h"
+
+// quake uses 1 / 32
+const F32 BSP_TRACE_EPSILON = 1.f / 64.f;
+
+struct BSP_TRACE {
+ VEC3 in_start;
+ VEC3 in_end;
+
+ U8 hit;
+ F32 frac;
+ VEC3 point;
+ VEC3 normal;
+ U32 propid;
+ I32 leafid;
+ U32 faceid;
+};
+
+extern U8 bsp_trace( BSP_TRACE* trace, BSP* bsp );
+extern U8 bsp_trace( BSP_TRACE* trace, BSP* bsp, VEC3 start, VEC3 end );
+
+extern U8 bsp_trace( BSP_TRACE* trace, BSP* bsp, AABB aabb );
+extern U8 bsp_trace( BSP_TRACE* trace, BSP* bsp, AABB aabb, VEC3 start, VEC3 end );
+
+extern U8 bsp_trace( BSP_TRACE* trace );
+extern U8 bsp_trace( BSP_TRACE* trace, VEC3 start, VEC3 end );
diff --git a/src/game/world/world.cpp b/src/game/world/world.cpp
new file mode 100644
index 0000000..337b496
--- /dev/null
+++ b/src/game/world/world.cpp
@@ -0,0 +1,14 @@
+#include "world.h"
+#include "../objlist.h"
+
+WORLD* world_create( WORLD_MAP* map ) {
+ WORLD* w = obj_add<WORLD>( "root" );
+ w->map = map;
+
+ return w;
+}
+
+STAT world_populate_entities( WORLD *world ) {
+
+ return STAT_OK;
+}
diff --git a/src/game/world/world.h b/src/game/world/world.h
new file mode 100644
index 0000000..229f235
--- /dev/null
+++ b/src/game/world/world.h
@@ -0,0 +1,11 @@
+#pragma once
+#include "../object.h"
+
+struct WORLD : OBJECT {
+ static const U32 CLASSID = OBJCLASS_WORLD;
+
+ struct WORLD_MAP* map;
+};
+
+extern WORLD* world_create( WORLD_MAP* map );
+extern STAT world_populate_entities( WORLD* world );
diff --git a/src/gl.h b/src/gl.h
new file mode 100644
index 0000000..099788c
--- /dev/null
+++ b/src/gl.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "render/gl.h"
+#include "render/gl_2d.h"
+#include "render/gl_2d_font.h"
diff --git a/src/gui.h b/src/gui.h
new file mode 100644
index 0000000..8a620ff
--- /dev/null
+++ b/src/gui.h
@@ -0,0 +1,13 @@
+#include "gui/base.h"
+
+
+#ifdef IS_EDITOR
+inline void gui_create( struct GAME_DATA* game ) {
+ gui_init( game );
+
+ //gui_window( 300, 200 );
+ //gui_title( "hello, world!" );
+
+ gui_end();
+}
+#endif
diff --git a/src/gui/base.cpp b/src/gui/base.cpp
new file mode 100644
index 0000000..5d345d8
--- /dev/null
+++ b/src/gui/base.cpp
@@ -0,0 +1,405 @@
+#include <string.h>
+
+#include "base.h"
+#include "../game.h"
+
+// =========== [ impl ] ===========
+#include "../util/input.h"
+#include "../render/gl_2d.h"
+#include "../render/gl_2d_font.h"
+// ================================
+
+__gui_internal _gui;
+
+void gui_load_font( GL_DATA* gl, __gui_internal::__font* fnt, const char* name ) {
+ // todo: this probably crashes when fonts r re-created
+ GL_FONT** fptr = gl->fonts.where( fn( GL_FONT** ptr ) {
+ GL_FONT* f = *ptr;
+ return strstr( f->name, name ) != 0;
+ } );
+
+ if( !fptr ) {
+ dlog( "gui_load_font() : error loading font %s\n", name );
+ return;
+ }
+
+ fnt->glfnt = *fptr;
+}
+
+void gui_init( GAME_DATA* game ) {
+ GL_DATA* gl = game->gl;
+ gui_load_font( gl, &_gui.fonts.jpn12, "jpn12" );
+ gui_load_font( gl, &_gui.fonts.jpn16, "jpn16" );
+}
+
+void gui_end() {
+ _gui.cur_window = 0;
+}
+
+void gui_draw( GAME_DATA* game ) {
+ _gui.gl2d = game->shaders.gl2d;
+ _gui.gl2d_font = game->shaders.gl2d_texcoord;
+
+ _gui.windows.each( fn( GUI_WINDOW** w ) {
+ GUI_WINDOW* wnd = *w;
+ if( !wnd->enabled ) return;
+
+ wnd->draw_fn( wnd );
+ } );
+}
+
+void gui_input( GAME_DATA* game ) {
+ for( I32 i = (I32)_gui.windows.size - 1; i >= 0; --i ) {
+ GUI_WINDOW* wnd = _gui.windows[i];
+ if( !wnd->enabled ) continue;
+
+ wnd->input_fn( wnd );
+ break;
+ }
+}
+
+void gui_onframe( GAME_DATA* game ) {
+ gui_run_callbacks();
+
+ gui_input( game );
+ gui_draw( game );
+}
+
+void gui_free( GUI_BASE* node ) {
+ node->children.each( fn( GUI_BASE** ptr ) {
+ GUI_BASE* child = *ptr;
+ gui_free( child );
+ } );
+
+ node->children.clear();
+
+ if( !node->parent ) {
+ I32 idx = _gui.windows.idx_of( (GUI_WINDOW*)node );
+ if( idx != -1 )
+ _gui.windows.erase( idx );
+
+ if( _gui.cur_window == node ) {
+ if( _gui.windows.size > 0 ) {
+ idx = idx > _gui.windows.size - 1 ? _gui.windows.size - 1 : 0;
+ _gui.cur_window = _gui.windows[idx];
+ }
+ }
+ }
+ else if( _gui.cur_view == node ) {
+ _gui.cur_view = 0;
+ for( U32 i = 0; i < node->parent->children.size; ++i ) {
+ GUI_BASE* n = node->parent->children.data[i];
+ if( n == node )
+ continue;
+
+ if( !strcmp( n->name, "BASE_VIEW" ) ) {
+ _gui.cur_view = (GUI_VIEW*)n;
+ break;
+ }
+
+ n = gui_find_node( n, "BASE_VIEW" );
+ if( n != node ) {
+ _gui.cur_view = (GUI_VIEW*)n;
+ break;
+ }
+ }
+ }
+
+ delete node;
+}
+
+I32 gui_relx( GUI_BASE* node ) {
+ I32 x = node->x;
+ for( GUI_BASE* p = node->parent; !!p; p = p->parent ) {
+ x += p->x;
+ }
+ return x;
+}
+
+I32 gui_rely( GUI_BASE* node ) {
+ I32 y = node->y;
+ for( GUI_BASE* p = node->parent; !!p; p = p->parent ) {
+ y += p->y;
+ }
+ return y;
+}
+
+GUI_BASE* gui_find_node( GUI_BASE* root, const char* name ) {
+ if( !root )
+ return 0;
+
+ if( !strcmp( root->name, name ) )
+ return root;
+
+ for( U32 i = 0; i < root->children.size; ++i ) {
+ GUI_BASE* child = root->children.data[i];
+ GUI_BASE* node = gui_find_node( child, name );
+ if( node )
+ return node;
+ }
+
+ return 0;
+}
+
+GUI_WINDOW* gui_get_parent_wnd( GUI_BASE* node ) {
+ if( !node->parent )
+ return (GUI_WINDOW*)node;
+
+ for( GUI_BASE* b = node; !!b; b = b->parent ) {
+ if( !b->parent )
+ return (GUI_WINDOW*)b;
+ }
+
+ return (GUI_WINDOW*)node;
+}
+
+void gui_empty_children( GUI_BASE* node ) {
+ node->children.each( fn( GUI_BASE** ptr ) {
+ gui_free( *ptr );
+ } );
+
+ node->children.clear();
+}
+
+GUI_VIEW* gui_get_view() {
+ return _gui.cur_view;
+}
+
+void gui_set_view( struct GUI_VIEW* view ) {
+ _gui.cur_view = view;
+}
+
+GUI_WINDOW* gui_get_window() {
+ return _gui.cur_window;
+}
+
+void gui_set_window( GUI_WINDOW* window ) {
+ _gui.cur_window = window;
+}
+
+void gui_bring_to_top( GUI_WINDOW* window ) {
+ for( U32 i = 0; i < _gui.windows.size; ++i ) {
+ if( _gui.windows[i] == window ) {
+ _gui.windows.erase( i );
+ break;
+ }
+ }
+
+ _gui.windows.push( window );
+}
+
+U8 gui_is_fg_window( GUI_BASE* node ) {
+ GUI_WINDOW* top_wnd;
+ for( I32 i = _gui.windows.size - 1; i >= 0; --i ) {
+ GUI_WINDOW* wnd = _gui.windows[i];
+ if( !wnd->enabled )
+ continue;
+
+ top_wnd = wnd;
+ break;
+ }
+
+ if( !node->parent ) {
+ return top_wnd == node;
+ }
+
+ return gui_get_parent_wnd( node ) == top_wnd;
+}
+
+U8 gui_check_target() {
+ if( !_gui.cur_view ) {
+ dlog( "gui_check_target() : no view" );
+ return 0;
+ }
+
+ return 1;
+}
+
+void gui_run_callbacks() {
+ LIST<__gui_internal::callback_entry> cb_copy = _gui.callbacks;
+ _gui.callbacks.clear();
+
+ for( I32 i = cb_copy.size - 1; i >= 0; --i ) {
+ __gui_internal::callback_entry* e = &cb_copy[i];
+ if( e->callback )
+ e->callback( e->data );
+ }
+}
+
+void gui_push_callback( GUI_CALLBACK cb ) {
+ __gui_internal::callback_entry e;
+ e.data = 0;
+ e.callback = cb;
+
+ _gui.callbacks.push( e );
+}
+
+void gui_push_callback( void* data, GUI_CALLBACK cb ) {
+ __gui_internal::callback_entry e;
+ e.data = data;
+ e.callback = cb;
+
+ _gui.callbacks.push( e );
+}
+
+void gui_base_input_fn( void* ptr ) {
+ GUI_BASE* e = (GUI_BASE*)ptr;
+ if( !e ) return;
+
+ e->children.each( fn( GUI_BASE** ptr ) {
+ GUI_BASE* child = *ptr;
+ if( child->enabled && child->input_fn )
+ child->input_fn( child );
+ } );
+}
+
+void gui_draw_frect( I32 x, I32 y, I32 w, I32 h, CLR col ) {
+ gl_2d_frect( _gui.gl2d, { (F32)x, (F32)y }, { (F32)w, (F32)h }, col );
+}
+
+void gui_draw_rect( I32 x, I32 y, I32 w, I32 h, CLR col ) {
+ gl_2d_rect( _gui.gl2d, { (F32)x, (F32)y }, { (F32)w, (F32)h }, col );
+}
+
+void gui_draw_line( I32 x0, I32 y0, I32 x1, I32 y1, CLR col ) {
+ gl_2d_line( _gui.gl2d, { (F32)x0, (F32)y0 }, { (F32)x1, (F32)y1 }, col );
+}
+
+GL_FONT* gui_font_from_idx( U32 fnt ) {
+ if( fnt > FNT_LAST ) {
+ dlog( "gui_font_from_idx() : invalid font index %d", fnt );
+ return 0;
+ }
+
+ __gui_internal::__font* pfnt;
+ switch( fnt ) {
+ case FNT_JPN12: pfnt = &_gui.fonts.jpn12; break;
+ case FNT_JPN16: pfnt = &_gui.fonts.jpn16; break;
+ default: return 0;
+ };
+
+ return pfnt->glfnt;
+}
+
+void gui_draw_str_internal( I32 x, I32 y, U8 align, U32 fnt, CLR col, const char* str ) {
+ GL_FONT* pfnt = gui_font_from_idx( fnt );
+ if( !pfnt ) return;
+
+ I32 w;
+ gui_draw_get_str_bounds( &w, 0, fnt, str );
+ switch( align ) {
+ case ALIGN_R: x -= w; break;
+ case ALIGN_C: x -= (I32)( w * 0.5f ); break;
+ }
+
+ gl_font_draw( pfnt, _gui.gl2d_font, { (F32)x, (F32)y }, str, col );
+}
+
+void gui_draw_str( I32 x, I32 y, U8 align, U32 fnt, CLR col, const char* fmt, ... ) {
+ va_list args;
+ va_start( args, fmt );
+ char buf[GUI_STR_BUF_MAX];
+ vsprintf( buf, fmt, args );
+ va_end( args );
+
+ gui_draw_str_internal( x, y, align, fnt, col, buf );
+}
+
+void gui_draw_get_str_bounds_internal( I32* w, I32* h, U32 fnt, const char* buf ) {
+ GL_FONT* pfnt = gui_font_from_idx( fnt );
+ if( !pfnt ) return;
+
+ VEC2 dim = gl_font_dim( pfnt, buf );
+ if( w ) *w = dim.x;
+ if( h ) *h = dim.y;
+}
+
+void gui_draw_get_str_bounds( I32* w, I32* h, U32 fnt, const char *fmt, ... ) {
+ va_list args;
+ va_start( args, fmt );
+ char buf[GUI_STR_BUF_MAX];
+ vsprintf( buf, fmt, args );
+ va_end( args );
+
+ gui_draw_get_str_bounds_internal( w, h, fnt, buf );
+}
+
+void gui_draw_get_clip( I32 *x, I32 *y, I32 *w, I32 *h ) {
+ VEC2 start, dim;
+ gl_get_clip( _gui.gl2d->gl, &start, &dim );
+
+ if( x ) *x = (I32)start.x;
+ if( y ) *y = (I32)start.y;
+ if( w ) *w = (I32)dim.x;
+ if( h ) *h = (I32)dim.y;
+}
+
+void gui_draw_set_clip( I32 x, I32 y, I32 w, I32 h ) {
+ VEC2 start, dim;
+ start.x = (F32)x;
+ start.y = (F32)y;
+ dim.x = (F32)w;
+ dim.y = (F32)h;
+
+ gl_set_clip( _gui.gl2d->gl, start, dim );
+}
+
+void gui_draw_reset_clip() {
+ gl_reset_clip( _gui.gl2d->gl );
+}
+
+void gui_draw_push_clip( I32 x, I32 y, I32 w, I32 h ) {
+ I32 cx,cy,cw,ch;
+ gui_draw_get_clip( &cx, &cy, &cw, &ch );
+
+ __gui_internal::clip_rect rect{ cx, cy, cw, ch };
+ _gui.clip_rects.push( rect );
+
+ gui_draw_set_clip( x, y, w, h );
+}
+
+void gui_draw_pop_clip() {
+ if( !_gui.clip_rects.size ) {
+ dlog( "gui_draw_pop_clip() : clip stack empty\n" );
+ return gui_draw_reset_clip();
+ }
+
+ __gui_internal::clip_rect rect = _gui.clip_rects.pop();
+ gui_draw_set_clip( rect.x, rect.y, rect.w, rect.h );
+}
+
+void gui_cursor_pos( I32 *x, I32 *y ) {
+ if( x ) *x = input.mouse.pos.x;
+ if( y ) *y = input.mouse.pos.y;
+}
+
+U8 gui_mbutton_down( U8 button ) {
+ switch( button ) {
+ case GUI_MBTNLEFT:
+ return input.mouse.left;
+ case GUI_MBTNRIGHT:
+ return input.mouse.right;
+ case GUI_MBTNMIDDLE:
+ return input.mouse.middle;
+ case GUI_MBTNSCROLL:
+ return input.mouse.wheel;
+ };
+
+ return 0;
+}
+
+void gui_capture_scroll() {
+ input.mouse.wheel = 0;
+}
+
+U8 gui_key_down( U8 key ) {
+ return input.keys[key];
+}
+
+F32 gui_frametime() {
+ return _gui.gl2d->gl->frametime;
+}
+
+F32 gui_time() {
+ return u_time();
+}
diff --git a/src/gui/base.h b/src/gui/base.h
new file mode 100644
index 0000000..96e0fa5
--- /dev/null
+++ b/src/gui/base.h
@@ -0,0 +1,314 @@
+#pragma once
+#include "../util/color.h"
+#include "../util/allocator.h"
+
+// ======================================= [ colorscheme ] ========================================
+
+static const struct {
+ CLR txt = CLR::WHITE();
+ CLR txt_inactive = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.3f );
+ CLR border = CLR::WHITE();
+ CLR border_inactive = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.55f );
+ CLR bg = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.92f );
+ CLR bg_sec = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.97f );
+ CLR bg_alt = CLR::blend( CLR::WHITE(), CLR::BLACK(), 0.985f );
+} ui_clr;
+
+// ========================================== [ base ] ============================================
+
+const U32 GUI_STR_BUF_MAX = 16384;
+const U32 GUI_TEXTBOX_MAX = 1024;
+const U32 GUI_NAME_LEN = 512;
+
+/* impl */
+extern void gui_init( struct GAME_DATA* game );
+extern void gui_draw( struct GAME_DATA* game );
+extern void gui_input( struct GAME_DATA* game );
+extern void gui_onframe( struct GAME_DATA* game );
+extern void gui_end();
+/* */
+
+extern I32 gui_relx( struct GUI_BASE* node ); //relative x pos for node
+extern I32 gui_rely( struct GUI_BASE* node ); //relative y pos for node
+
+extern struct GUI_BASE* gui_find_node( struct GUI_BASE* root, const char* name );
+extern struct GUI_WINDOW* gui_get_parent_wnd( struct GUI_BASE* node );
+extern void gui_empty_children( struct GUI_BASE* node );
+
+extern void gui_free( struct GUI_BASE* node );
+
+extern struct GUI_VIEW* gui_get_view(); // gets currently edited view
+extern void gui_set_view( struct GUI_VIEW* view ); // sets currently edited view
+extern struct GUI_WINDOW* gui_get_window(); // gets currently edited window
+extern void gui_set_window( struct GUI_WINDOW* wnd ); // sets currently edited window
+extern void gui_bring_to_top( struct GUI_WINDOW* wnd ); // pushes window to be rendered last
+extern U8 gui_is_fg_window( struct GUI_BASE* node ); // 1 if parent window (or window itself) is rendered last
+
+// ======================================= [ components ] =========================================
+
+struct GUI_LIST_ENTRY {
+ I32 val;
+ char title[256];
+};
+
+typedef void( *GUI_CALLBACK )( void* ptr );
+
+extern struct GUI_VIEW* gui_view( I32 x, I32 y, I32 w, I32 h ); // sets current view
+extern struct GUI_WINDOW* gui_window( I32 w, I32 h ); // sets current view and window
+extern struct GUI_WINDOW* gui_window( I32 x, I32 y, I32 w, I32 h ); // sets current view and window
+extern struct GUI_TITLE* gui_title( const char* title );
+
+extern struct GUI_LABEL* gui_label( I32 x, I32 y, const char* title, ... );
+extern struct GUI_BUTTON* gui_button( I32 x, I32 y, I32 w, I32 h, const char* title, GUI_CALLBACK cb );
+extern struct GUI_TEXTBOX* gui_textbox( I32 x, I32 y, I32 w, I32 h, const char* title, U32 maxlen, U8 allow_newl = 0 );
+extern struct GUI_CHECKBOX* gui_checkbox( I32 x, I32 y, const char* title, U8* pval );
+
+extern struct GUI_LIST* gui_list( I32 x, I32 y, I32 w, I32 h, const char* title, LIST<GUI_LIST_ENTRY>* list, I32* pval );
+extern struct GUI_LIST_ENTRY* gui_list_get_selected( struct GUI_LIST* list );
+
+extern struct GUI_FLOATINPUT* gui_floatinput(
+ I32 x, I32 y, I32 w,
+ const char* title,
+ F32* pval,
+ F32 min,
+ F32 max,
+ F32 step = 1.f,
+ const char* printfmt = "%.2f"
+);
+
+extern struct GUI_VECTORINPUT* gui_vectorinput(
+ I32 x, I32 y, I32 w,
+ const char* title,
+ F32* pval,
+ U32 valc,
+ F32 min,
+ F32 max,
+ F32 step = 1.f,
+ const char* letters = "xyzw",
+ const char* printfmt = "%.2f"
+);
+
+extern struct GUI_COLORINPUT* gui_colorinput(
+ I32 x,
+ I32 y,
+ I32 w,
+ const char* title,
+ CLR* pval,
+ U8 showalpha = 1
+);
+
+// ======================================= [ definitions ] ========================================
+
+enum GuiTxtAlign_t {
+ ALIGN_L,
+ ALIGN_R,
+ ALIGN_C
+};
+
+enum GuiFont_t {
+ FNT_JPN12,
+ FNT_JPN16,
+ FNT_LAST
+};
+
+typedef void( *GUI_DRAW_FN )( void* el );
+typedef void( *GUI_INPUT_FN )( void* el );
+
+extern void gui_base_input_fn( void* );
+
+struct GUI_BASE {
+ I32 x{}, y{};
+ I32 w{}, h{};
+ U8 enabled{1};
+ U8 canvas{};
+
+ char name[GUI_NAME_LEN];
+
+ I32 xbound{}, ybound{};
+ I32 xoff{}, yoff{};
+
+ GUI_BASE* parent{};
+ LIST<GUI_BASE*> children{};
+
+ GUI_DRAW_FN draw_fn{};
+ GUI_INPUT_FN input_fn = gui_base_input_fn;
+};
+
+struct GUI_VIEW : GUI_BASE {
+ U8 initheld;
+};
+struct GUI_WINDOW : GUI_VIEW {
+ U8 locked{};
+ U8 ontop{};
+};
+
+struct GUI_LABEL : GUI_BASE {};
+
+struct GUI_TITLE : GUI_BASE {
+ I32 xoff, yoff;
+ U8 held{};
+};
+
+struct GUI_LIST : GUI_BASE {
+ LIST<GUI_LIST_ENTRY>* plist;
+ I32* pval;
+ U8 held;
+
+ GUI_CALLBACK cb;
+};
+
+struct GUI_BUTTON : GUI_BASE {
+ GUI_CALLBACK cb;
+ U8 held;
+ void* extra;
+};
+
+struct GUI_CHECKBOX : GUI_BASE {
+ U8 held;
+
+ U8* pval;
+ GUI_CALLBACK cb;
+};
+
+struct GUI_TEXTBOX : GUI_BASE {
+ char value[GUI_TEXTBOX_MAX];
+
+ U32 len;
+ U32 maxlen;
+
+ U8 keys[0xff];
+ U8 prevkeys[0xff];
+
+ U8 heldkey;
+ F32 heldtime;
+ F32 repeattime;
+
+ F32 blinktime;
+ U8 blink;
+
+ U8 allow_newl;
+ U8 active;
+
+ U8 m1held;
+
+ GUI_CALLBACK cb;
+};
+
+struct GUI_FLOATINPUT : GUI_BASE {
+ const char* valfmt;
+ F32* pval;
+
+ F32 min;
+ F32 max;
+ F32 step;
+
+ I32 lastmx;
+ U8 heldoutbounds;
+ U8 held;
+
+ // for color sliders
+ CLR bgcol;
+ U8 customclr;
+
+ U8 wraparound;
+ GUI_CALLBACK cb;
+};
+
+struct GUI_VECTORINPUT : GUI_VIEW {
+ const char* valfmt;
+ F32* pval;
+ U32 valc;
+
+ const char* letters;
+ F32 min;
+ F32 max;
+ F32 step;
+
+ GUI_VIEW* inputview;
+ LIST<GUI_FLOATINPUT*> inputs;
+
+ GUI_CALLBACK cb;
+};
+
+struct GUI_COLORINPUT : GUI_VECTORINPUT {};
+
+// ======================================== [ internal ] ==========================================
+
+extern void gui_push_callback( GUI_CALLBACK cb );
+extern void gui_push_callback( void* data, GUI_CALLBACK cb );
+extern void gui_run_callbacks();
+
+extern U8 gui_check_target();
+
+extern void gui_draw_line( I32 x0, I32 y0, I32 x1, I32 y1, CLR col );
+extern void gui_draw_rect( I32 x, I32 y, I32 w, I32 h, CLR col );
+extern void gui_draw_frect( I32 x, I32 y, I32 w, I32 h, CLR col );
+extern void gui_draw_circle( I32 x, I32 y, I32 r, CLR col );
+
+extern void gui_draw_str( I32 x, I32 y, U8 align, U32 fnt, CLR col, const char* fmt, ... );
+extern void gui_draw_get_str_bounds( I32* w, I32* h, U32 fnt, const char* fmt, ... );
+
+extern void gui_draw_get_clip( I32* x, I32* y, I32* w, I32* h );
+extern void gui_draw_set_clip( I32 x, I32 y, I32 w, I32 h );
+extern void gui_draw_reset_clip();
+extern void gui_draw_push_clip( I32 x, I32 y, I32 w, I32 h );
+extern void gui_draw_pop_clip();
+
+extern F32 gui_frametime();
+extern F32 gui_time();
+extern void gui_cursor_pos( I32* x, I32* y );
+extern U8 gui_key_down( U8 key );
+extern U8 gui_mbutton_down( U8 button );
+extern void gui_capture_scroll();
+
+enum __gui_internal_mouse_button {
+ GUI_MBTNLEFT,
+ GUI_MBTNRIGHT,
+ GUI_MBTNMIDDLE,
+ GUI_MBTNSCROLL
+};
+
+struct __gui_internal {
+ GUI_VIEW* cur_view;
+ GUI_WINDOW* cur_window;
+ LIST<GUI_WINDOW*> windows;
+ struct clip_rect {
+ I32 x,y,w,h;
+ };
+
+ LIST<clip_rect> clip_rects;
+ struct callback_entry {
+ GUI_CALLBACK callback;
+ void* data;
+ };
+
+ LIST<callback_entry> callbacks;
+
+ struct __font {
+ struct GL_FONT* glfnt;
+ };
+
+ struct {
+ __font jpn12;
+ __font jpn16;
+ } fonts;
+
+ // draw stuff below
+ struct GL_SHADER_PROGRAM* gl2d;
+ struct GL_SHADER_PROGRAM* gl2d_font;
+};
+
+void __gui_internal_vectorinput_init(
+ GUI_VECTORINPUT* input,
+ I32 x, I32 y, I32 w,
+ const char* title,
+ F32* pval,
+ U32 valc,
+ F32 min,
+ F32 max,
+ F32 step,
+ const char* letters,
+ const char* printfmt
+);
+
+extern __gui_internal _gui;
diff --git a/src/gui/button.cpp b/src/gui/button.cpp
new file mode 100644
index 0000000..2b57772
--- /dev/null
+++ b/src/gui/button.cpp
@@ -0,0 +1,66 @@
+#include "base.h"
+
+void gui_button_draw_fn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+
+ I32 x = gui_relx( btn );
+ I32 y = gui_rely( btn );
+
+ CLR col = gui_is_fg_window( btn )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x, y, btn->w, btn->h, col );
+ gui_draw_frect( x+1, y+1, btn->w-2, btn->h-2, ui_clr.bg_sec );
+
+ I32 middle = x + btn->w/2;
+ I32 middle_y = y + btn->h/2 - 7;
+
+ gui_draw_str( middle, middle_y, ALIGN_C, FNT_JPN12, ui_clr.txt, btn->name );
+}
+
+void gui_button_input_fn( void* ptr ) {
+ GUI_BUTTON* btn = (GUI_BUTTON*)ptr;
+
+ I32 x = gui_relx( btn );
+ I32 y = gui_rely( btn );
+
+ U8 m1 = gui_mbutton_down( 0 );
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ U8 inbounds = mx >= x && mx <= x + btn->w && my >= y && my <= y + btn->h;
+
+ if( !m1 ) {
+ // button could be destroyed by callback
+ U8 was_held = btn->held;
+ btn->held = 0;
+ if( inbounds && was_held )
+ btn->cb( btn );
+
+ return;
+ }
+
+ if( inbounds ) {
+ btn->held = 1;
+ }
+}
+
+GUI_BUTTON* gui_button( I32 x, I32 y, I32 w, I32 h, const char* title, GUI_CALLBACK cb ) {
+ if( !gui_check_target() ) return 0;
+
+ GUI_BUTTON* btn = new GUI_BUTTON;
+ btn->x = x;
+ btn->y = y;
+ btn->xbound = btn->w = w;
+ btn->ybound = btn->h = h;
+ btn->cb = cb;
+ btn->draw_fn = gui_button_draw_fn;
+ btn->input_fn = gui_button_input_fn;
+
+ btn->parent = _gui.cur_view;
+ strcpy( btn->name, title );
+
+ btn->extra = 0;
+ btn->held = 0;
+
+ gui_get_view()->children.push( btn );
+ return btn;
+}
diff --git a/src/gui/checkbox.cpp b/src/gui/checkbox.cpp
new file mode 100644
index 0000000..02cadc6
--- /dev/null
+++ b/src/gui/checkbox.cpp
@@ -0,0 +1,78 @@
+#include "base.h"
+
+const I32 CHECKBOX_SIZE = 14;
+
+void gui_checkbox_draw_fn( void* ptr ) {
+ GUI_CHECKBOX* check = (GUI_CHECKBOX*)ptr;
+
+ I32 x = gui_relx( check );
+ I32 y = gui_rely( check );
+
+ I32 half = CHECKBOX_SIZE / 2;
+
+ CLR col = gui_is_fg_window( check )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x, y+2, CHECKBOX_SIZE, CHECKBOX_SIZE, col );
+ gui_draw_frect( x+1, y+3, CHECKBOX_SIZE-2, CHECKBOX_SIZE-2, ui_clr.bg_sec );
+
+ if( *check->pval )
+ gui_draw_str( x + half, y, ALIGN_C, FNT_JPN12, ui_clr.txt, "x" );
+
+ gui_draw_str( x + CHECKBOX_SIZE + 2, y, ALIGN_L, FNT_JPN12, ui_clr.txt, check->name );
+}
+
+void gui_checkbox_input_fn( void* ptr ) {
+ GUI_CHECKBOX* check = (GUI_CHECKBOX*)ptr;
+
+ I32 x = gui_relx( check );
+ I32 y = gui_rely( check );
+
+ U8 m1 = gui_mbutton_down( 0 );
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ gui_draw_get_str_bounds( &check->w, 0, FNT_JPN12, check->name );
+ check->w += CHECKBOX_SIZE + 2;
+
+ U8 inbounds = mx >= x && mx <= x + check->w && my >= y && my <= y + check->h;
+
+ if( !m1 ) {
+ // checkbox could be destroyed by callback
+ U8 was_held = check->held;
+ check->held = 0;
+ if( inbounds && was_held ) {
+ *check->pval = !*check->pval;
+ if( check->cb )
+ check->cb( check );
+ }
+
+ return;
+ }
+
+ if( inbounds )
+ check->held = 1;
+}
+
+GUI_CHECKBOX* gui_checkbox( I32 x, I32 y, const char* title, U8* pval ) {
+ if( !gui_check_target() ) return 0;
+
+ GUI_CHECKBOX* check = new GUI_CHECKBOX;
+ check->x = x;
+ check->y = y;
+ check->cb = 0;
+ check->ybound = check->h = 16;
+ check->draw_fn = gui_checkbox_draw_fn;
+ check->input_fn = gui_checkbox_input_fn;
+
+ check->pval = pval;
+
+ check->parent = _gui.cur_view;
+ strcpy( check->name, title );
+
+ gui_draw_get_str_bounds( &check->w, 0, FNT_JPN12, check->name );
+ check->w += CHECKBOX_SIZE + 2;
+ check->xbound = check->w;
+ check->ybound = check->h;
+
+ gui_get_view()->children.push( check );
+ return check;
+}
diff --git a/src/gui/colorinput.cpp b/src/gui/colorinput.cpp
new file mode 100644
index 0000000..1d649fb
--- /dev/null
+++ b/src/gui/colorinput.cpp
@@ -0,0 +1,59 @@
+#include "base.h"
+#include <stdio.h>
+
+void gui_colorinput_draw_fn( void* ptr ) {
+ GUI_COLORINPUT* input = (GUI_COLORINPUT*)ptr;
+
+ I32 x = gui_relx( input );
+ I32 y = gui_rely( input );
+
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, input->name );
+
+ CLR val = *(CLR*)input->pval;
+ char hex[16];
+ sprintf( hex, "#%02x%02x%02x",
+ (I32)(val.r * 255.f),
+ (I32)(val.g * 255.f),
+ (I32)(val.b * 255.f)
+ );
+
+ gui_draw_str( x + input->w, y, ALIGN_R, FNT_JPN12, ui_clr.txt, hex );
+
+ CLR border = {
+ 1.f - val.r,
+ 1.f - val.g,
+ 1.f - val.b,
+ 1.f
+ };
+
+ gui_draw_frect( x + input->w - 21, y + 14, 22, 22, border );
+ gui_draw_frect( x + input->w - 19, y + 16, 18, 18, CLR::BLACK() );
+ gui_draw_frect( x + input->w - 19, y + 16, 18, 18, *(CLR*)(input->pval) );
+
+ GUI_VIEW* inputview = input->inputview;
+ inputview->draw_fn( inputview );
+}
+
+GUI_COLORINPUT* gui_colorinput( I32 x, I32 y, I32 w, const char* title, CLR* pval, U8 showalpha ) {
+ if( !gui_check_target() ) return 0;
+
+ GUI_COLORINPUT* input = new GUI_COLORINPUT;
+
+ __gui_internal_vectorinput_init(
+ input,
+ x, y,
+ w - 26,
+ title,
+ (F32*)pval,
+ showalpha? 4 : 3,
+ 0.f,
+ 1.f,
+ 1.f / 255.f,
+ "rgba",
+ "%.02f"
+ );
+
+ input->xbound = input->w = w;
+ input->draw_fn = gui_colorinput_draw_fn;
+ return input;
+}
diff --git a/src/gui/console.h b/src/gui/console.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/gui/console.h
diff --git a/src/gui/floatinput.cpp b/src/gui/floatinput.cpp
new file mode 100644
index 0000000..9f7fdfe
--- /dev/null
+++ b/src/gui/floatinput.cpp
@@ -0,0 +1,257 @@
+#include "base.h"
+#include <math.h>
+
+U8 gui_floatinput_is_bound_val( GUI_FLOATINPUT* input ) {
+ if( input->wraparound )
+ return 0;
+ if( !isfinite( input->min ) || isnan( input->min ) )
+ return 0;
+ if( !isfinite( input->max ) || isnan( input->max ) )
+ return 0;
+ if( input->min > input->max )
+ return 0;
+ return 1;
+}
+
+CLR gui_floatinput_get_progress_clr( GUI_FLOATINPUT* input ) {
+ if( input->customclr ) {
+ if( gui_is_fg_window( input ) ) return input->bgcol;
+ else return CLR::blend( input->bgcol, CLR::BLACK(), 0.333f );
+ }
+
+ CLR clr_border = gui_is_fg_window( input )? ui_clr.border : ui_clr.border_inactive;
+ return CLR::blend( clr_border, CLR::BLACK(), 0.5f );
+}
+
+void gui_floatinput_draw_bound( GUI_FLOATINPUT* input ) {
+ I32 x = gui_relx( input );
+ I32 y = gui_rely( input );
+ I32 w = input->w;
+ I32 h = input->h;
+
+ F32 min = input->min;
+ F32 max = input->max;
+
+ F32 val = *input->pval;
+ F32 percent = (val - min) / (max - min);
+ if( percent > 1.f ) percent = 1.f;
+ if( percent < 0.f ) percent = 0.f;
+
+ CLR clr_progress = gui_floatinput_get_progress_clr( input );
+ gui_draw_frect( x+1, y+1, (I32)( (w-2) * percent ), h-2, clr_progress );
+
+ gui_draw_push_clip( (x+1) + (I32)( (w-2) * percent ), (y+1), (I32)( (w-2) * (1.f - percent) ), h-2 );
+ gui_draw_str( x + 2, y + 2, ALIGN_L, FNT_JPN12, ui_clr.txt, input->name );
+ gui_draw_str( x + w - 2, y + 2, ALIGN_R, FNT_JPN12, ui_clr.txt, input->valfmt, val );
+ gui_draw_set_clip( (x+1), (y+1), (I32)( (w-2) * percent ), h-2 );
+ gui_draw_str( x + 2, y + 2, ALIGN_L, FNT_JPN12, ui_clr.bg_alt, input->name );
+ gui_draw_str( x + w - 2, y + 2, ALIGN_R, FNT_JPN12, ui_clr.bg_alt, input->valfmt, val );
+ gui_draw_pop_clip();
+}
+
+void gui_floatinput_draw_unbound( GUI_FLOATINPUT* input ) {
+ I32 x = gui_relx( input );
+ I32 y = gui_rely( input );
+ I32 w = input->w;
+
+ F32 val = *input->pval;
+
+ gui_draw_str( x + 2, y + 2, ALIGN_L, FNT_JPN12, ui_clr.txt, input->name );
+ gui_draw_str( x + w - 2, y + 2, ALIGN_R, FNT_JPN12, ui_clr.txt, input->valfmt, val );
+
+ I32 t1w, t2w, t3w;
+ gui_draw_get_str_bounds( &t1w, 0, FNT_JPN12, input->name );
+ gui_draw_get_str_bounds( &t2w, 0, FNT_JPN12, input->valfmt, val );
+ gui_draw_get_str_bounds( &t3w, 0, FNT_JPN12, "<->" );
+
+ I32 stw = t2w + t3w - 2;
+ I32 pos = w/2;
+ if( stw > pos )
+ pos = stw;
+
+ CLR handleclr = CLR::blend( ui_clr.txt, CLR::BLACK(), .7f );
+
+ if( t1w < w / 2 && t1w + t2w + t3w + 8 < w ) {
+ gui_draw_str(
+ x + w - pos,
+ y + 2,
+ ALIGN_C,
+ FNT_JPN12,
+ handleclr,
+ "<->"
+ );
+ }
+ else {
+ gui_draw_str( x + w - pos + 8, y, ALIGN_C, FNT_JPN12, handleclr, "-" );
+ gui_draw_str( x + w - pos + 8, y + 5, ALIGN_C, FNT_JPN12, handleclr, "+" );
+ }
+}
+
+void gui_floatinput_draw_fn( void* ptr ) {
+ GUI_FLOATINPUT* input = (GUI_FLOATINPUT*)ptr;
+
+ I32 x = gui_relx( input );
+ I32 y = gui_rely( input );
+ I32 w = input->w;
+ I32 h = input->h;
+
+ CLR clr_border = gui_is_fg_window( input )? ui_clr.border : ui_clr.border_inactive;
+
+ gui_draw_frect( x, y, w, h, clr_border );
+ gui_draw_frect( x+1, y+1, w-2, h-2, ui_clr.bg_sec );
+
+ if( !gui_floatinput_is_bound_val( input ) )
+ gui_floatinput_draw_unbound( input );
+ else
+ gui_floatinput_draw_bound( input );
+}
+
+void gui_floatinput_input_bound( GUI_FLOATINPUT* input ) {
+ if( !gui_mbutton_down( GUI_MBTNLEFT ) )
+ return;
+
+ I32 x = gui_relx( input );
+ I32 w = input->w;
+
+ F32 min = input->min;
+ F32 max = input->max;
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ F32 progress = (F32)(mx - x) / w;
+ if( progress < 0.f ) progress = 0.f;
+ if( progress > 1.f ) progress = 1.f;
+
+ F32 nval = min + (max - min) * progress;
+ F32 rmn = remainderf( nval, input->step );
+ *input->pval = nval - rmn;
+}
+
+void gui_floatinput_input_unbound( GUI_FLOATINPUT* input ) {
+ if( !gui_mbutton_down( GUI_MBTNLEFT ) )
+ return;
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ I32 dx = mx - input->lastmx;
+ if( dx )
+ *input->pval += dx * input->step;
+
+ F32 min = input->min;
+ F32 max = input->max;
+
+ if( isfinite( min ) && *input->pval < min )
+ *input->pval = input->wraparound? max : min;
+ if( isfinite( max ) && *input->pval > max )
+ *input->pval = input->wraparound? min : max;
+
+ F32 rmn = remainderf( *input->pval, input->step );
+ *input->pval -= rmn;
+}
+
+void gui_floatinput_input_scroll( GUI_FLOATINPUT* input ) {
+ U8 scroll = gui_mbutton_down( GUI_MBTNSCROLL );
+ gui_capture_scroll();
+ F32 oldval = *input->pval;
+ F32 nval = oldval;
+
+ if( !scroll )
+ return;
+
+ if( scroll == 1 )
+ nval += input->step;
+ else if( scroll == (U8)-1 )
+ nval -= input->step;
+
+ if( isfinite( input->min ) && nval < input->min )
+ nval = input->wraparound? input->max : input->min;
+ if( isfinite( input->max ) && nval > input->max )
+ nval = input->wraparound? input->min : input->max;
+
+ F32 rmn = remainderf( nval, input->step );
+ *input->pval = nval - rmn;
+
+ if( input->cb )
+ input->cb( input );
+}
+
+void gui_floatinput_input_fn( void* ptr ) {
+ GUI_FLOATINPUT* input = (GUI_FLOATINPUT*)ptr;
+
+ I32 m1 = gui_mbutton_down( 0 );
+
+ I32 x = gui_relx( input );
+ I32 y = gui_rely( input );
+ I32 w = input->w;
+ I32 h = input->h;
+
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+ U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h;
+
+ if( inbounds )
+ gui_floatinput_input_scroll( input );
+
+ if( !input->held && m1 && !inbounds )
+ input->heldoutbounds = 1;
+ if( !input->heldoutbounds && m1 && inbounds ) {
+ if( !input->held ) {
+ input->lastmx = mx;
+ input->held = 1;
+ return;
+ }
+ }
+
+ if( !m1 ) {
+ input->heldoutbounds = 0;
+ input->held = 0;
+ return;
+ }
+
+ if( input->heldoutbounds )
+ return;
+
+ F32 oldv = *input->pval;
+ if( !gui_floatinput_is_bound_val( input ) )
+ gui_floatinput_input_unbound( input );
+ else
+ gui_floatinput_input_bound( input );
+
+ if( input->cb && oldv != *input->pval ) {
+ input->cb( input );
+ }
+
+ input->lastmx = mx;
+}
+
+struct GUI_FLOATINPUT* gui_floatinput( I32 x, I32 y, I32 w, const char* title, F32* pval, F32 min, F32 max, F32 step, const char* valfmt ) {
+ if( !gui_check_target() ) return 0;
+
+ GUI_FLOATINPUT* input = new GUI_FLOATINPUT;
+ input->x = x;
+ input->y = y;
+ input->xbound = input->w = w;
+ input->ybound = input->h = 20;
+ strcpy( input->name, title );
+ input->input_fn = gui_floatinput_input_fn;
+ input->draw_fn = gui_floatinput_draw_fn;
+
+ input->cb = 0;
+ input->pval = pval;
+ input->min = min;
+ input->max = max;
+ input->step = step;
+ input->valfmt = valfmt;
+
+ input->wraparound = 0;
+ input->customclr = 0;
+ input->held = 0;
+
+ GUI_VIEW* parent = gui_get_view();
+ parent->children.push( input );
+ input->parent = parent;
+
+ return input;
+}
diff --git a/src/gui/label.cpp b/src/gui/label.cpp
new file mode 100644
index 0000000..b5d6290
--- /dev/null
+++ b/src/gui/label.cpp
@@ -0,0 +1,40 @@
+#include "base.h"
+#include <stdarg.h>
+#include <stdio.h>
+
+void gui_label_draw_fn( void* ptr ) {
+ GUI_LABEL* label = (GUI_LABEL*)ptr;
+
+ I32 x = gui_relx( label );
+ I32 y = gui_rely( label );
+
+ gui_draw_get_str_bounds( &label->w, &label->h, FNT_JPN12, label->name );
+ label->xbound = label->w;
+ label->ybound = label->h;
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, label->name );
+}
+
+GUI_LABEL* gui_label( I32 x, I32 y, const char* title, ... ) {
+ if( !gui_check_target() ) return 0;
+ char buf[GUI_NAME_LEN];
+
+ va_list args;
+ va_start( args, title );
+ vsprintf( buf, title, args );
+ va_end( args );
+
+ GUI_LABEL* label = new GUI_LABEL;
+ label->x = x;
+ label->y = y;
+ label->draw_fn = gui_label_draw_fn;
+ strcpy( label->name, buf );
+
+ gui_draw_get_str_bounds( &label->w, &label->h, FNT_JPN12, label->name );
+ label->xbound = label->w;
+ label->ybound = label->h;
+
+ label->parent = gui_get_view();
+ label->parent->children.push( label );
+
+ return label;
+}
diff --git a/src/gui/list.cpp b/src/gui/list.cpp
new file mode 100644
index 0000000..90f2f49
--- /dev/null
+++ b/src/gui/list.cpp
@@ -0,0 +1,98 @@
+#include "base.h"
+
+const I32 LIST_TITLE_OFFSET = 15;
+const I32 LIST_ITEM_HEIGHT = 18;
+
+void gui_list_draw_fn( void* ptr ) {
+ GUI_LIST* list = (GUI_LIST*)ptr;
+
+ I32 x = gui_relx( list );
+ I32 y = gui_rely( list );
+
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, list->name );
+ y += LIST_TITLE_OFFSET;
+
+ CLR col = gui_is_fg_window( list )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( x, y, list->w, list->h, col );
+ gui_draw_frect( x+1, y+1, list->w-2, list->h-2, ui_clr.bg_sec );
+
+ I32 yoff = 0;
+ list->plist->each( fn( GUI_LIST_ENTRY* e ) {
+ U8 selected = e->val == *list->pval;
+ CLR col = selected? ui_clr.txt : ui_clr.txt_inactive;
+
+ gui_draw_str( x + 4, y + yoff + 6, ALIGN_L, FNT_JPN12, col, e->title );
+ yoff += LIST_ITEM_HEIGHT;
+ } );
+}
+
+void gui_list_input_fn( void* ptr ) {
+ GUI_LIST* list = (GUI_LIST*)ptr;
+
+ I32 x = gui_relx( list );
+ I32 y = gui_rely( list );
+ I32 w = list->w;
+ I32 h = list->h;
+
+ U8 m1 = gui_mbutton_down( 0 );
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h;
+ if( !inbounds )
+ return;
+
+ I32 diff = my - y;
+ I32 idx = diff / LIST_ITEM_HEIGHT - 1;
+ if( idx >= list->plist->size ) idx = list->plist->size - 1;
+ if( idx < 0 ) idx = 0;
+
+ if( m1 ) {
+ if( !list->held ) {
+ GUI_LIST_ENTRY* e = &list->plist->data[idx];
+ I32 prevval = *list->pval;
+ *list->pval = e->val;
+
+ if( list->cb && prevval != *list->pval )
+ list->cb( list );
+ }
+ list->held = 1;
+ } else {
+ list->held = 0;
+ }
+}
+
+GUI_LIST* gui_list( I32 x, I32 y, I32 w, I32 h, const char* title, LIST<GUI_LIST_ENTRY>* plist, I32* pval ) {
+ if( !gui_check_target() ) return 0;
+
+ GUI_LIST* list = new GUI_LIST;
+ list->x = x;
+ list->y = y;
+ list->w = w;
+ list->h = h;
+
+ list->xbound = w;
+ list->ybound = h + LIST_TITLE_OFFSET;
+
+ list->cb = 0;
+ list->pval = pval;
+ list->plist = plist;
+ list->draw_fn = gui_list_draw_fn;
+ list->input_fn = gui_list_input_fn;
+ strcpy( list->name, title );
+
+ list->parent = gui_get_view();
+
+ gui_get_view()->children.push( list );
+ return list;
+}
+
+GUI_LIST_ENTRY* gui_list_get_selected( GUI_LIST* list ) {
+ for( U32 i = 0; i < list->plist->size; ++i ) {
+ GUI_LIST_ENTRY* e = &list->plist->data[i];
+ if( e->val == *list->pval )
+ return e;
+ }
+
+ return 0;
+}
diff --git a/src/gui/textbox.cpp b/src/gui/textbox.cpp
new file mode 100644
index 0000000..688450b
--- /dev/null
+++ b/src/gui/textbox.cpp
@@ -0,0 +1,246 @@
+#include "base.h"
+#include "../util/input.h"
+
+#include <SDL_keycode.h>
+
+const U32 TEXTBOX_TITLE_OFFSET = 15;
+
+U8 is_printable_char( U8 key ) {
+ return key >= 32 && key < 127;
+}
+
+void gui_textbox_draw_newline_str( GUI_TEXTBOX* tb, I32 x, I32 y, CLR clr, I32* endposx, I32* endposy ) {
+ char linebuf[GUI_TEXTBOX_MAX];
+ char* start = tb->value;
+ I32 tw, th;
+
+ for( char* c = start; !!*c; ++c ) {
+ if( *c == '\n' ) {
+ U32 len = (U32)( c - start );
+ memcpy( linebuf, start, len );
+ linebuf[len] = 0;
+
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, linebuf );
+ gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, linebuf );
+ y += th + 1;
+
+ *endposx = x + tw;
+ *endposy = y + th;
+
+ if( *(c+1) == 0 )
+ break;
+
+ start = ++c;
+ }
+ else if( (*c+1) == 0 ) {
+ U32 len = (U32)( c - start );
+ memcpy( linebuf, start, len );
+ linebuf[len] = 0;
+
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, linebuf );
+ gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, linebuf );
+ y += th + 1;
+ *endposx = x + tw;
+ *endposy = y + th;
+
+ break;
+ }
+ }
+}
+
+void gui_textbox_draw_blinker( GUI_TEXTBOX* tb, I32 x, I32 y, CLR clr ) {
+ if( !tb->active )
+ return;
+ if( gui_time() - tb->blinktime > 1.0f ) {
+ tb->blink = !tb->blink;
+ tb->blinktime = gui_time();
+ }
+
+ if( tb->blink )
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, clr, "|" );
+}
+
+void gui_textbox_draw_fn( void* ptr ) {
+ GUI_TEXTBOX* tb = (GUI_TEXTBOX*)ptr;
+
+ I32 x = gui_relx( tb );
+ I32 y = gui_rely( tb );
+
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, tb->name );
+ y += TEXTBOX_TITLE_OFFSET;
+
+ CLR col = gui_is_fg_window( tb )? ui_clr.border : ui_clr.border_inactive;
+
+ gui_draw_frect( x, y, tb->w, tb->h, col );
+ gui_draw_frect( x+1, y+1, tb->w-2, tb->h-2, ui_clr.bg_sec );
+
+ CLR clr = tb->active? ui_clr.txt : ui_clr.txt_inactive;
+ I32 ypos = y + 3;
+ I32 xpos = x + 2;
+
+ I32 endposx, endposy;
+
+ if( tb->allow_newl ) {
+ gui_textbox_draw_newline_str( tb, xpos, ypos, clr, &endposx, &endposy );
+ } else {
+ gui_draw_str( xpos, ypos, ALIGN_L, FNT_JPN12, clr, tb->value );
+
+ I32 tw, th;
+ gui_draw_get_str_bounds( &tw, &th, FNT_JPN12, tb->value );
+ endposx = xpos + tw;
+ endposy = ypos;
+ }
+
+ gui_textbox_draw_blinker( tb, endposx, endposy, clr );
+}
+
+void gui_textbox_handle_mouse( GUI_TEXTBOX* tb ) {
+ I32 x = gui_relx( tb );
+ I32 y = gui_rely( tb ) + TEXTBOX_TITLE_OFFSET;
+ I32 w = tb->w;
+ I32 h = tb->h;
+
+ U8 m1 = gui_mbutton_down( 0 );
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h;
+
+ if( m1 ) {
+ if( inbounds )
+ tb->m1held = 1;
+ else
+ tb->active = 0;
+ } else {
+ if( inbounds && tb->m1held ) {
+ tb->active = 1;
+ tb->blink = 1;
+ tb->blinktime = gui_time();
+ }
+ tb->m1held = 0;
+ }
+}
+
+void gui_textbox_loop_keys( GUI_TEXTBOX* tb ) {
+ U32 len = strlen( tb->value );
+
+ for( U32 i = 0; i < 0xff; ++i ) {
+ U8 key = tb->keys[i];
+ U8 prev = tb->prevkeys[i];
+
+ if( !key && prev && i == tb->heldkey )
+ tb->heldkey = 0;
+ else if( key && !prev ) {
+ if( is_printable_char( i ) && len < tb->maxlen - 1 ) {
+ if( tb->keys[SDLK_LSHIFT & 0xff] || tb->keys[SDLK_RSHIFT & 0xff] )
+ tb->value[len] = (char)i + ('a' - 'A');
+ else
+ tb->value[len] = (char)i;
+ tb->value[++len] = 0;
+ }
+ else if( i == '\b' ) {
+ if( len > 0 ) {
+ tb->value[--len] = 0;
+ }
+ }
+ else if( i == '\r' ) {
+ if( tb->allow_newl ) {
+ tb->value[len] = '\n';
+ tb->value[++len] = 0;
+ }
+ else {
+ tb->active = 0;
+ }
+ } else {
+ if( i == '\x1b' )
+ tb->active = 0;
+ if( tb->cb )
+ tb->cb( tb );
+ continue;
+ }
+
+ tb->heldkey = i;
+ tb->heldtime = gui_time();
+ if( tb->cb )
+ tb->cb( tb );
+ }
+ }
+}
+
+void gui_textbox_handle_repeat( GUI_TEXTBOX* tb ) {
+ if( !tb->heldkey )
+ return;
+
+ F32 delta = gui_time() - tb->heldtime;
+ if( delta <= 0.75f )
+ return;
+
+ if( gui_time() - tb->repeattime > 0.05f ) {
+ U32 len = strlen( tb->value );
+ if( is_printable_char( tb->heldkey ) && len < (tb->maxlen - 1) ) {
+ if( tb->keys[SDLK_LSHIFT & 0xff] || tb->keys[SDLK_RSHIFT & 0xff] )
+ tb->value[len] = tb->heldkey + ('a' - 'A');
+ else
+ tb->value[len] = tb->heldkey;
+ tb->value[++len] = 0;
+ if( tb->cb )
+ tb->cb( tb );
+ }
+ else if( tb->heldkey == '\b' && len > 0 ) {
+ tb->value[--len] = 0;
+ if( tb->cb )
+ tb->cb( tb );
+ }
+
+ tb->repeattime = gui_time();
+ }
+}
+
+void gui_textbox_handle_keyboard( GUI_TEXTBOX* tb ) {
+ if( !tb->active )
+ return;
+
+ memcpy( tb->prevkeys, tb->keys, 0xff );
+ memcpy( tb->keys, input.keys, 0xff );
+
+ gui_textbox_loop_keys( tb );
+ gui_textbox_handle_repeat( tb );
+}
+
+void gui_textbox_input_fn( void* ptr ) {
+ GUI_TEXTBOX* tb = (GUI_TEXTBOX*)ptr;
+
+ gui_textbox_handle_mouse( tb );
+ gui_textbox_handle_keyboard( tb );
+}
+
+GUI_TEXTBOX* gui_textbox( I32 x, I32 y, I32 w, I32 h, const char* title, U32 maxlen, U8 allow_newl ) {
+ GUI_TEXTBOX* tb = new GUI_TEXTBOX;
+
+ tb->x = x;
+ tb->y = y;
+ tb->w = w;
+ tb->h = h;
+ tb->xbound = tb->w;
+ tb->ybound = tb->h + TEXTBOX_TITLE_OFFSET;
+ tb->parent = _gui.cur_view;
+ tb->draw_fn = gui_textbox_draw_fn;
+ tb->input_fn = gui_textbox_input_fn;
+ strcpy( tb->name, title );
+
+ tb->cb = 0;
+ tb->active = 0;
+ tb->m1held = 0;
+ tb->heldkey = 0;
+ tb->heldtime = 0;
+ tb->repeattime = 0;
+ tb->maxlen = maxlen;
+ tb->allow_newl = allow_newl;
+ memset( tb->value, 0, GUI_TEXTBOX_MAX );
+ memset( tb->keys, 0, 0xff );
+ memset( tb->prevkeys, 0, 0xff );
+
+ gui_get_view()->children.push( tb );
+
+ return tb;
+}
diff --git a/src/gui/vectorinput.cpp b/src/gui/vectorinput.cpp
new file mode 100644
index 0000000..9ebb0fa
--- /dev/null
+++ b/src/gui/vectorinput.cpp
@@ -0,0 +1,120 @@
+#include "base.h"
+
+const I32 VECTORINPUT_TITLE_OFFSET = 15;
+
+void gui_vectorinput_draw_fn( void* ptr ) {
+ GUI_VECTORINPUT* input = (GUI_VECTORINPUT*)ptr;
+
+ I32 x = gui_relx( input );
+ I32 y = gui_rely( input );
+
+ gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, input->name );
+
+ GUI_VIEW* inputview = input->inputview;
+ inputview->draw_fn( inputview );
+}
+
+void gui_vectorinput_input_fn( void* ptr ) {
+ GUI_VECTORINPUT* input = (GUI_VECTORINPUT*)ptr;
+
+ GUI_VIEW* inputview = input->inputview;
+ inputview->input_fn( inputview );
+}
+
+void gui_vectorinput_child_cb( void* ptr ) {
+ GUI_FLOATINPUT* child = (GUI_FLOATINPUT*)ptr;
+ // slider -> child view -> vectorinput
+ GUI_VECTORINPUT* parent = (GUI_VECTORINPUT*)child->parent->parent;
+ if( parent->cb )
+ parent->cb( parent );
+}
+
+void __gui_internal_vectorinput_init(
+ GUI_VECTORINPUT *input,
+ I32 x, I32 y, I32 w,
+ const char *title,
+ F32 *pval,
+ U32 valc,
+ F32 min,
+ F32 max,
+ F32 step,
+ const char *letters,
+ const char *printfmt
+) {
+ input->x = x;
+ input->y = y;
+ input->w = w;
+ input->h = 20;
+ strcpy( input->name, title );
+ input->xbound = input->w;
+ input->ybound = input->h + VECTORINPUT_TITLE_OFFSET;
+ input->draw_fn = gui_vectorinput_draw_fn;
+ input->input_fn = gui_vectorinput_input_fn;
+
+ GUI_VIEW* parent = gui_get_view();
+ parent->children.push( input );
+ input->parent = parent;
+
+ gui_set_view( input );
+
+ input->cb = 0;
+ input->pval = pval;
+ input->letters = letters;
+ input->inputview = gui_view( 0, VECTORINPUT_TITLE_OFFSET, w, input->h );
+ input->inputview->parent = input;
+ I32 wdiv = (input->w - 5 * (valc - 1)) / valc;
+ for( I32 i = 0; i < valc; ++i ) {
+ char letter[2] = { input->letters[i], 0 };
+ GUI_FLOATINPUT* slider = gui_floatinput(
+ (wdiv + 5) * i,
+ 0,
+ wdiv,
+ letter,
+ &pval[i],
+ min,
+ max,
+ step,
+ printfmt
+ );
+
+ slider->cb = gui_vectorinput_child_cb;
+ }
+
+ gui_set_view( parent );
+}
+
+GUI_VECTORINPUT* gui_vectorinput(
+ I32 x,
+ I32 y,
+ I32 w,
+ const char* title,
+ F32* pval,
+ U32 valc,
+ F32 min,
+ F32 max,
+ F32 step,
+ const char* letters,
+ const char* printfmt
+) {
+ if( !gui_check_target() ) return 0;
+ if( valc > 25 ) {
+ dlog( "gui_vectorinput() : have you lost your mind?" );
+ return 0;
+ }
+
+ GUI_VECTORINPUT* input = new GUI_VECTORINPUT;
+ __gui_internal_vectorinput_init(
+ input,
+ x, y, w,
+ title,
+ pval,
+ valc,
+ min,
+ max,
+ step,
+ letters,
+ printfmt
+ );
+
+ return input;
+}
diff --git a/src/gui/view.cpp b/src/gui/view.cpp
new file mode 100644
index 0000000..da470e7
--- /dev/null
+++ b/src/gui/view.cpp
@@ -0,0 +1,59 @@
+#include "base.h"
+
+void gui_view_draw_fn( void* ptr ) {
+ GUI_VIEW* view = (GUI_VIEW*)ptr;
+
+ I32 x = gui_relx( view );
+ I32 y = gui_rely( view );
+
+ gui_draw_push_clip( x, y, view->w, view->h );
+ view->children.each( fn( GUI_BASE** childptr ) {
+ GUI_BASE* child = *childptr;
+ if( !child->enabled ) return;
+
+ if( child->draw_fn ) child->draw_fn( child );
+ else dlog( "gui_view_draw_fn(): child %p no draw_fn\n", child );
+ } );
+ gui_draw_pop_clip();
+}
+
+void gui_view_input_fn( void* ptr ) {
+ GUI_VIEW* view = (GUI_VIEW*)ptr;
+
+ if( view->initheld ) {
+ U8 m1 = gui_mbutton_down( 0 );
+ if( m1 )
+ return;
+
+ view->initheld = 0;
+ }
+
+ gui_base_input_fn( view );
+}
+
+GUI_VIEW* gui_view( I32 x, I32 y, I32 w, I32 h ) {
+ GUI_VIEW* view = new GUI_VIEW;
+ view->x = x;
+ view->y = y;
+ view->xbound = view->w = w;
+ view->ybound = view->h = h;
+ strcpy( view->name, "BASE_VIEW" );
+
+ view->draw_fn = gui_view_draw_fn;
+ view->input_fn = gui_view_input_fn;
+
+ view->initheld = 1;
+
+ GUI_VIEW* curview = gui_get_view();
+ if( !curview ) {
+ view->parent = gui_get_window();
+ gui_get_window()->children.push( view );
+ }
+ else {
+ view->parent = curview;
+ curview->children.push( view );
+ }
+
+ gui_set_view( view );
+ return view;
+}
diff --git a/src/gui/window.cpp b/src/gui/window.cpp
new file mode 100644
index 0000000..0183d8c
--- /dev/null
+++ b/src/gui/window.cpp
@@ -0,0 +1,113 @@
+#include "base.h"
+
+#include "../render/gl.h"
+
+void gui_window_draw_fn( void* ptr ) {
+ GUI_WINDOW* wnd = (GUI_WINDOW*)ptr;
+
+ CLR clr = gui_is_fg_window( wnd )? ui_clr.border : ui_clr.border_inactive;
+ gui_draw_frect( wnd->x, wnd->y, wnd->w, wnd->h, clr );
+ gui_draw_frect( wnd->x + 1, wnd->y + 1, wnd->w - 2, wnd->h - 2, ui_clr.bg );
+
+ wnd->children.each( fn( GUI_BASE** ptr ) {
+ GUI_BASE* it = *ptr;
+ if( it->draw_fn ) it->draw_fn( it );
+ else dlog( "window_draw_fn() : child %p no draw_fn", it );
+ } );
+}
+
+GUI_WINDOW* gui_window( I32 x, I32 y, I32 w, I32 h ) {
+ GUI_WINDOW* wnd = new GUI_WINDOW;
+ wnd->x = x;
+ wnd->y = y;
+ wnd->xbound = wnd->w = w;
+ wnd->ybound = wnd->h = h;
+ wnd->draw_fn = gui_window_draw_fn;
+ strcpy( wnd->name, "BASE_WINDOW" );
+
+ _gui.windows.push( wnd );
+ gui_set_window( wnd );
+ gui_set_view( 0 );
+
+ gui_view( 1, 1, w - 2, h - 2 );
+ return wnd;
+}
+
+GUI_WINDOW* gui_window( I32 w, I32 h ) {
+ return gui_window( 0, 0, w, h );
+}
+
+void gui_title_draw_fn( void* ptr ) {
+ GUI_TITLE* t = (GUI_TITLE*)ptr;
+
+ I32 relx = gui_relx( t );
+ I32 rely = gui_rely( t );
+ gui_draw_str( relx + 5, rely + 3, 0, FNT_JPN12, ui_clr.txt, t->name );
+}
+
+void gui_title_input_fn( void* ptr ) {
+ GUI_TITLE* t = (GUI_TITLE*)ptr;
+ GUI_WINDOW* w = gui_get_parent_wnd( t );
+
+ if( w->locked )
+ return;
+
+ I32 x = gui_relx( t );
+ I32 y = gui_rely( t );
+
+ U8 m1 = gui_mbutton_down( 0 );
+ I32 mx, my;
+ gui_cursor_pos( &mx, &my );
+
+ if( !m1 ) {
+ t->held = 0;
+ return;
+ }
+
+ if( !t->held &&
+ mx >= x && mx <= x + t->w &&
+ my >= y && my <= y + t->h
+ ) {
+ I32 wx = w->x;
+ I32 wy = w->y;
+
+ I32 moffx = mx - wx;
+ I32 moffy = my - wy;
+
+ t->xoff = moffx;
+ t->yoff = moffy;
+
+ t->held = 1;
+ }
+ else if( t->held ) {
+ w->x = mx - t->xoff;
+ w->y = my - t->yoff;
+
+ if( w->x > _gui.gl2d->gl->canvas_size[0] - 5 )
+ w->x = _gui.gl2d->gl->canvas_size[0] - 5;
+ if( w->x + w->w < 5 )
+ w->x = 5 - w->w;
+ if( w->y > _gui.gl2d->gl->canvas_size[1] - 5 )
+ w->y = _gui.gl2d->gl->canvas_size[1] - 5;
+ if( w->y + w->h < 5 )
+ w->y = 5 - w->h;
+ }
+}
+
+GUI_TITLE* gui_title( const char* title ) {
+ if( !gui_check_target() ) return 0;
+
+ GUI_TITLE* t = new GUI_TITLE;
+ t->parent = gui_get_view();
+ t->x = 0;
+ t->y = 0;
+ t->h = 25;
+ t->w = t->parent->w;
+ t->draw_fn = gui_title_draw_fn;
+ t->input_fn = gui_title_input_fn;
+ strcpy( t->name, title );
+
+ gui_get_view()->children.push( t );
+
+ return t;
+}
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..28886fa
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,49 @@
+#include "game.h"
+#include "game/assets.h"
+
+// disable new_delete_type_mismatch asan warning
+#ifndef __has_feature
+ #define __has_feature(feature) 0
+#endif
+#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
+ #ifdef __cplusplus
+ extern "C"
+ #endif
+ const char *__asan_default_options() { return "new_delete_type_mismatch=0"; }
+#endif
+
+I32* canvas;
+GAME_DATA* game;
+GL_DATA* gl;
+
+void loop() {
+ static U8 init = 0;
+
+ if( !init || !gl ) {
+ gl = gl_create( canvas );
+ memcpy( gl->canvas_size, canvas, sizeof(I32) * 2 );
+
+ gl_gen_buffers( gl );
+ game = game_init( gl );
+
+ init = 1;
+ return;
+ }
+
+ game_main_loop( game );
+}
+
+int main() {
+ if( !canvas )
+ canvas = (I32*)malloc( sizeof(I32) * 2 );
+
+ canvas[0] = 800;
+ canvas[1] = 600;
+
+ while( true ) {
+ loop();
+ }
+
+ game_destroy( game );
+ return 0;
+}
diff --git a/src/render/gl.cpp b/src/render/gl.cpp
new file mode 100644
index 0000000..915722e
--- /dev/null
+++ b/src/render/gl.cpp
@@ -0,0 +1,394 @@
+#include "SDL_video.h"
+#include <unistd.h>
+#define STB_IMAGE_IMPLEMENTATION
+#include "../util/stb_image.h"
+
+#include "gl.h"
+#include "gl_2d_font.h"
+#include "../util.h"
+
+I32 SAMPLER_INDICES[255];
+
+GL_DATA* gl_inst;
+
+GL_DATA* gl_instance() {
+ return gl_inst;
+}
+
+GL_DATA* gl_create( I32* _canvas ) {
+ if( gl_inst ) {
+ dlog( "gl_create() : fatal: gl instance already exists\n" );
+ abort();
+ }
+
+ if( !font_mutex.align )
+ thread_mutex_init( &font_mutex );
+
+ if( !!SDL_Init( SDL_INIT_VIDEO | SDL_VIDEO_OPENGL ) ) {
+ dlog( "gl_create() could not init SDL: %s\n", SDL_GetError() );
+ return 0;
+ }
+
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES );
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2 );
+ SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
+ SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 );
+ SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 );
+ SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
+ SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
+ SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
+ SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
+
+ GL_DATA* gl = new GL_DATA;
+ gl->window = SDL_CreateWindow(
+ "game",
+ 0,
+ 0,
+ _canvas[0],
+ _canvas[1],
+ SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
+ );
+
+ if( !gl->window ) {
+ dlog( "gl_init() could not create window: %s\n", SDL_GetError() );
+ return 0;
+ }
+
+ U32 renderer_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
+ gl->renderer = SDL_CreateRenderer( gl->window, -1, renderer_flags );
+ if( !gl->renderer ) {
+ dlog( "gl_init() could not create renderer: %s\n", SDL_GetError() );
+ return 0;
+ }
+
+ gl->ctx = SDL_GL_CreateContext( gl->window );
+ if( !gl->ctx ) {
+ dlog( "gl_init() could not create context: %s\n", SDL_GetError() );
+ return 0;
+ }
+
+ glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &gl->shader_texture_limit );
+ if( gl->shader_texture_limit > 255 )
+ gl->shader_texture_limit = 255;
+ for( U32 i = 0; i < 255; ++i )
+ SAMPLER_INDICES[i] = i;
+
+ gl->programs.clear();
+ memcpy( gl->canvas_size, _canvas, sizeof(I32) * 2 );
+ gl->clip_start = { 0, 0 };
+ gl->clip_dim = { (F32)gl->canvas_size[0], (F32)gl->canvas_size[1] };
+
+ gl_inst = gl;
+ return gl;
+}
+
+void gl_gen_buffers( GL_DATA* gl ) {
+ glGenBuffers( 1, &gl->vbuffer );
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, 8192, 0, GL_STATIC_DRAW );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+}
+
+void gl_destroy( GL_DATA *gl ) {
+ gl->programs.each( fn( GL_SHADER_PROGRAM** it ) { gl_program_destroy( gl, *it ); } );
+ gl->fonts.each( fn( GL_FONT** it ) { gl_font_destroy( gl, *it ); } );
+ gl->textures.each( fn( GL_TEX2D** it ) { gl_texture_destroy( gl, *it ); } );
+
+ if( gl->ctx )
+ SDL_GL_DeleteContext( gl->ctx );
+ if( gl->window )
+ SDL_DestroyWindow( gl->window );
+
+ free( gl );
+}
+
+void gl_update_window( GL_DATA* gl, I32* size ) {
+ if( !gl->window )
+ return;
+ SDL_SetWindowSize( gl->window, size[0], size[1] );
+ SDL_SetWindowPosition( gl->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED );
+
+ gl->canvas_size[0] = size[0];
+ gl->canvas_size[1] = size[1];
+
+ gl->programs.each( fn( GL_SHADER_PROGRAM** it ) {
+ F32 screen_ratio[] = {
+ 2.f / (F32)gl->canvas_size[0],
+ 2.f / (F32)gl->canvas_size[1],
+ 1.f,
+ 1.f
+ };
+
+ glUseProgram( (*it)->id );
+ I32 ratio_location = glGetUniformLocation( (*it)->id, "g_screenratio" );
+ glUniform4fv( ratio_location, 1, &screen_ratio[0] );
+ } );
+
+ glViewport( 0, 0, gl->canvas_size[0], gl->canvas_size[1] );
+ glUseProgram( 0 );
+}
+
+STAT gl_shader_compile( GL_DATA* gl, GL_SHADER* shader ) {
+ static char* log_buf = 0;
+ if( !log_buf )
+ log_buf = (char*)malloc( 8192 );
+
+ I32 res;
+ shader->id = glCreateShader( (GLenum)shader->type );
+ glShaderSource( shader->id, 1, &shader->code, 0 );
+ glCompileShader( shader->id );
+ glGetShaderiv( shader->id, GL_COMPILE_STATUS, &res );
+
+ if( !res ) {
+ glGetShaderInfoLog( shader->id, 8192, 0, log_buf );
+ dlog( "gl_shader_compile() : error compiling shader %s. log: \n%s\n%s", shader->name, log_buf, shader->code );
+
+ glDeleteShader( shader->id );
+ return STAT_ERR;
+ }
+
+ shader->compiled = 1;
+ return STAT_OK;
+}
+
+void gl_shader_destroy( GL_DATA* gl, GL_SHADER* shader ) {
+ if( shader->code )
+ free( (void*)shader->code );
+}
+
+GL_SHADER_PROGRAM* gl_program_create( GL_DATA* gl, const char* name, U32 size ) {
+ GL_SHADER_PROGRAM* program = (GL_SHADER_PROGRAM*)malloc( size );
+ char shader_string[256];
+ char* shader_code;
+
+ sprintf( shader_string, "../assets/shaders/%s.vsh", name );
+ shader_code = (char*)file_read( shader_string );
+ if( !shader_code ) {
+ dlog( "gl_program_create() : could not read shader file %s\n", shader_string );
+ return 0;
+ }
+
+ program->vsh.name = name;
+ program->vsh.type = GL_VERTEX_SHADER;
+ program->vsh.code = shader_code;
+
+ sprintf( shader_string, "../assets/shaders/%s.fsh", name );
+ shader_code = (char*)file_read( shader_string );
+ if( !shader_code ) {
+ dlog( "gl_program_create() : could not read shader file %s\n", shader_string );
+ return 0;
+ }
+
+ program->fsh.name = name;
+ program->fsh.type = GL_FRAGMENT_SHADER;
+ program->fsh.code = shader_code;
+
+ program->name = name;
+ program->gl = gl;
+
+ gl->programs.push( program );
+ return program;
+}
+
+STAT gl_program_compile( GL_DATA* gl, GL_SHADER_PROGRAM* program ) {
+ static char* log_buf = 0;
+ if( !log_buf )
+ log_buf = (char*)malloc( 8192 );
+
+ program->id = glCreateProgram();
+ if( !OK( gl_shader_compile( gl, &program->fsh ) )
+ || !OK( gl_shader_compile( gl, &program->vsh ) ) )
+ return STAT_ERR;
+
+ glAttachShader( program->id, program->fsh.id );
+ glAttachShader( program->id, program->vsh.id );
+
+ I32 status;
+ glLinkProgram( program->id );
+ glGetProgramiv( program->id, GL_LINK_STATUS, &status );
+ if( !status ) {
+ glGetProgramInfoLog( program->id, 8192, 0, log_buf );
+ dlog( "gl_program_compile() : error compiling program %s. log: \n%s\n",
+ program->name,
+ log_buf
+ );
+
+ return STAT_ERR;
+ }
+
+ glGenBuffers( 1, &program->vbuffer );
+ return STAT_OK;
+}
+
+void gl_program_destroy( GL_DATA* gl, GL_SHADER_PROGRAM* program ) {
+ if( program->fsh.compiled )
+ gl_shader_destroy( gl, &program->fsh );
+ if( program->vsh.compiled )
+ gl_shader_destroy( gl, &program->vsh );
+
+ I32 idx = gl->programs.idx_of( program );
+ if( idx != -1 )
+ gl->programs.erase( idx );
+}
+
+GL_TEX2D* gl_texture_from_file( GL_DATA* gl, const char* name ) {
+ char filename[256];
+ sprintf( filename, "../assets/%s", name );
+
+ I32 w, h, n;
+ U8* data = stbi_load( filename, &w, &h, &n, STBI_rgb_alpha );
+ if( !data ) {
+ dlog( "gl_texture_create() : could not load image %s\n", filename );
+ return 0;
+ }
+
+ GL_TEX2D* tex = gl_texture_create( gl, filename );
+ glBindTexture( GL_TEXTURE_2D, tex->id );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
+ glGenerateMipmap( GL_TEXTURE_2D );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ tex->width = w;
+ tex->height = h;
+
+ stbi_image_free( data );
+
+ return tex;
+}
+
+GL_TEX2D* gl_texture_from_bitmap( GL_DATA* gl, const char* name, U8* bitmap, U32 width, U32 height ) {
+ GL_TEX2D* tex = gl_texture_create( gl, name );
+ glBindTexture( GL_TEXTURE_2D, tex->id );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+
+ tex->width = width;
+ tex->height = height;
+
+ glTexImage2D(
+ GL_TEXTURE_2D,
+ 0, GL_RGBA,
+ (I32)width, (I32)height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, bitmap
+ );
+
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ return tex;
+}
+
+
+GL_TEX2D* gl_texture_create( GL_DATA* gl, const char* name ) {
+ GL_TEX2D* tex = (GL_TEX2D*)malloc( sizeof(GL_TEX2D) );
+ strcpy( tex->name, name );
+ glGenTextures( 1, &tex->id );
+
+ gl->textures.push( tex );
+ return tex;
+}
+
+void gl_texture_destroy( GL_DATA* gl, GL_TEX2D* tex ) {
+ glDeleteTextures( 1, &tex->id );
+ I32 idx = gl->textures.idx_of( tex );
+ if( idx != -1 )
+ gl->textures.erase( idx );
+
+ free( tex );
+}
+
+STAT gl_beginframe( GL_DATA* gl ) {
+ SDL_SetRenderTarget( gl->renderer, 0 );
+ SDL_SetRenderDrawColor( gl->renderer, 0, 0, 0, 255 );
+ SDL_RenderClear( gl->renderer );
+
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+ gl->last_tick = u_tick();
+ return STAT_OK;
+}
+
+STAT gl_endframe( GL_DATA* gl ) {
+ SDL_GL_SwapWindow( gl->window );
+
+ // 1000 fps max be real
+ while( true ) {
+ U64 diff = u_tick() - gl->last_tick;
+ if( diff < 10 )
+ usleep( 10 );
+ else break;
+ }
+
+ gl->frametime = (F32)(u_tick() - gl->last_tick) / 10000.0f;
+ gl->fps = 1.0f / gl->frametime;
+
+ input_frame_end();
+
+ SDL_Event e;
+ while( SDL_PollEvent( &e ) ) {
+ input_on_event( &e );
+
+ switch( e.type ) {
+ case SDL_QUIT:
+ return STAT_BREAK;
+ case SDL_KEYDOWN:
+ return e.key.keysym.sym == SDLK_ESCAPE ? STAT_BREAK : STAT_OK;
+ default: break;
+ }
+ }
+
+ return STAT_OK;
+}
+
+void gl_set_clip( GL_DATA* gl, VEC2 start, VEC2 dim ) {
+ gl->clip_start = start;
+ gl->clip_dim = dim;
+
+ glEnable( GL_SCISSOR_TEST );
+ glScissor( start.x, gl->canvas_size[1] - (I32)(dim.y + start.y), dim.x, dim.y );
+}
+
+void gl_get_clip( GL_DATA* gl, VEC2* start, VEC2* dim ) {
+ *start = gl->clip_start;
+ *dim = gl->clip_dim;
+}
+
+void gl_reset_clip( GL_DATA* gl ) {
+ glDisable( GL_SCISSOR_TEST );
+ gl->clip_start = {};
+ gl->clip_dim = { (F32)gl->canvas_size[0], (F32)gl->canvas_size[1] };
+}
+
+void gl_set_viewport( GL_DATA* gl, VEC2 start, VEC2 dim ) {
+ I32 vpy = (I32)gl->canvas_size[1] - start.y - dim.y;
+ glViewport( (I32)start.x, vpy, (I32)dim.x, (I32)dim.y );
+
+ gl->programs.each( fn( GL_SHADER_PROGRAM** it ) {
+ F32 screen_ratio[] = {
+ 2.f / (F32)dim.x,
+ 2.f / (F32)dim.y,
+ 1.f,
+ 1.f
+ };
+
+ glUseProgram( (*it)->id );
+ I32 ratio_location = glGetUniformLocation( (*it)->id, "g_screenratio" );
+ glUniform4fv( ratio_location, 1, &screen_ratio[0] );
+ } );
+
+ gl->viewport_start = start;
+ gl->viewport_dim = dim;
+}
+
+void gl_get_viewport( GL_DATA* gl, VEC2* start, VEC2* dim ) {
+ *start = gl->viewport_start;
+ *dim = gl->viewport_dim;
+}
diff --git a/src/render/gl.h b/src/render/gl.h
new file mode 100644
index 0000000..cf2b5dd
--- /dev/null
+++ b/src/render/gl.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#include <SDL.h>
+#include <GLES2/gl2.h>
+
+#include "../util.h"
+#include "../util/matrix.h"
+
+typedef struct GL_FONT *PGL_FONT;
+typedef struct GL_DATA *PGL_DATA;
+
+struct VERTEX {
+ VEC2 pos;
+ VEC2 uv;
+ CLR clr;
+ U8 sampler;
+};
+
+struct GL_SHADER_DEF {
+ const char* name;
+ const char* code;
+ I32 type;
+ U32 id;
+ U8 compiled;
+};
+
+struct GL_SHADER_PROGRAM {
+ const char* name;
+ GL_SHADER_DEF fsh;
+ GL_SHADER_DEF vsh;
+ U32 id;
+ U32 vbuffer;
+
+ GL_DATA* gl;
+};
+
+struct GL_TEX2D {
+ GLuint id;
+ char name[256];
+ U32 width;
+ U32 height;
+ U32 channels;
+ U8* data;
+};
+
+typedef struct GL_DATA {
+ SDL_Window* window;
+ SDL_GLContext ctx;
+ SDL_Renderer* renderer;
+
+ I32 canvas_size[2];
+
+ LIST<GL_SHADER_PROGRAM*> programs;
+ LIST<GL_TEX2D*> textures;
+ LIST<GL_FONT*> fonts;
+
+ I32 shader_texture_limit;
+ U64 last_tick;
+ F32 frametime;
+ F32 fps;
+
+ GLuint vbuffer;
+
+ VEC2 clip_start;
+ VEC2 clip_dim;
+
+ VEC2 viewport_start;
+ VEC2 viewport_dim;
+
+ MAT4* proj_matrix;
+} *PGL_DATA;
+
+GL_DATA* gl_instance();
+GL_DATA* gl_create( I32* _canvas );
+void gl_destroy( GL_DATA* gl );
+void gl_gen_buffers( GL_DATA* gl );
+STAT gl_beginframe( GL_DATA* gl );
+STAT gl_endframe( GL_DATA* gl );
+STAT gl_shader_compile( GL_DATA* gl, GL_SHADER_DEF* shader );
+void gl_shader_destroy( GL_DATA* gl, GL_SHADER_DEF* shader );
+GL_SHADER_PROGRAM* gl_program_create( GL_DATA* gl, const char* name, U32 size = sizeof(GL_SHADER_PROGRAM) );
+STAT gl_program_compile( GL_DATA* gl, GL_SHADER_PROGRAM* program );
+void gl_program_destroy( GL_DATA* gl, GL_SHADER_PROGRAM* program );
+GL_TEX2D* gl_texture_create( GL_DATA* gl, const char* name );
+GL_TEX2D* gl_texture_from_file( GL_DATA* gl, const char* name );
+GL_TEX2D* gl_texture_from_bitmap( GL_DATA* gl, const char* name, U8* bitmap, U32 width, U32 height );
+void gl_texture_destroy( GL_DATA* gl, GL_TEX2D* tex );
+void gl_update_window( GL_DATA* gl, I32* size );
+void gl_set_clip( GL_DATA* gl, VEC2 start, VEC2 dim );
+void gl_get_clip( GL_DATA* gl, VEC2* start, VEC2* dim );
+void gl_set_viewport( GL_DATA* gl, VEC2 start, VEC2 dim );
+void gl_get_viewport( GL_DATA* gl, VEC2* start, VEC2* dim );
+void gl_reset_clip( GL_DATA* gl );
+
+// special sampler id for no texture
+const U8 SAMPLER_ID_NONE = 255;
+extern I32 SAMPLER_INDICES[255];
+
+template <typename VERTEX>
+LIST<VERTEX> triangle_fan_to_list( VERTEX* vertices, U32 vert_count ) {
+ LIST<VERTEX> ret{};
+ if( vert_count < 3 ) {
+ return ret;
+ }
+
+ ret.reserve( (vert_count - 2) * 3 );
+ VERTEX* start = &vertices[0];
+ VERTEX* last = &vertices[1];
+ for( U32 i = 2; i < vert_count; ++i ) {
+ ret.push( *start );
+ ret.push( *last );
+ ret.push( vertices[i] );
+ last = &vertices[i];
+ }
+
+ return ret;
+}
diff --git a/src/render/gl_2d.cpp b/src/render/gl_2d.cpp
new file mode 100644
index 0000000..44e7e00
--- /dev/null
+++ b/src/render/gl_2d.cpp
@@ -0,0 +1,314 @@
+#include "gl_2d.h"
+#include "../util.h"
+
+GL_PROGRAM* gl_2d_init( GL_DATA* gl, VEC2 screensize, const char* shadername ) {
+ GL_PROGRAM* program = gl_program_create( gl, shadername );
+ if( !OK( gl_program_compile( gl, program ) ) )
+ dlog( "gl_2d_init() : error compiling shader %s\n", shadername );
+
+ F32 screen_ratio[] = {
+ 2.f / screensize.x,
+ 2.f / screensize.y,
+ 1.f,
+ 1.f
+ };
+
+ glUseProgram( program->id );
+ glEnable( GL_BLEND );
+ glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+
+ I32 ratio_location = glGetUniformLocation( program->id, "g_screenratio" );
+ glUniform4fv( ratio_location, 1, &screen_ratio[0] );
+
+ I32 samplers_location = glGetUniformLocation( program->id, "g_samplers" );
+ glUniform1iv( samplers_location, 255, SAMPLER_INDICES );
+
+ return program;
+}
+
+void gl_2d_line( GL_PROGRAM* gl2d, VEC2 start, VEC2 end, CLR col ) {
+ static const U16 order[] = { 0, 1 };
+
+ glUseProgram( gl2d->id );
+
+ VERTEX vertices[] = {
+ { .pos = { start.x, start.y }, .clr = col },
+ { .pos = { end.x, end.y }, .clr = col },
+ };
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_DYNAMIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawElements( GL_LINES, 2, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_2d_rect( GL_PROGRAM* gl2d, VEC2 origin, VEC2 dim, CLR col ) {
+ static const U16 order[] = { 0, 1, 2, 3, 4 };
+
+ glUseProgram( gl2d->id );
+
+ VERTEX vertices[] = {
+ { .pos = { origin.x , origin.y }, .clr = col },
+ { .pos = { origin.x + dim.x, origin.y }, .clr = col },
+ { .pos = { origin.x + dim.x, origin.y + dim.y }, .clr = col },
+ { .pos = { origin.x , origin.y + dim.y }, .clr = col },
+ { .pos = { origin.x , origin.y }, .clr = col },
+ };
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_DYNAMIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawElements( GL_LINE_STRIP, 5, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_2d_textured_frect( GL_PROGRAM* gl2d, VEC2 origin, VEC2 dim, GL_TEX2D* texture, CLR col, VEC2* uv, F32 rotation ) {
+ static const U16 order[] = { 0, 1, 2, 3 };
+
+ glUseProgram( gl2d->id );
+ VERTEX vertices[] = {
+ { { origin.x, origin.y }, uv? uv[0] : VEC2{ 0.f, 0.f }, col, 0 },
+ { { origin.x + dim.x, origin.y }, uv? uv[1] : VEC2{ 1.f, 0.f }, col, 0 },
+ { { origin.x, origin.y + dim.y }, uv? uv[2] : VEC2{ 0.f, 1.f }, col, 0 },
+ { { origin.x + dim.x, origin.y + dim.y }, uv? uv[3] : VEC2{ 1.f, 1.f }, col, 0 }
+ };
+
+ rotation = remainderf( rotation, 360.f );
+ if( rotation != 0.f ) {
+ F32 rad2dg = rotation * (M_PI / 180.f);
+
+ // rotate texture coordinates
+ for( U32 i = 0; i < 4; ++i ) {
+ F32 x = vertices[i].uv.x - 0.5f;
+ F32 y = vertices[i].uv.y - 0.5f;
+ vertices[i].uv.x = x * cosf( rad2dg ) - y * sinf( rad2dg ) + 0.5f;
+ vertices[i].uv.y = x * sinf( rad2dg ) + y * cosf( rad2dg ) + 0.5f;
+ if( vertices[i].uv.x < 0.f ) vertices[i].uv.x = 0.f;
+ if( vertices[i].uv.x > 1.f ) vertices[i].uv.x = 1.f;
+ }
+ }
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_DYNAMIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+ I32 sampler = glGetAttribLocation( gl2d->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX), &( (VERTEX*)nullptr)->sampler );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, texture->id );
+
+ glUniform1f( glGetUniformLocation( gl2d->id, "in_time" ), u_time() );
+
+ glDrawElements( GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, order );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+}
+
+void gl_2d_frect( GL_PROGRAM* gl2d, VEC2 origin, VEC2 dim, CLR col ) {
+ static const U16 order[] = { 0, 1, 2, 3 };
+
+ glUseProgram( gl2d->id );
+ VERTEX vertices[] = {
+ { .pos = { origin.x, origin.y }, .uv = { 0.f, 0.f }, .clr = col },
+ { .pos = { origin.x + dim.x, origin.y }, .uv = { 1.f, 0.f }, .clr = col },
+ { .pos = { origin.x, origin.y + dim.y }, .uv = { 0.f, 1.f }, .clr = col },
+ { .pos = { origin.x + dim.x, origin.y + dim.y }, .uv = { 1.f, 1.f }, .clr = col }
+ };
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_DYNAMIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawElements( GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_2d_circle( GL_PROGRAM* gl2d, VEC2 origin, F32 radius, CLR col, U32 res ) {
+ static U16* order = 0;
+ const F32 step = 360.f / (F32)res;
+
+ glUseProgram( gl2d->id );
+
+ if( !order ) {
+ order = (U16*)malloc( sizeof( U16 ) * (res + 1) );
+ for( U32 i = 0; i < res + 1; ++i )
+ order[i] = i;
+ }
+
+ VERTEX* vertices = (VERTEX*)malloc( sizeof( VERTEX ) * (res + 1) );
+ for( U32 i = 0; i < res + 1; ++i ) {
+ VEC2 offset = m_radial_offset( step * ( i == res? 0 : i ), radius );
+
+ vertices[i] = (VERTEX){
+ .pos = {
+ origin.x + offset.x,
+ origin.y + offset.y,
+ },
+ .uv = {},
+ .clr = col
+ };
+ }
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * (res + 1), &vertices[0], GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glDrawElements( GL_LINE_STRIP, res + 1, GL_UNSIGNED_SHORT, order );
+ free( vertices );
+}
+
+void gl_2d_fcircle( GL_PROGRAM* gl2d, VEC2 origin, F32 radius, CLR col, U32 res ) {
+ static U16* order = 0;
+ const F32 step = 360.f / (F32)res;
+
+ glUseProgram( gl2d->id );
+
+ if( !order ) {
+ order = (U16*)malloc( sizeof( U16 ) * (res * 2) );
+ for( U32 i = 0; i < res * 2; ++i )
+ order[i] = i;
+ }
+
+ VERTEX* vertices = (VERTEX*)malloc( sizeof(VERTEX) * (res * 2) );
+ for( U32 i = 0; i < res * 2; i += 2 ) {
+ VEC2 offset = m_radial_offset( step * i, radius );
+ vertices[i].pos = (VEC2){
+ origin.x,
+ origin.y,
+ };
+
+ vertices[i + 1].pos = (VEC2){
+ origin.x + offset.x,
+ origin.y + offset.y,
+ };
+
+ vertices[i].uv = (VEC2){ 0.5f, 0.5f };
+ vertices[i + 1].uv = (VEC2){
+ 0.5f + ( offset.x * 0.5f ) / radius,
+ 0.5f + ( offset.y * 0.5f ) / radius
+ };
+
+ vertices[i].clr = col;
+ vertices[i + 1].clr = col;
+ };
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * res * 2, &vertices[0], GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glDrawElements( GL_TRIANGLE_STRIP, res + 2, GL_UNSIGNED_SHORT, order );
+ free( vertices );
+}
+
+void gl_polygon( GL_PROGRAM* gl2d, VERTEX* vertices, U32 vertices_count ) {
+ glUseProgram( gl2d->id );
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * vertices_count, vertices, GL_STATIC_DRAW );
+
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ U16* order = (U16*)alloca( sizeof(U16) * vertices_count );
+ for( U32 i = 0; i < vertices_count; i++ )
+ order[i] = i;
+
+ glDrawElements( GL_TRIANGLE_FAN, vertices_count, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_textured_polygon(
+ GL_PROGRAM *gl2d,
+ VERTEX *vertices,
+ U32 vertices_count,
+ GL_TEX2D *tex
+) {
+ glUseProgram( gl2d->id );
+
+ for( U32 i = 0; i < vertices_count; ++i )
+ vertices[i].sampler = 0;
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl2d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * vertices_count, vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( gl2d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), 0 );
+ I32 color = glGetAttribLocation( gl2d->id, "in_col" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+ I32 texcoord = glGetAttribLocation( gl2d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+ I32 sampler = glGetAttribLocation( gl2d->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX), &( (VERTEX*)nullptr)->sampler );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, tex->id );
+
+ glUniform1f( glGetUniformLocation( gl2d->id, "in_time" ), u_time() );
+
+ U16* order = (U16*)alloca( sizeof(U16) * vertices_count );
+ for( U32 i = 0; i < vertices_count; i++ )
+ order[i] = i;
+
+ glDrawElements( GL_TRIANGLE_FAN, vertices_count, GL_UNSIGNED_SHORT, order );
+}
diff --git a/src/render/gl_2d.h b/src/render/gl_2d.h
new file mode 100644
index 0000000..9830516
--- /dev/null
+++ b/src/render/gl_2d.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "gl.h"
+
+extern GL_SHADER_PROGRAM* gl_2d_init( GL_DATA* gl, VEC2 screensize, const char* shadername );
+
+void gl_polygon( GL_SHADER_PROGRAM* gl2d, VERTEX* vertices, U32 vertices_count );
+void gl_textured_polygon(
+ GL_SHADER_PROGRAM* gl2d,
+ VERTEX* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+);
+
+extern void gl_2d_line( GL_SHADER_PROGRAM* gl2d, VEC2 start, VEC2 end, CLR col );
+extern void gl_2d_rect( GL_SHADER_PROGRAM* gl2d, VEC2 origin, VEC2 dimensions, CLR col );
+extern void gl_2d_frect( GL_SHADER_PROGRAM* gl2d, VEC2 origin, VEC2 dimensions, CLR col );
+extern void gl_2d_circle( GL_SHADER_PROGRAM* gl2d, VEC2 origin, F32 radius, CLR col, U32 res = 48 );
+extern void gl_2d_fcircle( GL_SHADER_PROGRAM* gl2d, VEC2 origin, F32 radius, CLR col, U32 res = 48 );
+extern void gl_2d_textured_frect(
+ GL_SHADER_PROGRAM* gl2d,
+ VEC2 origin,
+ VEC2 dim,
+ GL_TEX2D* texture,
+ CLR col = { 1.f, 1.f, 1.f, 1.f },
+ VEC2* uv = 0,
+ F32 rotation = 0.F
+);
diff --git a/src/render/gl_2d_font.cpp b/src/render/gl_2d_font.cpp
new file mode 100644
index 0000000..1dc498b
--- /dev/null
+++ b/src/render/gl_2d_font.cpp
@@ -0,0 +1,366 @@
+#include "gl_2d_font.h"
+#include "gl.h"
+
+FT_Library libft = NULL;
+
+THREAD_MUTEX font_mutex;
+
+void freetype_init() {
+ U32 err;
+
+ if( !libft ) {
+ err = FT_Init_FreeType( &libft );
+
+ if( err )
+ dlog( "gl_font_create() : FT_Init_FreeType() failed\n" );
+ }
+}
+
+void gl_font_create_bitmap( GL_FONT* font ) {
+ U32 char_width = (font->face->size->metrics.max_advance >> 6) + 1;
+ U32 char_height = (font->face->size->metrics.height >> 6) + 1;
+ U32 max_width = char_width * 16;
+ U32 max_height = char_height * 16;
+
+ if( !max_width || !max_height ) {
+ dlog( "gl_font_create() : invalid font size\n" );
+ return;
+ }
+
+ font->bitmap = (U32*)malloc( sizeof(U32) * max_width * max_height );
+ memset( font->bitmap, 0, sizeof(U32) * max_width * max_height );
+ font->bitmap_width = max_width;
+ font->bitmap_height = max_height;
+ font->char_width = char_width;
+ font->char_height = char_height;
+
+ U32 cur_x = 0, cur_y = 1;
+ for( I32 c = 0; c < GL_FONT_MAX_GLYPHS; ++c ) {
+ U32 err = FT_Load_Char( font->face, c, FT_LOAD_RENDER );
+ if( !err ) {
+ FT_Bitmap* bitmap = &font->face->glyph->bitmap;
+ for( U32 y = 0; y < bitmap->rows; ++y ) {
+ for( U32 x = 0; x < bitmap->width; ++x ) {
+ U32 dest_x = x + cur_x;
+ U32 dest_y = y + cur_y;
+
+ U8 pixel = bitmap->buffer[x + bitmap->width * y];
+ U32 out_pixel = 0x00FFFFFF;
+ out_pixel |= ( pixel << 24 );
+ font->bitmap[dest_x + max_width * dest_y] = out_pixel;
+ }
+ }
+
+ font->glyphs[c].offset_x = (F32)cur_x;
+ font->glyphs[c].offset_y = (F32)cur_y;
+ font->glyphs[c].width = bitmap->width;
+ font->glyphs[c].height = bitmap->rows;
+ font->glyphs[c].advance_x = (F32)(font->face->glyph->metrics.horiAdvance >> 6);
+ font->glyphs[c].advance_y = (F32)(char_height - (font->face->glyph->metrics.horiBearingY >> 6));
+ font->glyphs[c].bearing = (F32)(font->face->glyph->metrics.horiBearingX >> 6);
+ }
+ else {
+ dlog( "gl_font_create() : FT_Load_Char() failed (%s: %c) (%x)\n", font->name, c, err );
+ }
+
+ if( cur_x + char_width >= max_width ) {
+ cur_x = 0;
+ cur_y += char_height;
+ } else {
+ cur_x += char_width;
+ }
+ }
+}
+
+void gl_font_create_atlas( GL_DATA* gl, GL_FONT* font ) {
+ if( !font->bitmap ) {
+ dlog( "gl_font_create_atlas() : no bitmap\n" );
+ return;
+ }
+
+ char texture_name[256];
+ sprintf( texture_name, "%s_%d_atlas", font->name, font->size );
+
+ font->atlas = gl_texture_from_bitmap( gl,
+ texture_name,
+ (U8*)font->bitmap,
+ font->bitmap_width,
+ font->bitmap_height
+ );
+
+ free( font->bitmap );
+}
+
+GL_FONT* gl_font_create( GL_DATA* gl, const char* path, I32 size ) {
+ freetype_init();
+
+ if( size <= 2 ) {
+ dlog( "gl_font_create() : size too small.\n" );
+ return 0;
+ }
+
+ char full_path[256];
+ GL_FONT* font = (GL_FONT*)malloc( sizeof( GL_FONT ) );
+ font->size = size;
+ font->name = path;
+
+ sprintf( full_path, "../assets/fonts/%s", path );
+ U32 err = FT_New_Face( libft, full_path, 0, &font->face );
+
+ if( err ) {
+ dlog( "gl_font_create() : FT_New_Face() failed (%x)\n", err );
+ free( font );
+ return 0;
+ }
+
+ err = FT_Set_Pixel_Sizes( font->face, 0, size );
+ if( err ) {
+ dlog( "gl_font_create() : FT_Set_Char_Size() failed\n" );
+ free( font );
+ return 0;
+ }
+
+ gl_font_create_bitmap( font );
+ FT_Done_Face( font->face );
+
+ gl_font_create_atlas( gl, font );
+ gl->fonts.push( font );
+ return font;
+}
+
+void gl_font_destroy( GL_DATA* gl, GL_FONT* font ) {
+ gl_texture_destroy( gl, font->atlas );
+ I32 idx = gl->fonts.idx_of( font );
+ if( idx != -1 )
+ gl->fonts.erase( idx );
+
+ free( font );
+}
+
+
+void gl_font_calc_vertices_uvs(
+ GL_FONT* font,
+ VEC2 origin,
+ const char* text,
+ F32 _scale,
+ VERTEX* vertices,
+ U16* indices,
+ VEC2* coords,
+ CLR clr
+) {
+ U32 len = (U32)strlen( text );
+
+ F32 cur_x = origin.x;
+ F32 cur_y = origin.y;
+
+ for( U32 i = 0; i < len; ++i ) {
+ VERTEX* v = &vertices[i * 6];
+ U16* idx = &indices[i * 6];
+
+ if( text[i] == '\n' ) {
+ v[0] = v[1] = v[2] = v[3] = v[4] = v[5] = { { cur_x, cur_y }, {} };
+ idx[0] = idx[1] = idx[2] = idx[3] = idx[4] = idx[5] = i * 6;
+
+ cur_x = origin.x;
+ cur_y += (F32)font->char_height * _scale;
+ continue;
+ }
+
+ U32 c = (U8)text[i];
+ FONT_GLYPH* glyph = &font->glyphs[c];
+ F32 final_y = cur_y + glyph->advance_y * _scale;
+ F32 final_x = cur_x + (F32)glyph->bearing * _scale;
+
+ v[0].pos = { final_x, final_y };
+ v[1].pos = { final_x + glyph->width * _scale, final_y };
+ v[2].pos = { final_x, final_y + glyph->height * _scale };
+ v[3].pos = { final_x + glyph->width * _scale, final_y + glyph->height * _scale };
+ v[4].pos = { final_x, final_y + glyph->height * _scale };
+ v[5].pos = { final_x + glyph->width * _scale, final_y };
+
+ idx[0] = i * 6;
+ idx[1] = i * 6 + 1;
+ idx[2] = i * 6 + 2;
+ idx[3] = i * 6 + 3;
+ idx[4] = i * 6 + 4;
+ idx[5] = i * 6 + 5;
+
+ v[0].uv = { glyph->offset_x / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+ v[1].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+ v[2].uv = { glyph->offset_x / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[3].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[4].uv = { glyph->offset_x / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[5].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+
+ v[0].clr = v[1].clr = v[2].clr = v[3].clr = v[4].clr = v[5].clr = clr;
+ v[0].sampler = v[1].sampler = v[2].sampler = v[3].sampler = v[4].sampler = v[5].sampler = 0;
+
+ cur_x += glyph->advance_x * _scale;
+ }
+}
+
+void gl_font_draw( GL_FONT* font, GL_SHADER_PROGRAM* shader, VEC2 origin, const char* text, CLR clr, F32 _scale ) {
+ U32 len = strlen( text );
+ VERTEX* vertices = (VERTEX*)malloc( sizeof(VERTEX) * 6 * len );
+ U16* indices = (U16*)malloc( sizeof(U16) * 6 * len );
+ VEC2* coords = (VEC2*)malloc( sizeof(VEC2) * 6 * len );
+
+ gl_font_calc_vertices_uvs( font, origin, text, _scale, vertices, indices, coords, clr );
+ glUseProgram( shader->id );
+
+ glBindBuffer( GL_ARRAY_BUFFER, shader->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * 6 * len, vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( shader->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->pos );
+
+ I32 color = glGetAttribLocation( shader->id, "in_clr" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ I32 texcoord = glGetAttribLocation( shader->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ I32 sampler = glGetAttribLocation( shader->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX), &( (VERTEX*)nullptr)->sampler );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glActiveTexture( GL_TEXTURE0 );
+
+ glBindTexture( GL_TEXTURE_2D, font->atlas->id );
+
+ glDrawElements( GL_TRIANGLES, len * 6, GL_UNSIGNED_SHORT, indices );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ free( vertices );
+ free( indices );
+ free( coords );
+}
+
+void gl_font_textured(
+ GL_FONT* font,
+ GL_SHADER_PROGRAM* shader,
+ VEC2 origin,
+ const char* text,
+ GL_TEX2D* tex,
+ CLR clr,
+ F32 _scale
+) {
+ U32 len = strlen( text );
+ struct FONT_CUSTOM_VERTEX {
+ VERTEX v;
+ VEC2 uv2;
+ };
+
+ FONT_CUSTOM_VERTEX* custom_vertices = (FONT_CUSTOM_VERTEX*)malloc( sizeof(FONT_CUSTOM_VERTEX) * 6 * len );
+ VERTEX* vertices = (VERTEX*)malloc( sizeof(VERTEX) * 6 * len );
+ U16* indices = (U16*)malloc( sizeof(U16) * 6 * len );
+ VEC2* coords = (VEC2*)malloc( sizeof(VEC2) * 6 * len );
+
+ gl_font_calc_vertices_uvs( font, origin, text, _scale, vertices, indices, coords, clr );
+ VEC2 dim = gl_font_dim( font, text, _scale );
+
+ for( U32 i = 0; i < len * 6; ++i ) {
+ custom_vertices[i].v = vertices[i];
+ custom_vertices[i].uv2 = {
+ (vertices[i].pos.x - origin.x) / dim.x,
+ (vertices[i].pos.y - origin.y - vertices[0].pos.y) / dim.y
+ };
+ }
+
+ glUseProgram( shader->id );
+
+ glBindBuffer( GL_ARRAY_BUFFER, shader->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(FONT_CUSTOM_VERTEX) * 6 * len, custom_vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( shader->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), 0 );
+
+ I32 texcoord = glGetAttribLocation( shader->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), (void*)(sizeof(VEC2)) );
+
+ I32 texcoord2 = glGetAttribLocation( shader->id, "in_texcoord2" );
+ glEnableVertexAttribArray( texcoord2 );
+ glVertexAttribPointer( texcoord2, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), (void*)(sizeof(VERTEX)) );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ I32 color = glGetUniformLocation( shader->id, "in_color" );
+ glUniform4fv( color, 1, (F32*)&clr );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glUniform1iv( glGetUniformLocation( shader->id, "in_sampler" ), 0, 0 );
+ glBindTexture( GL_TEXTURE_2D, font->atlas->id );
+
+ glDrawElements( GL_TRIANGLES, len * 6, GL_UNSIGNED_SHORT, indices );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ free( vertices );
+ free( indices );
+ free( coords );
+ free( custom_vertices );
+}
+
+
+VEC2 gl_font_dim( GL_FONT* font, const char* text, F32 _scale ) {
+ U32 len = strlen( text );
+
+ F32 cur_x = 0.f;
+ F32 cur_y = font->char_height * _scale;
+ F32 max_x = 0.f;
+
+ for( U32 i = 0; i < len; ++i ) {
+ if( text[i] == '\n' ) {
+ cur_x = 0;
+ cur_y += font->char_height * _scale;
+ continue;
+ }
+
+ U32 c = (U8)text[i];
+ FONT_GLYPH* glyph = &font->glyphs[c];
+ cur_x += glyph->advance_x * _scale;
+ if( cur_x > max_x )
+ max_x = cur_x;
+ }
+
+ return { max_x, cur_y };
+}
+
+void gl_font_fit_into_box( GL_FONT* font, const char* source, char* out, F32 box_width, F32 box_height, F32 scale ) {
+ U32 len = strlen( source );
+ U32 last_space = 0;
+
+ F32 cur_x = 0.f;
+ for( U32 i = 0; i < len; ++i ) {
+ if( source[i] == ' ' )
+ last_space = i;
+
+ if( source[i] == '\n' ) {
+ cur_x = 0;
+ continue;
+ }
+
+ FONT_GLYPH* glyph = &font->glyphs[(U8)source[i]];
+ cur_x += glyph->advance_x * scale;
+
+ out[i] = source[i];
+ if( cur_x > box_width ) {
+ if( last_space == 0 ) {
+ out[i] = '\n';
+ cur_x = 0;
+ } else {
+ out[last_space] = '\n';
+ cur_x = 0;
+ i = last_space;
+ last_space = 0;
+ }
+ }
+
+ }
+
+ out[len] = '\0';
+}
diff --git a/src/render/gl_2d_font.h b/src/render/gl_2d_font.h
new file mode 100644
index 0000000..71154c0
--- /dev/null
+++ b/src/render/gl_2d_font.h
@@ -0,0 +1,52 @@
+#pragma once
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include "gl.h"
+#include "../util/thread.h"
+
+extern THREAD_MUTEX font_mutex;
+
+typedef struct {
+ F32 offset_x;
+ F32 offset_y;
+ U32 width;
+ U32 height;
+ F32 advance_x;
+ F32 advance_y;
+ U32 bearing;
+} FONT_GLYPH;
+
+const I32 GL_FONT_MAX_GLYPHS = 256;
+typedef struct GL_FONT {
+ FT_Face face;
+ I32 size;
+
+ U32* bitmap;
+ U32 bitmap_width;
+ U32 bitmap_height;
+ U32 char_width;
+ U32 char_height;
+
+ GL_TEX2D* atlas;
+ const char* name;
+
+ FONT_GLYPH glyphs[GL_FONT_MAX_GLYPHS];
+} *PGL_FONT;
+
+GL_FONT* gl_font_create( GL_DATA* gl, const char* path, I32 size );
+void gl_font_destroy( GL_DATA* gl, GL_FONT* font );
+
+void gl_font_draw( GL_FONT* font, GL_SHADER_PROGRAM* shader, VEC2 pos, const char* text, CLR color, F32 scale = 1.0f );
+void gl_font_textured(
+ GL_FONT* font,
+ GL_SHADER_PROGRAM* shader,
+ VEC2 pos,
+ const char* text,
+ GL_TEX2D* tex,
+ CLR color = (CLR){1.f, 1.f, 1.f, 1.f},
+ F32 scale = 1.0f
+);
+
+VEC2 gl_font_dim( GL_FONT* font, const char* text, F32 scale = 1.0f );
+void gl_font_fit_into_box( GL_FONT* font, const char* source, char* out, F32 box_width, F32 box_height, F32 scale );
diff --git a/src/render/gl_3d.cpp b/src/render/gl_3d.cpp
new file mode 100644
index 0000000..78e7f5e
--- /dev/null
+++ b/src/render/gl_3d.cpp
@@ -0,0 +1,204 @@
+#include "gl_3d.h"
+#include "../util/matrix.h"
+#include "gl.h"
+#include "gl_batch.h"
+
+GL_3D* gl_3d_init( GL_DATA* gl, VEC2 screensize, const char* shadername ) {
+ GL_3D *gl3d = (GL_3D*)gl_program_create( gl, shadername, sizeof(GL_3D) );
+ if( !OK( gl_program_compile( gl, gl3d ) ) )
+ dlog( "gl_2d_init() : error compiling shader %s\n", shadername );
+
+ gl3d->winsize = screensize;
+ gl3d->aspect = screensize.x / screensize.y;
+
+ glUseProgram( gl3d->id );
+ I32 samplers_location = glGetUniformLocation( gl3d->id, "g_samplers" );
+ glUniform1iv( samplers_location, gl->shader_texture_limit, SAMPLER_INDICES );
+ return gl3d;
+}
+
+void gl_3d_projection_setup( GL_PROGRAM *_gl3d, VEC3 pos, F32 fov_deg, F32 yaw, F32 pitch, F32 near, F32 far, VEC2 winsize ) {
+ GL_3D* gl3d = (GL_3D*)_gl3d;
+ MAT4 proj, yaw_rot, pitch_rot, view, transl, tmp;
+
+ F32 fov_rad = m_deg2rad( fov_deg );
+ F32 yaw_rad = m_deg2rad( remainderf( yaw + 90.f, 360.f ) );
+ F32 pitch_rad = m_deg2rad( remainderf( pitch , 360.f ) );
+ gl3d->aspect = winsize.x / winsize.y;
+ gl3d->winsize = winsize;
+
+ mat4_perspective( &proj, fov_rad, gl3d->aspect, near, far );
+ mat4_rotation_y( &yaw_rot, -yaw_rad );
+ mat4_rotation_x( &pitch_rot, -pitch_rad );
+ mat4_translation( &transl, -pos.x, -pos.z, -pos.y );
+
+ mat4_mul( &yaw_rot, &transl, &tmp );
+ mat4_mul( &pitch_rot, &tmp, &view );
+ mat4_mul( &proj, &view, &gl3d->projmat );
+ gl3d->gl->proj_matrix = &gl3d->projmat;
+
+ glUseProgram( gl3d->id );
+ I32 location = glGetUniformLocation( gl3d->id, "in_proj_matrix" );
+ glUniformMatrix4fv( location, 1, 1, (GLfloat*)&gl3d->projmat );
+
+ glUniform1f( glGetUniformLocation( gl3d->id, "in_time" ), u_time() );
+}
+
+void gl_3d_batch_setup( GL_BATCH3D *batch ) {
+ glUseProgram( batch->shader->id );
+ glBindBuffer( GL_ARRAY_BUFFER, batch->vbuffer );
+ I32 position = glGetAttribLocation( batch->shader->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 3, GL_FLOAT, 0, sizeof(VERTEX3D), 0 );
+ I32 color = glGetAttribLocation( batch->shader->id, "in_clr" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 3, GL_FLOAT, 0, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->clr );
+ I32 texcoord = glGetAttribLocation( batch->shader->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->uv );
+ I32 sampler = glGetAttribLocation( batch->shader->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->sampler );
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+}
+
+void gl_3d_polygon(
+ GL_PROGRAM* gl3d,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+) {
+ glUseProgram( gl3d->id );
+
+ if( !!tex ) {
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, tex->id );
+ } else {
+ for( U32 i = 0; i < vertices_count; ++i )
+ vertices[i].sampler = SAMPLER_ID_NONE;
+ }
+
+ glBindBuffer( GL_ARRAY_BUFFER, gl3d->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX3D) * vertices_count, vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( gl3d->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 3, GL_FLOAT, 0, sizeof(VERTEX3D), 0 );
+ I32 color = glGetAttribLocation( gl3d->id, "in_clr" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 3, GL_FLOAT, 0, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->clr );
+ I32 texcoord = glGetAttribLocation( gl3d->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->uv );
+ I32 sampler = glGetAttribLocation( gl3d->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX3D), &( (VERTEX3D*)nullptr )->sampler );
+
+ U16* order = (U16*)alloca( sizeof(U16) * vertices_count );
+ for( U32 i = 0; i < vertices_count; i++ )
+ order[i] = i;
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawElements( GL_TRIANGLE_FAN, vertices_count, GL_UNSIGNED_SHORT, order );
+}
+
+void gl_3d_polygon_fan(
+ GL_BATCH3D* batch,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+) {
+ if( vertices_count < 3 )
+ return;
+
+ LIST<VERTEX3D> list = triangle_fan_to_list( vertices, vertices_count );
+ gl_batch_insert(
+ batch,
+ list.data,
+ list.size,
+ tex
+ );
+}
+
+void gl_3d_polygon(
+ GL_BATCH3D* batch,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+) {
+ gl_batch_insert(
+ batch,
+ vertices,
+ vertices_count,
+ tex
+ );
+}
+
+void build_plane( VERTEX3D* v, VEC3 pos, VEC2 size, VEC2 rot, CLR col ) {
+ F32 half_width = size.x * 0.5f;
+ F32 half_height = size.y * 0.5f;
+
+ VEC3 vertices[4] = {
+ { -half_width, -half_height, 0 },
+ { half_width, -half_height, 0 },
+ { half_width, half_height, 0 },
+ { -half_width, half_height, 0 }
+ };
+
+ F32 sin_pitch = sinf( m_deg2rad( rot.x ) );
+ F32 cos_pitch = cosf( m_deg2rad( rot.x ) );
+ F32 sin_yaw = sinf( m_deg2rad( rot.y ) );
+ F32 cos_yaw = cosf( m_deg2rad( rot.y ) );
+
+ for( I32 i = 0; i < 4; i++) {
+ F32 x = vertices[i].x;
+ F32 y = vertices[i].y;
+ F32 z = vertices[i].z;
+ vertices[i].y = y * cos_pitch - z * sin_pitch;
+ vertices[i].z = y * sin_pitch + z * cos_pitch;
+ y = vertices[i].y;
+ vertices[i].x = x * cos_yaw - y * sin_yaw;
+ vertices[i].y = x * sin_yaw + y * cos_yaw;
+
+ vertices[i].x += pos.x;
+ vertices[i].y += pos.y;
+ vertices[i].z += pos.z;
+ }
+
+ VEC2 uvs[4] = {
+ { 0.0f, 1.0f },
+ { 1.0f, 1.0f },
+ { 1.0f, 0.0f },
+ { 0.0f, 0.0f }
+ };
+
+ v[0] = { .pos = { vertices[0].x, vertices[0].z, vertices[0].y }, .uv = uvs[0], .clr = col, .sampler = 0 };
+ v[1] = { .pos = { vertices[1].x, vertices[1].z, vertices[1].y }, .uv = uvs[1], .clr = col, .sampler = 0 };
+ v[2] = { .pos = { vertices[2].x, vertices[2].z, vertices[2].y }, .uv = uvs[2], .clr = col, .sampler = 0 };
+ v[3] = { .pos = { vertices[3].x, vertices[3].z, vertices[3].y }, .uv = uvs[3], .clr = col, .sampler = 0 };
+}
+
+void gl_3d_plane(
+ GL_SHADER_PROGRAM* gl3d,
+ VEC3 pos,
+ VEC2 size,
+ VEC2 rot,
+ GL_TEX2D* tex,
+ CLR col
+) {
+ VERTEX3D v[4];
+ build_plane( v, pos, size, rot, col );
+ gl_3d_polygon( gl3d, v, 4, tex );
+}
+
+void gl_3d_plane(
+ GL_BATCH3D* batch,
+ VEC3 pos,
+ VEC2 size,
+ VEC2 rot,
+ GL_TEX2D* tex,
+ CLR col
+) {
+ VERTEX3D v[4];
+ build_plane( v, pos, size, rot, col );
+ gl_3d_polygon_fan( batch, v, 4, tex );
+}
diff --git a/src/render/gl_3d.h b/src/render/gl_3d.h
new file mode 100644
index 0000000..531c3a3
--- /dev/null
+++ b/src/render/gl_3d.h
@@ -0,0 +1,69 @@
+#pragma once
+#include "gl_batch.h"
+#include "../util/matrix.h"
+
+// magic number sue me. yes this float is representable exactly.
+const F32 SCREEN_INVALID_POS = -2137000.f;
+
+struct VERTEX3D {
+ VEC3 pos;
+ VEC2 uv;
+ CLR clr;
+ U8 sampler;
+};
+
+using GL_BATCH3D = GL_BATCH<VERTEX3D>;
+struct GL_3D : GL_SHADER_PROGRAM {
+ F32 aspect;
+ MAT4 projmat;
+
+ VEC2 winsize;
+};
+
+// shader init -----------------------------------------------------------
+extern GL_3D* gl_3d_init( GL_DATA* gl, VEC2 screensize, const char* shadername );
+// sets up projection matrix and sends it to the gpu ------------------------------------------------------------
+extern void gl_3d_projection_setup( GL_SHADER_PROGRAM* gl3d, VEC3 pos, F32 fov_deg, F32 yaw, F32 pitch, F32 near, F32 far, VEC2 winsize );
+extern void gl_3d_batch_setup( GL_BATCH3D* batch );
+
+// takes a triangle fan not set of triangles
+extern void gl_3d_polygon(
+ GL_SHADER_PROGRAM* gl3d,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+);
+
+// calculates triangle set from triangle fan before sending to batch
+extern void gl_3d_polygon_fan(
+ GL_BATCH3D* batch,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+);
+
+// draws a set of triangles
+extern void gl_3d_polygon(
+ GL_BATCH3D* batch,
+ VERTEX3D* vertices,
+ U32 vertices_count,
+ GL_TEX2D* tex
+);
+
+extern void gl_3d_plane(
+ GL_SHADER_PROGRAM* gl3d,
+ VEC3 pos,
+ VEC2 size,
+ VEC2 rot,
+ GL_TEX2D* tex,
+ CLR col = CLR::WHITE()
+);
+
+extern void gl_3d_plane(
+ GL_BATCH3D* batch,
+ VEC3 pos,
+ VEC2 size,
+ VEC2 rot,
+ GL_TEX2D* tex,
+ CLR col = CLR::WHITE()
+);
diff --git a/src/render/gl_batch.h b/src/render/gl_batch.h
new file mode 100644
index 0000000..944521f
--- /dev/null
+++ b/src/render/gl_batch.h
@@ -0,0 +1,193 @@
+#pragma once
+#include "gl.h"
+
+// this is maybe a bit too much c++ voodoo for my liking
+
+const U32 BATCH_VERTICES_MAX = 16384;
+
+template <typename VERTEX>
+struct GL_BATCH;
+
+// func for setting sampler idx inside of vertex data
+template <typename VERTEX>
+using GL_BATCH_VERTEX_SAMPLER_IDX_FN = void (*)(VERTEX* vertex, U8 idx);
+
+template <typename VERTEX>
+using GL_BATCH_SETUP_FN = void (*)(GL_BATCH<VERTEX>* batch);
+
+template <typename VERTEX>
+struct GL_BATCH_CALL {
+ LIST<VERTEX> vertices{};
+ VEC2 clip_start;
+ VEC2 clip_dim;
+
+ VEC2 viewport_start;
+ VEC2 viewport_dim;
+
+ U16 primitive;
+ LIST<GL_TEX2D*> textures{};
+};
+
+template <typename VERTEX>
+struct GL_BATCH {
+ GL_DATA* gl;
+ GL_SHADER_PROGRAM* shader;
+
+ VEC2 clip_start;
+ VEC2 clip_dim;
+
+ VEC2 viewport_start;
+ VEC2 viewport_dim;
+
+ GLuint vbuffer;
+
+ LIST<GL_BATCH_CALL<VERTEX>> calls{};
+
+ // func for setting up vertex attribs
+ GL_BATCH_SETUP_FN<VERTEX> setup_cb;
+};
+
+/*
+ * takes in a callback func that gets called before the batch is drawn
+ *
+ * should set up vertex attribs correctly according to vertex fmt
+ */
+template <typename VERTEX>
+inline GL_BATCH<VERTEX>* gl_batch_create(
+ GL_DATA* gl,
+ GL_SHADER_PROGRAM* shader,
+ GL_BATCH_SETUP_FN<VERTEX> setup_cb
+) {
+ GL_BATCH<VERTEX>* batch = new GL_BATCH<VERTEX>;
+ batch->gl = gl;
+ batch->shader = shader;
+
+ batch->clip_start = gl->clip_start;
+ batch->clip_dim = gl->clip_dim;
+
+ batch->viewport_start = gl->viewport_start;
+ batch->viewport_dim = gl->viewport_dim;
+
+ batch->setup_cb = setup_cb;
+
+ glGenBuffers( 1, &batch->vbuffer );
+ glBindBuffer( GL_ARRAY_BUFFER, batch->vbuffer );
+ // make sure we have enough vram
+ glBufferData( GL_ARRAY_BUFFER, sizeof( VERTEX ) * BATCH_VERTICES_MAX, 0, GL_DYNAMIC_DRAW );
+ return batch;
+}
+
+template <typename VERTEX>
+inline void gl_batch_destroy( GL_BATCH<VERTEX>* batch ) {
+ glDeleteBuffers( &batch->vbuffer );
+ delete batch;
+}
+
+template <typename VERTEX>
+inline void gl_batch_empty( GL_BATCH<VERTEX>* batch ) {
+ batch->calls.clear();
+}
+
+template <typename VERTEX>
+inline void gl_batch_draw( GL_BATCH<VERTEX>* batch ) {
+ glUseProgram( batch->shader->id );
+
+ VEC2 vp_start, vp_dim, clip_start, clip_dim;
+ gl_get_viewport( batch->gl, &vp_start, &vp_dim );
+ gl_get_clip( batch->gl, &clip_start, &clip_dim );
+
+ batch->setup_cb( batch );
+ batch->calls.each( fn( GL_BATCH_CALL<VERTEX>* call ) {
+ for( U32 i = 0; i < call->textures.size; ++i ) {
+ glActiveTexture( GL_TEXTURE0 + i );
+ glBindTexture( GL_TEXTURE_2D, call->textures.data[i] ? call->textures.data[i]->id : 0 );
+ }
+
+ glBindBuffer( GL_ARRAY_BUFFER, batch->vbuffer );
+ glBufferData(
+ GL_ARRAY_BUFFER,
+ sizeof(VERTEX) * call->vertices.size,
+ call->vertices.data,
+ GL_DYNAMIC_DRAW
+ );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+ glDrawArrays( call->primitive, 0, call->vertices.size );
+ } );
+
+ gl_set_viewport( batch->gl, vp_start, vp_dim );
+ gl_set_clip( batch->gl, clip_start, clip_dim );
+}
+
+template <typename VERTEX>
+inline GL_BATCH_CALL<VERTEX>* gl_batch_get_call( GL_BATCH<VERTEX>* batch, GL_TEX2D* tex, U16 primitive, I32* texid ) {
+ U32 n = batch->calls.size;
+ VEC2 vp_start = batch->viewport_start;
+ VEC2 vp_dim = batch->viewport_dim;
+ VEC2 clip_start = batch->clip_start;
+ VEC2 clip_dim = batch->clip_dim;
+
+ if( n ) {
+ GL_BATCH_CALL<VERTEX>* last = &batch->calls.data[n - 1];
+ if( last->primitive == primitive
+ && vp_start == last->clip_start && vp_dim == last->viewport_dim
+ && clip_start == last->clip_start && clip_dim == last->clip_dim
+ ) {
+ I32 idx = last->textures.idx_of( tex );
+ if( idx != -1 ) {
+ *texid = idx;
+ return last;
+ }
+ else if( last->textures.size < batch->gl->shader_texture_limit ) {
+ *texid = last->textures.size;
+ last->textures.push( tex );
+ return last;
+ }
+ }
+ }
+
+
+ GL_BATCH_CALL<VERTEX> call{
+ .vertices = {},
+ .clip_start = batch->clip_start,
+ .clip_dim = batch->clip_dim,
+ .viewport_start = batch->viewport_start,
+ .viewport_dim = batch->viewport_dim,
+ .primitive = primitive,
+ .textures = {},
+ };
+
+ *texid = 0;
+ call.textures.push( tex );
+ return batch->calls.push( call );
+}
+
+template <typename VERTEX>
+inline void gl_batch_insert(
+ GL_BATCH<VERTEX>* batch,
+ VERTEX* vertices,
+ U32 count,
+ GL_TEX2D* tex,
+ U16 primitive = GL_TRIANGLES
+) {
+ if( !count )
+ return;
+
+ GL_BATCH_CALL<VERTEX>* call;
+ I32 texid;
+ VEC2 vp_start, vp_dim;
+ VEC2 clip_start, clip_dim;
+
+ gl_get_viewport( batch->gl, &vp_start, &vp_dim );
+ gl_get_clip( batch->gl, &clip_start, &clip_dim );
+
+ batch->viewport_start = vp_start; batch->viewport_dim = vp_dim;
+ batch->clip_start = clip_start; batch->clip_dim = clip_dim;
+
+ call = gl_batch_get_call( batch, tex, primitive, &texid );
+
+ for( U32 i = 0; i < count; ++i ) {
+ vertices[i].sampler = (!!tex)? texid : SAMPLER_ID_NONE;
+ call->vertices.push( vertices[i] );
+ }
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..32fdb7e
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,31 @@
+#pragma once
+#include <SDL.h>
+
+#include "SDL_timer.h"
+#include "util/color.h"
+#include "util/allocator.h"
+#include "util/vector.h"
+#include "util/math.h"
+#include "util/config.h"
+#include "util/screen.h"
+#include "util/input.h"
+#include "util/file.h"
+#include "util/thread.h"
+
+inline U64 u_tick() {
+ return (F64)SDL_GetPerformanceCounter() * 10000 / ((F64)SDL_GetPerformanceFrequency() );
+}
+
+inline F32 u_time() {
+ return SDL_GetTicks64() / 1000.f;
+}
+
+template < typename T >
+T min( T a, T b ) {
+ return a < b? a : b;
+}
+
+template < typename T >
+T max( T a, T b ) {
+ return a > b? a : b;
+}
diff --git a/src/util/aabb.h b/src/util/aabb.h
new file mode 100644
index 0000000..f2fd251
--- /dev/null
+++ b/src/util/aabb.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "vector.h"
+
+struct AABB {
+ VEC3 min;
+ VEC3 max;
+};
+
+inline VEC3 aabb_center( const AABB& aabb ) { return (aabb.min + aabb.max) * 0.5f; }
+inline VEC3 aabb_half( const AABB& aabb ) { return (aabb.max - aabb.min) * 0.5f; }
+inline F32 aabb_support_radius( const AABB& aabb, const VEC3& dir ) {
+ VEC3 half = aabb_half( aabb );
+ return fabsf(dir.x) * half.x + fabsf(dir.y) * half.y + fabsf(dir.z) * half.z;
+}
+
+inline VEC2 aabb_extend_at_feet( const AABB& aabb, const VEC3& dir ) {
+ VEC3 half = aabb_half( aabb );
+
+ F32 hx = half.x;
+ F32 hy = half.y;
+ F32 hz = half.z * 2;
+
+ F32 lateral = fabsf( dir.x ) * hx + fabsf( dir.y ) * hy;
+
+ F32 pos_z = dir.z > 0 ? dir.z * hz : 0.f;
+ F32 neg_z = dir.z < 0 ? (-dir.z) * hz : 0.f;
+
+ return { lateral + pos_z, lateral + neg_z };
+}
diff --git a/src/util/allocator.h b/src/util/allocator.h
new file mode 100644
index 0000000..ee0ddd8
--- /dev/null
+++ b/src/util/allocator.h
@@ -0,0 +1,297 @@
+#pragma once
+
+#include <stdlib.h>
+#include <string.h>
+#include <functional>
+#include "typedef.h"
+
+template < typename T >
+using QSORT_FN = std::function< U8( T*, T* ) >;
+
+template < typename T >
+static U8 qsort_basic_sort( T* t1, T* t2 ) {
+ return (*t1 > *t2);
+}
+
+// sort fn should return 0 if t1 < t2, else 1
+template < typename T >
+static void _qsort( T* arr, I32 low, I32 high, QSORT_FN<T> sortfn = qsort_basic_sort<T> ) {
+ if( low > high )
+ return;
+
+ T pivot = arr[high];
+ I32 i = ( low - 1 );
+
+ for( I32 j = low; j <= high - 1; j++ ) {
+ if( sortfn( &arr[j], &pivot ) ) {
+ i++;
+ T temp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = temp;
+ }
+ }
+ T temp = arr[i + 1];
+ arr[i + 1] = arr[high];
+ arr[high] = temp;
+ I32 pi = i + 1;
+
+ _qsort( arr, low, pi - 1, sortfn );
+ _qsort( arr, pi + 1, high, sortfn );
+}
+
+template < typename T >
+struct LIST {
+ T* data;
+ U32 capacity;
+ U32 size;
+
+ using ON_EACH_FN = std::function< void(T*) >;
+ using ON_SEARCH_FN = std::function< bool(T*) >;
+
+ LIST() {
+ data = (T*)malloc( sizeof( T ) );
+ memset( data, 0, sizeof( T ) );
+ capacity = 1;
+ size = 0;
+ }
+
+ LIST( U32 _size ) {
+ if( !_size ) _size = 1;
+ data = (T*)malloc( sizeof( T ) * _size );
+ memset( data, 0, _size * sizeof( T ) );
+ capacity = _size;
+ size = 0;
+ }
+
+ LIST( const LIST<T>& other ) {
+ if( !other.capacity || !other.size ) {
+ capacity = 1;
+ size = 0;
+ data = (T*)malloc( sizeof( T ) );
+ memset( data, 0, sizeof( T ) );
+ return;
+ }
+
+ data = (T*)malloc( sizeof( T ) * other.capacity );
+ memcpy( data, other.data, other.size * sizeof( T ) );
+ size = other.size;
+ capacity = other.capacity;
+ }
+
+ LIST<T>& operator=( const LIST<T>& other ) {
+ if( this == &other )
+ return *this;
+
+ if( data && data != other.data )
+ free( data );
+
+ data = 0;
+
+ if( !other.capacity || !other.size ) {
+ capacity = 1;
+ size = 0;
+ data = (T*)malloc( sizeof( T ) );
+ return *this;
+ }
+
+ data = (T*)malloc( other.capacity * sizeof( T ) );
+ memcpy( data, other.data, other.size * sizeof( T ) );
+ size = other.size;
+ capacity = other.capacity;
+
+ return *this;
+ }
+
+ ~LIST() {
+ free( data );
+ }
+
+ void reserve( U32 count ) {
+ if( capacity >= count )
+ return;
+
+ T* newblock = (T*)malloc( count * sizeof( T ) );
+ memset( newblock, 0, capacity * sizeof( T ) );
+ if( data ) {
+ memcpy( newblock, data, size * sizeof( T ) );
+ free( data );
+ }
+ data = newblock;
+ capacity = count;
+ }
+
+ void grow() {
+ capacity = 2 * capacity;
+ T* newblock = (T*)malloc( capacity * sizeof( T ) );
+ memset( newblock, 0, capacity * sizeof( T ) );
+ if( data ) {
+ memcpy( newblock, data, size * sizeof( T ) );
+ free( data );
+ }
+ data = newblock;
+ }
+
+ void shrink() {
+ if( capacity == 0 )
+ return;
+
+ capacity /= 2;
+ T* newblock = (T*)malloc( capacity * sizeof( T ) );
+ if( data ) {
+ memcpy( newblock, data, capacity * sizeof( T ) );
+ free( data );
+ }
+ data = newblock;
+ }
+
+ // does not call copy funcs or constructors
+ T* emplace( const T& item ) {
+ if( capacity == size )
+ grow();
+ memcpy( &data[size++], &item, sizeof( T ) );
+
+ return &data[size - 1];
+ }
+
+ T* push( const T& item ) {
+ if( capacity == size )
+ grow();
+ data[size++] = item;
+
+ return &data[size - 1];
+ }
+
+ // does not call copy constructors, raw memcpy
+ void emplace_list( const LIST<T>& list ) {
+ if( !list.size )
+ return;
+ capacity += list.size;
+ T* newblock = (T*)malloc( capacity * sizeof(T) );
+ memset( newblock, 0, sizeof(T) * capacity );
+ if( data ) {
+ memcpy( newblock, data, sizeof(T) * size );
+ free( data );
+ }
+
+ memcpy( &newblock[size], list.data, sizeof(T) * list.size );
+ data = newblock;
+ size += list.size;
+ }
+
+ // slow - pushes every item individually, calls copy constructors
+ void push_list( LIST<T>& list ) {
+ list.each( fn( T* item ) {
+ push( *item );
+ } );
+ }
+
+ T pop() {
+ if( size == 0 ) {
+ dlog( "LIST::pop_front() : called on empty list\n" );
+ abort();
+ return {};
+ }
+
+ T ret = data[--size];
+ if( size < capacity / 4 )
+ shrink();
+
+ return ret;
+ }
+
+ T pop_front() {
+ if( size == 0 ) {
+ dlog( "LIST::pop_front() : called on empty list\n" );
+ abort();
+ return {};
+ }
+
+ T ret = data[0];
+ erase( 0 );
+ return ret;
+ }
+
+ void erase( U32 idx ) {
+ if( idx >= size )
+ return;
+
+ for( U32 i = idx; i < size - 1; i++ )
+ data[i] = data[i + 1];
+
+ if( --size < capacity / 4 )
+ shrink();
+ }
+
+ T& operator[]( U32 idx ) {
+ return data[idx];
+ }
+
+ void clear() {
+ size = 0;
+ free( data );
+ data = (T*)malloc( sizeof( T ) );
+ memset( data, 0, sizeof( T ) );
+ capacity = 1;
+ }
+
+ void each( ON_EACH_FN func ) {
+ for( U32 i = 0; i < size; ++i ) {
+ func( &data[i] );
+ }
+ }
+
+ I32 idx_of( const T& what ) {
+ for( U32 i = 0; i < size; ++i ) {
+ if( data[i] == what ) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ I32 idx_where( ON_SEARCH_FN what ) {
+ for( U32 i = 0; i < size; ++i ) {
+ if( what( &data[i] ) ) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ T* find( const T& what ) {
+ for( U32 i = 0; i < size; ++i ) {
+ if( data[i] == what ) {
+ return &data[i];
+ }
+ }
+ return 0;
+ }
+
+ T* where( ON_SEARCH_FN what ) {
+ for( U32 i = 0; i < size; ++i ) {
+ if( what( &data[i] ) ) {
+ return &data[i];
+ }
+ }
+ return 0;
+ }
+
+ // does not copy, sorts in place
+ //
+ // sort fn should return 0 if t1 < t2, else 1
+ LIST<T>& sort( QSORT_FN<T> fn = qsort_basic_sort<T> ) {
+ _qsort<T>( data, 0, size - 1, fn );
+ return *this;
+ }
+
+ // creates a sorted copy
+ //
+ // sort fn should return 0 if t1 < t2, else 1
+ LIST<T> sorted( QSORT_FN<T> fn = qsort_basic_sort<T> ) {
+ LIST<T> ret = LIST<T>( *this );
+ ret.sort( fn );
+ return ret;
+ }
+};
diff --git a/src/util/anim.h b/src/util/anim.h
new file mode 100644
index 0000000..dbc2c11
--- /dev/null
+++ b/src/util/anim.h
@@ -0,0 +1,27 @@
+#include "typedef.h"
+
+#include <SDL.h>
+
+template < typename T >
+inline T lerp( T a, T b, F32 t ) {
+ return a + ( b - a ) * t;
+}
+
+template < typename t >
+inline t clamp( t a, t min, t max ) {
+ if ( a < min ) return min;
+ if ( a > max ) return max;
+ return a;
+}
+
+inline F32 ease_in( F32 t, F32 pow = 2.f ) {
+ return powf( t, pow );
+}
+
+inline F32 ease_out( F32 t, F32 pow = 2.f ) {
+ return 1.f - powf( 1.f - t, pow );
+}
+
+inline F32 ease_in_out( F32 t, F32 pow = 2.f ) {
+ return t < 0.5f ? ease_in( t * 2.f, pow ) / 2.f : ease_out( t * 2.f - 1.f, pow ) / 2.f + 0.5f;
+}
diff --git a/src/util/color.h b/src/util/color.h
new file mode 100644
index 0000000..526cddc
--- /dev/null
+++ b/src/util/color.h
@@ -0,0 +1,168 @@
+#pragma once
+#include "typedef.h"
+#include <math.h>
+
+struct CLR {
+ F32 r, g, b, a;
+
+ static const CLR WHITE( F32 a_ = 1.f ) { return { 1.f, 1.f, 1.f, a_ }; }
+ static const CLR BLACK( F32 a_ = 1.f ) { return { 0.f, 0.f, 0.f, a_ }; }
+ static const CLR RED( F32 a_ = 1.f ) { return { 1.f, 0.f, 0.f, a_ }; }
+ static const CLR GREEN( F32 a_ = 1.f ) { return { 0.f, 1.f, 0.f, a_ }; }
+ static const CLR BLUE( F32 a_ = 1.f ) { return { 0.f, 0.f, 1.f, a_ }; }
+ static const CLR YELLOW( F32 a_ = 1.f ) { return { 1.f, 1.f, 0.f, a_ }; }
+ static const CLR MAGENTA( F32 a_ = 1.f ) { return { 1.f, 0.f, 1.f, a_ }; }
+ static const CLR CYAN( F32 a_ = 1.f ) { return { 0.f, 1.f, 1.f, a_ }; }
+
+ // slow accurate blend, looks prettier but cache maybe if possible
+ static CLR blend( const CLR& c1, const CLR& c2, float t = 0.5f ) {
+ F32 c1r = c1.r * c1.r;
+ F32 c1g = c1.g * c1.g;
+ F32 c1b = c1.b * c1.b;
+ F32 c1a = c1.a * c1.a;
+ F32 c2r = c2.r * c2.r;
+ F32 c2g = c2.g * c2.g;
+ F32 c2b = c2.b * c2.b;
+ F32 c2a = c2.a * c2.a;
+ return {
+ sqrtf( ( c2r * t ) + ( c1r * ( 1.f - t ) ) ),
+ sqrtf( ( c2g * t ) + ( c1g * ( 1.f - t ) ) ),
+ sqrtf( ( c2b * t ) + ( c1b * ( 1.f - t ) ) ),
+ sqrtf( ( c2a * t ) + ( c1a * ( 1.f - t ) ) )
+ };
+ }
+
+ // fast inaccurate blend
+ static CLR blend_root( const CLR& c1, const CLR& c2, float t = 0.5f ) {
+ return {
+ ( c2.r * t ) + ( c1.r * ( 1.f - t ) ),
+ ( c2.g * t ) + ( c1.g * ( 1.f - t ) ),
+ ( c2.b * t ) + ( c1.b * ( 1.f - t ) ),
+ ( c2.a * t ) + ( c1.a * ( 1.f - t ) )
+ };
+ }
+
+ static CLR from_hsb( F32 hue, F32 saturation, F32 brightness ) {
+ CLR c;
+ if( saturation <= 0.0f ) {
+ c.r = c.g = c.b = brightness;
+ return c;
+ }
+
+ I32 h = (I32)( hue * 6 );
+ F32 f = hue * 6 - h;
+ F32 p = brightness * ( 1 - saturation );
+ F32 q = brightness * ( 1 - f * saturation );
+ F32 t = brightness * ( 1 - (1 - f) * saturation );
+
+ switch( h % 6 ) {
+ case 0:
+ c.r = brightness; c.g = t; c.b = p;
+ return c;
+ case 1:
+ c.r = q; c.g = brightness; c.b = p;
+ return c;
+ case 2:
+ c.r = p; c.g = brightness; c.b = t;
+ return c;
+ case 3:
+ c.r = p; c.g = q; c.b = brightness;
+ return c;
+ case 4:
+ c.r = t; c.g = p; c.b = brightness;
+ return c;
+ case 5:
+ c.r = brightness; c.g = p; c.b = q;
+ return c;
+ }
+
+ return c;
+ }
+
+ CLR operator+( const CLR& c2 ) {
+ return { this->r + c2.r, this->g + c2.g, this->b + c2.b, this->a + c2.a };
+ }
+ CLR operator-( const CLR& c2 ) {
+ return { this->r - c2.r, this->g - c2.g, this->b - c2.b, this->a - c2.a };
+ }
+ CLR operator*( const CLR& c2 ) {
+ return { this->r * c2.r, this->g * c2.g, this->b * c2.b, this->a * c2.a };
+ }
+ CLR operator/( const CLR& c2 ) {
+ F32 r = (c2.r != 0.0f) ? this->r / c2.r : 0.0f;
+ F32 g = (c2.g != 0.0f) ? this->g / c2.g : 0.0f;
+ F32 b = (c2.b != 0.0f) ? this->b / c2.b : 0.0f;
+ F32 a = (c2.a != 0.0f) ? this->a / c2.a : 0.0f;
+
+ return { r, g, b, a };
+ }
+
+ CLR& operator+=( const CLR& c2 ) {
+ r += c2.r;
+ g += c2.g;
+ b += c2.b;
+ a += c2.a;
+ return *this;
+ }
+
+ CLR& operator-=( const CLR& c2 ) {
+ r -= c2.r;
+ g -= c2.g;
+ b -= c2.b;
+ a -= c2.a;
+ return *this;
+ }
+
+ CLR& operator*=( const CLR& c2 ) {
+ r *= c2.r;
+ g *= c2.g;
+ b *= c2.b;
+ a *= c2.a;
+ return *this;
+ }
+
+ CLR& operator/=( const CLR& c2 ) {
+ if( c2.r != 0.0f ) { r /= c2.r; }
+ else { r = 0.0f; }
+
+ if( c2.g != 0.0f ) { g /= c2.g; }
+ else { g = 0.0f; }
+
+ if( c2.b != 0.0f ) { b /= c2.b; }
+ else { b = 0.0f; }
+
+ if( c2.a != 0.0f ) { a /= c2.a; }
+ else { a = 0.0f; }
+
+ return *this;
+ }
+
+ CLR operator*( F32 f ) {
+ return { r * f, g * f, b * f, a * f };
+ }
+
+ CLR operator/( F32 f ) {
+ if( f != 0.0f )
+ return { this->r / f, this->g / f, this->b / f, this->a / f };
+ else
+ return { 0.0f, 0.0f, 0.0f, 0.0f };
+ }
+
+ CLR& operator*=( F32 f ) {
+ r *= f; g *= f; b *= f;
+ return *this;
+ }
+
+ CLR& operator/=( F32 f ) {
+ if( f != 0.0f ) {
+ r /= f;
+ g /= f;
+ b /= f;
+ }
+ else {
+ r = g = b = a = 0.f;
+ }
+
+ return *this;
+ }
+};
diff --git a/src/util/config.h b/src/util/config.h
new file mode 100644
index 0000000..7973db0
--- /dev/null
+++ b/src/util/config.h
@@ -0,0 +1,166 @@
+#pragma once
+#include <stdio.h>
+
+#include "allocator.h"
+#include "vector.h"
+#include "color.h"
+
+// config parsers and serializers go here
+
+struct CFG_NODE;
+struct CFG_SECTION;
+
+struct CFG_PARSER;
+struct CFG_SERIALIZER;
+
+enum CfgNodeType_t {
+ CFGT_SECTION,
+ CFGT_BYTES,
+ CFGT_STR,
+ CFGT_FLOAT,
+ CFGT_INT,
+ CFGT_VEC2,
+ CFGT_VEC3,
+ CFGT_CLR
+};
+
+// ===================================== [ definitions ] ===========================================
+
+using CFG_PARSEFN = std::function<void( CFG_PARSER*, CFG_SECTION*, char* )>;
+using CFG_SERIALIZEFN = std::function<void( CFG_SERIALIZER*, CFG_NODE*, char* )>;
+
+struct CFG_TYPE {
+ U8 type;
+ const char* def;
+ CFG_PARSEFN parser;
+ CFG_SERIALIZEFN serializer;
+};
+
+struct CFG_NODE {
+ char name[64];
+ CFG_NODE* parent;
+ U8 type;
+};
+
+struct CFG_SECTION : CFG_NODE {
+ LIST<CFG_NODE*> children;
+};
+
+struct CFG_BYTES : CFG_NODE { U8* bytes; U32 size; };
+struct CFG_STR : CFG_NODE { char* str; U32 len; };
+struct CFG_INT : CFG_NODE { I32 value; };
+struct CFG_FLOAT : CFG_NODE { F32 value; };
+struct CFG_VEC2 : CFG_NODE { VEC2 value; };
+struct CFG_VEC3 : CFG_NODE { VEC3 value; };
+struct CFG_CLR : CFG_NODE { CLR value; };
+
+// ======================================= [ parsers ] =============================================
+
+extern STAT cfg_parser_bytes( CFG_PARSER* parser, CFG_SECTION* section, char* name );
+extern STAT cfg_parser_str( CFG_PARSER* parser, CFG_SECTION* section, char* name );
+extern STAT cfg_parser_int( CFG_PARSER* parser, CFG_SECTION* section, char* name );
+extern STAT cfg_parser_float( CFG_PARSER* parser, CFG_SECTION* section, char* name );
+extern STAT cfg_parser_vec2( CFG_PARSER* parser, CFG_SECTION* section, char* name );
+extern STAT cfg_parser_vec3( CFG_PARSER* parser, CFG_SECTION* section, char* name );
+extern STAT cfg_parser_clr( CFG_PARSER* parser, CFG_SECTION* section, char* name );
+
+// ====================================== [ serializers ] ==========================================
+
+extern void cfg_serialize_section( CFG_SERIALIZER* serializer, CFG_NODE* section, char* buf );
+extern void cfg_serialize_bytes( CFG_SERIALIZER* serializer, CFG_NODE* node, char* buf );
+extern void cfg_serialize_str( CFG_SERIALIZER* serializer, CFG_NODE* node, char* buf );
+extern void cfg_serialize_float( CFG_SERIALIZER* serializer, CFG_NODE* node, char* buf );
+extern void cfg_serialize_int( CFG_SERIALIZER* serializer, CFG_NODE* node, char* buf );
+extern void cfg_serialize_vec2( CFG_SERIALIZER* serializer, CFG_NODE* node, char* buf );
+extern void cfg_serialize_vec3( CFG_SERIALIZER* serializer, CFG_NODE* node, char* buf );
+extern void cfg_serialize_clr( CFG_SERIALIZER* serializer, CFG_NODE* node, char* buf );
+
+// ========================================= [ config ] ============================================
+
+static const CFG_TYPE cfg_types[] = {
+ { CFGT_SECTION, "DEF" , 0, &cfg_serialize_section }, // section
+ { CFGT_BYTES , "U8" , &cfg_parser_bytes, &cfg_serialize_bytes },
+ { CFGT_STR , "STR" , &cfg_parser_str , &cfg_serialize_str },
+ { CFGT_FLOAT , "F32" , &cfg_parser_float, &cfg_serialize_float },
+ { CFGT_INT , "I32" , &cfg_parser_int , &cfg_serialize_int },
+ { CFGT_VEC2 , "VEC2", &cfg_parser_vec2 , &cfg_serialize_vec2 },
+ { CFGT_VEC3 , "VEC3", &cfg_parser_vec3 , &cfg_serialize_vec3 },
+ { CFGT_CLR , "CLR" , &cfg_parser_clr , &cfg_serialize_clr },
+};
+// ========================================= [ io ] ================================================
+
+extern CFG_SECTION* cfg_load( const char* path );
+extern STAT cfg_save( CFG_SECTION* root, const char* path );
+static void cfg_free( CFG_NODE* n );
+
+// =================================== [ getters/setters ] =========================================
+
+// cfg_section_get( CFG_SECTION*, const char* ) -- retrieve function
+extern CFG_SECTION* cfg_section( CFG_SECTION* parent, const char* name );
+// cfg_str_get( CFG_SECTION*, const char* ) -- retrieve function
+extern CFG_STR* cfg_str( CFG_SECTION* section, const char* name );
+// cfg_bytes_get( CFG_SECTION*, const char* ) -- retrieve function
+extern CFG_BYTES* cfg_bytes( CFG_SECTION* section, const char* name );
+// cfg_float_get( CFG_SECTION*, const char* ) -- retrieve function
+extern CFG_FLOAT* cfg_float( CFG_SECTION* section, const char* name );
+// cfg_int_get( CFG_SECTION*, const char* ) -- retrieve function
+extern CFG_INT* cfg_int( CFG_SECTION* section, const char* name );
+// cfg_vec2_get( CFG_SECTION*, const char* ) -- retrieve function
+extern CFG_VEC2* cfg_vec2( CFG_SECTION* section, const char* name );
+// cfg_vec3_get( CFG_SECTION*, const char* ) -- retrieve function
+extern CFG_VEC3* cfg_vec3( CFG_SECTION* section, const char* name );
+// cfg_clr_get( CFG_SECTION*, const char* ) -- retrieve function for
+extern CFG_CLR* cfg_clr( CFG_SECTION* section, const char* name );
+
+// cfg_section_new( const char*, CFG_NODE*, I32 ) -- create function
+extern CFG_SECTION* cfg_section_new( const char* name, CFG_NODE* parent );
+// cfg_str_new( const char*, CFG_NODE*, const char*, U32 ) -- create function
+extern CFG_STR* cfg_str( const char* name, CFG_NODE* section, const char* value, U32 len );
+// cfg_bytes_new( const char*, CFG_NODE*, U8*, U32 ) -- create function
+extern CFG_BYTES* cfg_bytes( const char* name, CFG_NODE* section, U8* value, U32 size );
+// cfg_float_new( const char*, CFG_NODE*, F32 ) -- create function
+extern CFG_FLOAT* cfg_float( const char* name, CFG_NODE* section, F32 value );
+// cfg_int_new( const char*, CFG_NODE*, I32 ) -- create function
+extern CFG_INT* cfg_int( const char* name, CFG_NODE* section, I32 value );
+// cfg_vec2_new( const char*, CFG_NODE*, VEC2 ) -- create function
+extern CFG_VEC2* cfg_vec2( const char* name, CFG_NODE* section, VEC2 value );
+// cfg_vec3_new( const char*, CFG_NODE*, VEC3 ) -- create function
+extern CFG_VEC3* cfg_vec3( const char* name, CFG_NODE* section, VEC3 value );
+// cfg_clr_new( const char*, CFG_NODE*, CLR ) -- create function
+extern CFG_CLR* cfg_clr( const char* name, CFG_NODE* section, CLR value );
+
+// =================================================================================================
+
+struct CFG_PARSER {
+ FILE* file;
+ CFG_SECTION* root;
+ char err[256];
+ U32 linen;
+ U8 iserr;
+};
+
+struct CFG_SERIALIZER {
+ FILE* f;
+ U8 iserr;
+ char err[256];
+ U32 tabc;
+};
+
+static void cfg_free( CFG_NODE* n ) {
+ if( n->type == CFGT_STR ) {
+ CFG_STR* str = (CFG_STR*)n;
+ free( str->str );
+ }
+ else if( n->type == CFGT_BYTES ) {
+ CFG_BYTES* bytes = (CFG_BYTES*)n;
+ free( bytes->bytes );
+ }
+ else if( n->type == CFGT_SECTION ) {
+ CFG_SECTION* s = (CFG_SECTION*)n;
+ s->children.each( fn( CFG_NODE** child ) { cfg_free( *child ); } );
+ }
+
+ delete n;
+}
+
+extern void cfg_seterr( CFG_PARSER* p, const char* fmt, ... );
diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp
new file mode 100644
index 0000000..1601afe
--- /dev/null
+++ b/src/util/config/config.cpp
@@ -0,0 +1,263 @@
+#include <cstdarg>
+#include <cstdio>
+
+#include "../config.h"
+
+void cfg_seterr( CFG_PARSER* p, const char* fmt, ... ) {
+ va_list args;
+ va_start( args, fmt );
+ vsnprintf( p->err, sizeof(p->err), fmt, args );
+ p->iserr = 1;
+ va_end( args );
+}
+
+inline U8 is_whitespace( char c ) {
+ return c == ' ' || c == '\t' || c == '\n';
+}
+
+inline void trim_whitespace( char* buf ) {
+ U32 i;
+ for( i = 0; !!buf[i]; ++i )
+ if( !is_whitespace( buf[i] ) ) break;
+ for( U32 i2 = i; !!buf[i2]; ++i2 ) {
+ if( is_whitespace( buf[i2] ) ) {
+ buf[i2 - i] = 0;
+ return;
+ }
+ buf[i2 - i] = buf[i2];
+ }
+}
+
+inline void init_cfg_node( CFG_NODE* node, const char* name, CFG_NODE* parent, U8 type ) {
+ memset( node->name, 0, sizeof(node->name) );
+ strcpy( node->name, name );
+ node->name[sizeof(node->name) - 1] = '\0';
+ node->parent = parent;
+ if( parent )
+ ( (CFG_SECTION*)parent )->children.push( node );
+ node->type = type;
+}
+
+CFG_SECTION* cfg_section_new( const char* name, CFG_NODE* parent ) {
+ CFG_SECTION* section = new CFG_SECTION;
+ init_cfg_node( (CFG_NODE*)section, name, parent, CFGT_SECTION );
+ return section;
+}
+
+CFG_BYTES* cfg_bytes( const char* name, CFG_NODE* parent, U8* bytes, U32 size ) {
+ CFG_BYTES* cfg_bytes = new CFG_BYTES;
+ init_cfg_node( (CFG_NODE*)cfg_bytes, name, parent, CFGT_BYTES );
+ cfg_bytes->bytes = bytes;
+ cfg_bytes->size = size;
+ return cfg_bytes;
+}
+
+CFG_STR* cfg_str( const char* name, CFG_NODE* parent, const char* str, U32 len ) {
+ CFG_STR* cfg_str = new CFG_STR;
+ init_cfg_node( (CFG_NODE*)cfg_str, name, parent, CFGT_STR );
+ cfg_str->str = (char*)malloc( len + 1 );
+ strcpy( cfg_str->str, str );
+ cfg_str->str[len] = '\0';
+ cfg_str->len = len;
+ return cfg_str;
+}
+
+CFG_INT* cfg_int( const char* name, CFG_NODE* parent, I32 value ) {
+ CFG_INT* cfg_int = new CFG_INT;
+ init_cfg_node( (CFG_NODE*)cfg_int, name, parent, CFGT_INT );
+ cfg_int->value = value;
+ return cfg_int;
+}
+
+CFG_FLOAT* cfg_float( const char* name, CFG_NODE* parent, F32 value) {
+ CFG_FLOAT* cfg_float = new CFG_FLOAT;
+ init_cfg_node( (CFG_NODE*)cfg_float, name, parent, CFGT_FLOAT );
+ cfg_float->value = value;
+ return cfg_float;
+}
+
+CFG_VEC2* cfg_vec2( const char* name, CFG_NODE* parent, VEC2 value ) {
+ CFG_VEC2* cfg_vec2 = new CFG_VEC2;
+ init_cfg_node( (CFG_NODE*)cfg_vec2, name, parent, CFGT_VEC2 );
+ cfg_vec2->value = value;
+ return cfg_vec2;
+}
+
+CFG_VEC3* cfg_vec3( const char* name, CFG_NODE* parent, VEC3 value ) {
+ CFG_VEC3* cfg_vec3 = new CFG_VEC3;
+ init_cfg_node( (CFG_NODE*)cfg_vec3, name, parent, CFGT_VEC3 );
+ cfg_vec3->value = value;
+ return cfg_vec3;
+}
+
+CFG_CLR* cfg_clr( const char* name, CFG_NODE* parent, CLR value ) {
+ CFG_CLR* cfg_clr = new CFG_CLR;
+ init_cfg_node( (CFG_NODE*)cfg_clr, name, parent, CFGT_CLR );
+ cfg_clr->value = value;
+ return cfg_clr;
+}
+
+CFG_SECTION* cfg_section( CFG_SECTION* parent, const char* name ) {
+ for( I32 i = 0; i < parent->children.size; ++i ) {
+ CFG_NODE* n = parent->children[i];
+ if( n->type == CFGT_SECTION && strcmp( n->name, name ) == 0 )
+ return (CFG_SECTION*)n;
+ }
+
+ return 0;
+}
+
+CFG_BYTES* cfg_bytes( CFG_SECTION* parent, const char* name ) {
+ for( I32 i = 0; i < parent->children.size; ++i ) {
+ CFG_NODE* n = parent->children[i];
+ if( n->type == CFGT_BYTES && strcmp( n->name, name ) == 0 )
+ return (CFG_BYTES*)n;
+ }
+
+ return 0;
+}
+
+CFG_STR* cfg_str( CFG_SECTION* parent, const char* name ) {
+ for( I32 i = 0; i < parent->children.size; ++i ) {
+ CFG_NODE* n = parent->children[i];
+ if( n->type == CFGT_STR && strcmp( n->name, name ) == 0 )
+ return (CFG_STR*)n;
+ }
+
+ return 0;
+}
+
+CFG_INT* cfg_int( CFG_SECTION* parent, const char* name ) {
+ for( I32 i = 0; i < parent->children.size; ++i ) {
+ CFG_NODE* n = parent->children[i];
+ if( n->type == CFGT_INT && strcmp( n->name, name ) == 0 )
+ return (CFG_INT*)n;
+ }
+
+ return 0;
+}
+
+CFG_FLOAT* cfg_float( CFG_SECTION* parent, const char* name ) {
+ for( I32 i = 0; i < parent->children.size; ++i ) {
+ CFG_NODE* n = parent->children[i];
+ if( n->type == CFGT_FLOAT && strcmp( n->name, name ) == 0 )
+ return (CFG_FLOAT*)n;
+ }
+ return 0;
+}
+
+CFG_VEC2* cfg_vec2( CFG_SECTION* parent, const char* name ) {
+ for( I32 i = 0; i < parent->children.size; ++i ) {
+ CFG_NODE* n = parent->children[i];
+ if( n->type == CFGT_VEC2 && strcmp( n->name, name ) == 0 )
+ return (CFG_VEC2*)n;
+ }
+ return 0;
+}
+
+CFG_VEC3* cfg_vec3( CFG_SECTION* parent, const char* name ) {
+ for( I32 i = 0; i < parent->children.size; ++i ) {
+ CFG_NODE* n = parent->children[i];
+ if( n->type == CFGT_VEC3 && strcmp( n->name, name ) == 0 )
+ return (CFG_VEC3*)n;
+ }
+ return 0;
+}
+
+CFG_CLR* cfg_clr( CFG_SECTION* parent, const char* name ) {
+ for( I32 i = 0; i < parent->children.size; ++i ) {
+ CFG_NODE* n = parent->children[i];
+ if( n->type == CFGT_CLR && strcmp( n->name, name ) == 0 )
+ return (CFG_CLR*)n;
+ }
+ return 0;
+}
+
+void parse_section( CFG_PARSER* parser, CFG_SECTION* current_section ) {
+ char line[8192];
+ char* token;
+ char* next_token;
+
+ while( fgets( line, sizeof(line), parser->file ) ) {
+ token = strtok( line, " \t\n" );
+ if( !token ) continue;
+ if( strcmp( token, "{" ) == 0 )
+ continue;
+ else if( strcmp( token, "}" ) == 0 ) {
+ return;
+ } else if( strcmp( token, cfg_types[CFGT_SECTION].def ) == 0 ) {
+ next_token = strtok( NULL, " \t\n" );
+ char sectname[64];
+ strcpy( sectname, next_token );
+ trim_whitespace( sectname );
+
+ CFG_SECTION* new_section = cfg_section_new( sectname, (CFG_NODE*)current_section );
+ strtok( NULL, " \t\n" );
+ parse_section( parser, new_section );
+ } else {
+ char name[64];
+ strcpy( name, token );
+ trim_whitespace( name );
+
+ token = strtok( NULL, "=[" );
+ if( !token )
+ continue;
+
+ char varname[64];
+ strcpy( varname, token );
+ trim_whitespace( varname );
+
+ for( I32 i = 0; i < sizeof(cfg_types) / sizeof(CFG_TYPE); ++i ) {
+ const CFG_TYPE* fn = &cfg_types[i];
+ if( strncmp( name, fn->def, strlen( fn->def ) ) == 0 ) {
+ fn->parser( parser, current_section, varname );
+ break;
+ }
+ }
+
+ if( parser->iserr ) {
+ dlog( "parse_section() : %s parse error:\n - %s\n", name, parser->err );
+ return;
+ }
+ }
+
+ parser->linen++;
+ }
+}
+
+CFG_SECTION* cfg_load( const char* path ) {
+ FILE* f = fopen( path, "rb" );
+ if( !f )
+ return 0;
+
+ CFG_PARSER p;
+ p.iserr = 0;
+ p.file = f;
+ p.linen = 0;
+ p.root = cfg_section_new( "root", 0 );
+ parse_section( &p, p.root );
+
+ fclose( f );
+ return p.root;
+}
+
+STAT cfg_save( CFG_SECTION* root, const char* path ) {
+ FILE* f = fopen( path, "w" );
+ if( !f )
+ return STAT_ERR;
+
+ char* buf = (char*)malloc( 999999 );
+ buf[0] = 0;
+ CFG_SERIALIZER s;
+ s.tabc = 0;
+ s.f = f;
+
+ cfg_serialize_section( &s, (CFG_NODE*)root, buf );
+ U32 len = strlen( buf );
+
+ fwrite( buf, 1, len, f );
+ free( buf );
+
+ fclose( f );
+ return STAT_OK;
+}
diff --git a/src/util/config/parsers.cpp b/src/util/config/parsers.cpp
new file mode 100644
index 0000000..1dbfc07
--- /dev/null
+++ b/src/util/config/parsers.cpp
@@ -0,0 +1,156 @@
+#include <cstdarg>
+#include <cstdio>
+
+#include "../config.h"
+
+U8* parse_hex_string( CFG_PARSER* p, const char* hex_str, U8* bytes, U32* size ) {
+ *size = strlen( hex_str ) / 2;
+ for( U32 i = 0; i < *size; ++i ) {
+ U32 buf;
+ I32 c = sscanf( hex_str + 2 * i, "%02x", &buf );
+ if( c != 1 ) {
+ cfg_seterr( p, "Invalid hex string: %s", hex_str );
+ free( bytes );
+ return NULL;
+ }
+
+ bytes[i] = ( buf & 0xff );
+ }
+
+ return bytes;
+}
+
+STAT cfg_parser_bytes( CFG_PARSER* p, CFG_SECTION* s, char* name ) {
+ char* sizep = strtok( NULL, "]" );
+ if( !sizep ) {
+ cfg_seterr( p, "cfg_parser_bytes() : %s: invalid size [L:%d]", name, p->linen );
+ return STAT_ERR;
+ }
+
+ U32 size = atoi( sizep );
+ strtok( NULL, "=" );
+ strtok( NULL, "\"" );
+ char* hex_value = strtok( NULL, "\"" );
+ if( !hex_value ) {
+ cfg_seterr( p, "cfg_parser_bytes() : %s: invalid value (size: %d) [L:%d]", name, size, p->linen );
+ return STAT_ERR;
+ }
+
+ U32 totals;
+ U8* bytes = (U8*)malloc( size );
+ parse_hex_string( p, hex_value, bytes, &totals );
+ cfg_bytes( name, (CFG_NODE*)s, bytes, size );
+
+ return STAT_OK;
+}
+
+STAT cfg_parser_str( CFG_PARSER* p, CFG_SECTION* s, char* name ) {
+ char* sizep = strtok( NULL, "]" );
+ if( !sizep ) {
+ cfg_seterr( p, "cfg_parser_str() : %s: invalid size [L:%d]", name, p->linen );
+ return STAT_ERR;
+ }
+
+ U32 size = atoi( sizep );
+ strtok( NULL, "=" );
+ strtok( NULL, "\"" );
+ char* v = strtok( NULL, "\"" );
+ if( !v ) {
+ cfg_seterr( p, "cfg_parser_str() : %s: incorrect value [L:%d]", name, p->linen );
+ return STAT_ERR;
+ }
+
+ cfg_str( name, (CFG_NODE*)s, v, size );
+
+ return STAT_OK;
+}
+
+STAT cfg_parser_int( CFG_PARSER* p, CFG_SECTION* s, char* name ) {
+ char* vp = strtok( NULL, ";" );
+ if( !vp ) {
+ cfg_seterr( p, "cfg_parser_int() : %s: incorrect value [L:%d]", name, p->linen );
+ return STAT_ERR;
+ }
+
+ I32 value = atoi( vp );
+ cfg_int( name, (CFG_NODE*)s, value );
+
+ return STAT_OK;
+}
+
+STAT cfg_parser_float( CFG_PARSER* p, CFG_SECTION* s, char* name ) {
+ char* vp = strtok( NULL, ";" );
+ if( !vp ) {
+ cfg_seterr( p, "cfg_parser_float() : %s: incorrect value [L:%d]", name, p->linen );
+ return STAT_ERR;
+ }
+
+ F32 value = atof( vp );
+ cfg_float( name, (CFG_NODE*)s, value );
+
+ return STAT_OK;
+}
+
+STAT cfg_parser_vec2( CFG_PARSER *p, CFG_SECTION* s, char* name ) {
+ char* vp = strtok( NULL, "{" );
+ const char* errstr = "cfg_parser_vec2() : %s: incorrect value [L:%d]";
+
+ if( !vp ) { cfg_seterr( p, errstr, name, p->linen ); return STAT_ERR; }
+ vp = strtok( NULL, "," );
+ if( !vp ) { cfg_seterr( p, errstr, name, p->linen ); return STAT_ERR; }
+ F32 v1 = atof( vp );
+ vp = strtok( NULL, "}" );
+ if( !vp ) { cfg_seterr( p, errstr, name, p->linen ); return STAT_ERR; }
+ F32 v2 = atof( vp );
+
+ VEC2 v = { v1, v2 };
+ cfg_vec2( name, (CFG_NODE*)s, v );
+
+ return STAT_OK;
+}
+
+STAT cfg_parser_vec3( CFG_PARSER *parser, CFG_SECTION *section, char *name ) {
+ char *vp = strtok( NULL, "{" );
+ const char* errstr = "cfg_parser_vec3() : %s: incorrect value [L:%d]";
+
+ if( !vp ) { cfg_seterr( parser, errstr, name, parser->linen ); return STAT_ERR; }
+ vp = strtok( NULL, "," );
+ if( !vp ) { cfg_seterr( parser, errstr, name, parser->linen ); return STAT_ERR; }
+ F32 x = atof( vp );
+ vp = strtok( NULL, "," );
+ if( !vp ) { cfg_seterr( parser, errstr, name, parser->linen ); return STAT_ERR; }
+ F32 y = atof( vp );
+ vp = strtok( NULL, "}" );
+ if( !vp ) { cfg_seterr( parser, errstr, name, parser->linen ); return STAT_ERR; }
+ F32 z = atof( vp );
+ VEC3 v = { x, y, z };
+ cfg_vec3( name, (CFG_NODE*)section, v );
+
+ return STAT_OK;
+}
+
+STAT cfg_parser_clr( CFG_PARSER* parser, CFG_SECTION* section, char* name ) {
+ const char* vp = strtok( NULL, "{" );
+ const char* errstr = "cfg_parser_clr() : %s: incorrect value [L:%d]";
+ if( !vp ) {
+ cfg_seterr( parser, errstr, name, parser->linen );
+ return STAT_ERR;
+ }
+
+ vp = strtok( NULL, "," );
+ if( !vp ) { cfg_seterr( parser, errstr, name, parser->linen ); return STAT_ERR; }
+ F32 r = atof( vp );
+ vp = strtok( NULL, "," );
+ if( !vp ) { cfg_seterr( parser, errstr, name, parser->linen ); return STAT_ERR; }
+ F32 g = atof( vp );
+ vp = strtok( NULL, "," );
+ if( !vp ) { cfg_seterr( parser, errstr, name, parser->linen ); return STAT_ERR; }
+ F32 b = atof( vp );
+ vp = strtok( NULL, "}" );
+ if( !vp ) { cfg_seterr( parser, errstr, name, parser->linen ); return STAT_ERR; }
+ F32 a = atof( vp );
+
+ CLR c = { r, g, b, a };
+ cfg_clr( name, (CFG_NODE*)section, c );
+ return STAT_OK;
+}
diff --git a/src/util/config/serializers.cpp b/src/util/config/serializers.cpp
new file mode 100644
index 0000000..01f416c
--- /dev/null
+++ b/src/util/config/serializers.cpp
@@ -0,0 +1,109 @@
+#include <cstdarg>
+#include <cstdio>
+
+#include "../config.h"
+
+void serialize_node( CFG_SERIALIZER* s, CFG_NODE* n, char* buf ) {
+ U32 len = strlen( buf );
+ for( U32 i = 0; i < s->tabc * 2; ++i ) buf[i + len] = ' ';
+ buf[len + s->tabc * 2] = 0;
+ sprintf( buf, "%s%s %s", buf, cfg_types[n->type].def, n->name );
+}
+
+void cfg_serialize_section( CFG_SERIALIZER* s, CFG_NODE *n, char *buf ) {
+ CFG_SECTION* sec = (CFG_SECTION*)n;
+ if( sec->parent ) {
+ serialize_node( s, n, buf );
+ strcat( buf, " {\n" );
+ s->tabc++;
+ }
+
+ char line[8192];
+ for( U32 i = 0; i < sec->children.size; ++i ) {
+ line[0] = 0;
+ CFG_NODE* c = sec->children[i];
+ if( c->type == CFGT_SECTION ) {
+ cfg_serialize_section( s, c, buf );
+ continue;
+ }
+
+ cfg_types[c->type].serializer( s, c, line );
+ strcat( buf, line );
+ strcat( buf, "\n" );
+ }
+
+ if( sec->parent ) {
+ s->tabc--;
+ char tabbuf[512] = { 0 };
+ for( U32 i = 0 ; i < s->tabc * 2; ++i )
+ tabbuf[i] = ' ';
+ tabbuf[s->tabc * 2] = 0;
+ strcat( buf, tabbuf );
+ strcat( buf, "}" );
+ if( sec->parent )
+ strcat( buf, "\n" );
+ }
+}
+
+void cfg_serialize_bytes( CFG_SERIALIZER* s, CFG_NODE* n, char* buf ) {
+ CFG_BYTES* b = (CFG_BYTES*)n;
+ U32 size = b->size;
+ U8* bytes = b->bytes;
+
+ serialize_node( s, n, buf );
+ sprintf( buf, "%s[%d] = \"", buf, size );
+ U32 len = strlen( buf );
+ for( U32 i = 0; i < size; ++i ) {
+ sprintf( buf + len + i * 2, "%02X", bytes[i] );
+ }
+
+ sprintf( buf, "%s%s", buf, "\";" );
+}
+
+void cfg_serialize_str( CFG_SERIALIZER* s, CFG_NODE* n, char *buf ) {
+ CFG_STR* sn = (CFG_STR*)n;
+ char* str = sn->str;
+
+ serialize_node( s, n, buf );
+ sprintf( buf, "%s[%d] = \"%s\";", buf, sn->len, str );
+}
+
+void cfg_serialize_int( CFG_SERIALIZER* s, CFG_NODE* n, char *buf ) {
+ CFG_INT* i = (CFG_INT*)n;
+ I32 ival = i->value;
+
+ serialize_node( s, n, buf );
+ sprintf( buf, "%s = %d;", buf, ival );
+}
+
+void cfg_serialize_float( CFG_SERIALIZER* s, CFG_NODE* n, char *buf ) {
+ CFG_FLOAT* f = (CFG_FLOAT*)n;
+ F32 fval = f->value;
+
+ serialize_node( s, n, buf );
+ sprintf( buf, "%s = %g;", buf, fval );
+}
+
+void cfg_serialize_vec2( CFG_SERIALIZER* s, CFG_NODE* n, char *buf ) {
+ CFG_VEC2* v = (CFG_VEC2*)n;
+ VEC2 val = v->value;
+
+ serialize_node( s, n, buf );
+ sprintf( buf, "%s = { %g, %g };", buf, val.x, val.y );
+}
+
+void cfg_serialize_vec3( CFG_SERIALIZER* s, CFG_NODE* n, char *buf ) {
+ CFG_VEC3* v = (CFG_VEC3*)n;
+ VEC3 val = v->value;
+
+ serialize_node( s, n, buf );
+ sprintf( buf, "%s = { %g, %g, %g };", buf, val.x, val.y, val.z );
+}
+
+void cfg_serialize_clr( CFG_SERIALIZER* s, CFG_NODE* n, char *buf ) {
+ CFG_CLR* v = (CFG_CLR*)n;
+ CLR val = v->value;
+
+ serialize_node( s, n, buf );
+ sprintf( buf, "%s = { %g, %g, %g, %g };", buf, val.r, val.g, val.b, val.a );
+}
diff --git a/src/util/file.h b/src/util/file.h
new file mode 100644
index 0000000..e19a390
--- /dev/null
+++ b/src/util/file.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <cstdlib>
+#include <cstring>
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "typedef.h"
+#include "allocator.h"
+
+inline void* file_read( const char* file ) {
+ FILE* f = fopen( file, "rb" );
+ if( !f )
+ return 0;
+
+ defer( fclose( f ) );
+
+ fseek( f, 0, SEEK_END );
+ U64 size = ftell( f );
+ rewind( f );
+ if( !size )
+ return 0;
+
+ void* block = malloc( size + 1 );
+ fread( block, size, 1, f );
+
+ ( (U8*)block )[size] = 0;
+ return block;
+}
+
+inline void file_write( const char* file, U8* data, U32 size ) {
+ FILE* f = fopen( file, "wb" );
+ if( !f )
+ return;
+
+ fwrite( data, size, 1, f );
+ fclose( f );
+}
+
+inline const char* file_path_last_of( const char* path ) {
+ const char* last_slash = strrchr( path, '/' );
+ return last_slash? last_slash + 1 : path;
+}
+
+struct FILE_ENTRY {
+ char name[256];
+ U8 dir;
+};
+
+inline LIST<FILE_ENTRY> dir_get_entries( const char* path ) {
+ LIST<FILE_ENTRY> entries;
+
+ DIR* dir = opendir( path );
+ if( !dir )
+ return entries;
+
+ struct dirent* entry;
+ struct stat file_stat;
+ char full_path[512];
+
+ while( !!( entry = readdir( dir ) ) ) {
+ if( !strcmp( entry->d_name, "." ) || !strcmp( entry->d_name, ".." ) )
+ continue;
+
+ snprintf( full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
+ if( stat( full_path, &file_stat ) )
+ continue;
+
+ FILE_ENTRY file_entry;
+ strncpy( file_entry.name, entry->d_name, sizeof(file_entry.name) - 1 );
+ file_entry.name[sizeof(file_entry.name) - 1] = '\0';
+ file_entry.dir = S_ISDIR( file_stat.st_mode ) ? 1 : 0;
+ entries.push( file_entry );
+ }
+
+ closedir( dir );
+ return entries;
+}
diff --git a/src/util/fnv.h b/src/util/fnv.h
new file mode 100644
index 0000000..534063d
--- /dev/null
+++ b/src/util/fnv.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "typedef.h"
+
+typedef U32 FNV1A;
+
+enum : FNV1A {
+ FNV1A_PRIME = 0x1000193,
+ FNV1A_BASIS = 0x811C9DC5
+};
+
+inline constexpr U32 strlen_ct( const char* str ) {
+ U32 s = 0;
+ for( ; !!str[s]; ++s );
+
+ return s;
+}
+
+inline constexpr FNV1A fnv1a( const U8* data, const U32 size ) {
+ FNV1A out = FNV1A_BASIS;
+
+ for( U32 i = 0; i < size; ++i )
+ out = ( out ^ data[i] ) * FNV1A_PRIME;
+
+ return out;
+}
+
+inline constexpr FNV1A fnv1a( const char* str ) {
+ U32 len = strlen_ct( str );
+
+ return fnv1a( (const U8*)str, len );
+}
diff --git a/src/util/input.cpp b/src/util/input.cpp
new file mode 100644
index 0000000..4a786aa
--- /dev/null
+++ b/src/util/input.cpp
@@ -0,0 +1,74 @@
+#include "input.h"
+#include "SDL_events.h"
+
+INPUT_DATA input;
+
+void input_on_event( SDL_Event* e ) {
+ input.on_input.each( fn( ON_INPUT_FN* func ) { (*func)( e ); } );
+
+ switch( e->type ) {
+ case SDL_MOUSEBUTTONDOWN: {
+ switch( e->button.button ) {
+ case 1: input.mouse.left = 1; break;
+ case 2: input.mouse.middle = 1; break;
+ case 3: input.mouse.right = 1; break;
+ }
+ } break;
+ case SDL_MOUSEBUTTONUP: {
+ switch( e->button.button ) {
+ case 1: input.mouse.left = 0; break;
+ case 2: input.mouse.middle = 0; break;
+ case 3: input.mouse.right = 0; break;
+ }
+ } break;
+ case SDL_MOUSEWHEEL: {
+ input.mouse.wheel = e->wheel.y;
+ } break;
+ case SDL_MOUSEMOTION: {
+ input.mouse.pos.x = (F32)e->motion.x;
+ input.mouse.pos.y = (F32)e->motion.y;
+ } break;
+ case SDL_KEYDOWN: {
+ input.keys[e->key.keysym.sym & 0xff] = 1;
+ } break;
+ case SDL_KEYUP: {
+ input.keys[e->key.keysym.sym & 0xff] = 0;
+ } break;
+ }
+}
+
+
+void input_on_mouse( I32 type, I32 x, I32 y ) {
+ if( type == MOUSEEV_MOVE ) {
+ input.mouse.pos.x = (F32)x;
+ input.mouse.pos.y = (F32)y;
+ }
+
+ if( type == MOUSEEV_DOWN ) {
+ I32 button = x;
+ if( button == MOUSE_LEFT )
+ input.mouse.left = 1;
+ if( button == MOUSE_RIGHT )
+ input.mouse.right = 1;
+ if( button == MOUSE_MIDDLE )
+ input.mouse.middle = 1;
+ if( button == MOUSE_WHEEL )
+ input.mouse.wheel = -1;
+ }
+
+ if( type == MOUSEEV_UP ) {
+ I32 button = x;
+ if( button == MOUSE_LEFT )
+ input.mouse.left = 0;
+ if( button == MOUSE_RIGHT )
+ input.mouse.right = 0;
+ if( button == MOUSE_MIDDLE )
+ input.mouse.middle = 0;
+ if( button == MOUSE_WHEEL )
+ input.mouse.wheel = 1;
+ }
+}
+
+void input_frame_end() {
+ input.mouse.wheel = 0;
+}
diff --git a/src/util/input.h b/src/util/input.h
new file mode 100644
index 0000000..e174fe6
--- /dev/null
+++ b/src/util/input.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <SDL_events.h>
+#include <functional>
+#include "vector.h"
+#include "allocator.h"
+
+const U32 MOUSEEV_MOVE = 0x0;
+const U32 MOUSEEV_DOWN = 0x1;
+const U32 MOUSEEV_UP = 0x2;
+
+const U32 MOUSE_LEFT = 0x1;
+const U32 MOUSE_RIGHT = 0x2;
+const U32 MOUSE_MIDDLE = 0x3;
+const U32 MOUSE_WHEEL = 0x4;
+
+struct MOUSE_DATA {
+ VEC2 pos;
+ U8 left;
+ U8 right;
+ U8 middle;
+ U8 wheel;
+};
+
+using ON_INPUT_FN = std::function<void( SDL_Event* )>;
+
+struct INPUT_DATA {
+ MOUSE_DATA mouse;
+ U8 keys[0xff];
+
+ LIST<ON_INPUT_FN> on_input;
+};
+
+extern INPUT_DATA input;
+
+extern void input_frame_end();
+extern void input_on_event( SDL_Event* e );
+extern void input_on_mouse( I32 type, I32 x, I32 y );
+extern void input_is_key_down( U32 key );
+
+#define kb_down( key ) input_is_key_down( key )
diff --git a/src/util/math.cpp b/src/util/math.cpp
new file mode 100644
index 0000000..4ab3f2b
--- /dev/null
+++ b/src/util/math.cpp
@@ -0,0 +1,26 @@
+#include "math.h"
+
+#include "../render/gl_3d.h"
+
+VEC2 m_screen_transform( const VEC3 &world ) {
+ GL_DATA* gl = gl_instance();
+ MAT4* gl_proj_matrix = gl->proj_matrix;
+ VEC2 screen = gl->viewport_start;
+ VEC3 clip;
+
+ clip.x = world.x * gl_proj_matrix->m[0][0] + world.y * gl_proj_matrix->m[1][0] + world.z * gl_proj_matrix->m[2][0] + gl_proj_matrix->m[3][0];
+ clip.y = world.x * gl_proj_matrix->m[0][1] + world.y * gl_proj_matrix->m[1][1] + world.z * gl_proj_matrix->m[2][1] + gl_proj_matrix->m[3][1];
+ clip.z = world.x * gl_proj_matrix->m[0][3] + world.y * gl_proj_matrix->m[1][3] + world.z * gl_proj_matrix->m[2][3] + gl_proj_matrix->m[3][3];
+
+ if( clip.z <= 0.f ) {
+ screen.x = screen.y = SCREEN_INVALID_POS;
+ }
+
+ F32 ndcx = clip.x / clip.z;
+ F32 ndcy = clip.y / clip.z;
+
+ screen.x += ( ndcx * .5f + .5f ) * (F32)gl->viewport_dim.x;
+ screen.y += ( 1.f - ( ndcy * .5f + .5f ) ) * (F32)gl->viewport_dim.y;
+
+ return screen;
+}
diff --git a/src/util/math.h b/src/util/math.h
new file mode 100644
index 0000000..add705a
--- /dev/null
+++ b/src/util/math.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <math.h>
+#include "vector.h"
+
+inline F32 m_deg2rad( F32 deg ) { return deg * PIRAD; }
+inline F32 m_rad2deg( F32 rad ) { return rad * RADPI; }
+
+inline VEC2 m_radial_offset( F32 degrees, F32 distance ) {
+ F32 rad = m_deg2rad( degrees );
+ F32 x = cos( rad );
+ F32 y = sin( rad );
+
+ VEC2 ret = { x * distance, y * distance };
+ return ret;
+}
+
+inline F32 m_dist_line_to_point( VEC2 p1, VEC2 p2, VEC2 p ) {
+ if( vec_dist( p1, p2 ) < 0.001f )
+ return vec_dist( p1, p );
+
+ F32 a = p2.y - p1.y;
+ F32 b = p1.x - p2.x;
+ F32 c = p2.x * p1.y - p1.x * p2.y;
+
+ F32 d = a * p.x + b * p.y + c;
+ F32 n = sqrtf( a * a + b * b );
+
+ F32 dot1 = (p.x - p1.x) * (p2.x - p1.x) + (p.y - p1.y) * (p2.y - p1.y);
+ F32 dot2 = (p.x - p2.x) * (p1.x - p2.x) + (p.y - p2.y) * (p1.y - p2.y);
+
+ if( dot1 < 0 ) return vec_dist( p, p1 );
+ if( dot2 < 0 ) return vec_dist( p, p2 );
+
+ return fabsf( d ) / n;
+}
+
+inline U8 m_point_in_polygon( VEC2 p, VEC2* points, U32 pointc, U32 point_stride = sizeof(VEC2) ) {
+ U8 in = 0;
+ for( U32 i = 0, j = pointc - 1; i < pointc; j = i++ ) {
+ U8* pointarr = (U8*)points;
+
+ VEC2* pi = (VEC2*)( pointarr + i * point_stride );
+ VEC2* pj = (VEC2*)( pointarr + j * point_stride );
+
+ if( ( pi->y > p.y ) != ( pj->y > p.y ) ) {
+ if( ( p.x < ( pj->x - pi->x ) * ( p.y - pi->y ) / ( pj->y - pi->y ) + pi->x ) )
+ in = !in;
+ }
+ }
+
+ return in;
+}
+
+inline F32 m_snap_to_grid( F32 x, F32 grid ) {
+ return x - copysignf( fmodf( fabsf( x ), grid ), x );
+}
+
+template <typename T>
+inline T m_min( T a, T b ) {
+ return a < b ? a : b;
+}
+
+template <typename T>
+inline T m_max( T a, T b ) {
+ return a > b ? a : b;
+}
+
+template <typename T>
+inline T m_clamp( T x, T a, T b ) {
+ return m_min( m_max( x, a ), b );
+}
+
+extern VEC2 m_screen_transform( const VEC3& world );
diff --git a/src/util/matrix.h b/src/util/matrix.h
new file mode 100644
index 0000000..6657323
--- /dev/null
+++ b/src/util/matrix.h
@@ -0,0 +1,81 @@
+#pragma once
+#include "typedef.h"
+#include "vector.h"
+
+typedef struct {
+ F32 m[4][4];
+} MAT4;
+
+inline void mat4_identity( MAT4* out ) {
+ for( I32 i = 0; i < 16; i++ ) {
+ out->m[0][i] = (i % 5 == 0) ? 1.0f : 0.0f;
+ }
+}
+
+inline void mat4_mul( MAT4* a, MAT4* b, MAT4* out ) {
+ for( I32 row = 0; row < 4; row++ ) {
+ for( I32 col = 0; col < 4; col++ ) {
+ out->m[row][col] = 0.0f;
+ for( I32 k = 0; k < 4; k++ ) {
+ out->m[row][col] += a->m[row][k] * b->m[k][col];
+ }
+ }
+ }
+}
+
+inline void mat4_mul_vec4( MAT4 *m, VEC4 *in, VEC4 *out ) {
+ out->x = m->m[0][0] * in->x + m->m[0][1] * in->y + m->m[0][2] * in->z + m->m[0][3] * in->w;
+ out->y = m->m[1][0] * in->x + m->m[1][1] * in->y + m->m[1][2] * in->z + m->m[1][3] * in->w;
+ out->z = m->m[2][0] * in->x + m->m[2][1] * in->y + m->m[2][2] * in->z + m->m[2][3] * in->w;
+ out->w = m->m[3][0] * in->x + m->m[3][1] * in->y + m->m[3][2] * in->z + m->m[3][3] * in->w;
+}
+
+inline void mat4_perspective( MAT4* out, F32 fovy_radians, F32 aspect, F32 znear, F32 zfar ) {
+ mat4_identity( out );
+ F32 f = 1.f / tanf( fovy_radians * .5f );
+
+ out->m[0][0] = f / aspect;
+ out->m[1][1] = f;
+ out->m[2][2] = (zfar + znear) / (znear - zfar);
+ out->m[2][3] = (2.f * zfar * znear) / (znear - zfar);
+ out->m[3][2] = -1.f;
+ out->m[3][3] = 0.f;
+}
+
+inline void mat4_translation( MAT4* out, F32 tx, F32 ty, F32 tz ) {
+ mat4_identity( out );
+ out->m[0][3] = tx;
+ out->m[1][3] = ty;
+ out->m[2][3] = tz;
+}
+
+inline void mat4_rotation_x( MAT4* out, F32 angle ) {
+ mat4_identity( out );
+ F32 c = cosf( angle );
+ F32 s = sinf( angle );
+ out->m[1][1] = c;
+ out->m[1][2] = -s;
+ out->m[2][1] = s;
+ out->m[2][2] = c;
+}
+
+inline void mat4_rotation_y( MAT4* out, F32 angle ) {
+ mat4_identity( out );
+ F32 c = cosf( angle );
+ F32 s = sinf( angle );
+ out->m[0][0] = c;
+ out->m[0][2] = -s;
+ out->m[2][0] = s;
+ out->m[2][2] = c;
+}
+
+inline void mat4_rotation_z( MAT4* out, F32 angle ) {
+ mat4_identity( out );
+ F32 c = cosf( angle );
+ F32 s = sinf( angle );
+ out->m[0][0] = c;
+ out->m[0][1] = -s;
+ out->m[1][0] = s;
+ out->m[1][1] = c;
+}
+
diff --git a/src/util/screen.h b/src/util/screen.h
new file mode 100644
index 0000000..34effb3
--- /dev/null
+++ b/src/util/screen.h
@@ -0,0 +1,41 @@
+#pragma once
+#include "typedef.h"
+#include "vector.h"
+
+extern I32* canvas;
+
+inline VEC2 s_tl( VEC2 offset = { 0.f, 0.f } ) {
+ return { offset.x, offset.y };
+}
+
+inline VEC2 s_tr( VEC2 offset = { 0.f, 0.f } ) {
+ return { offset.x + (F32)canvas[0], offset.y };
+}
+
+inline VEC2 s_bl( VEC2 offset = { 0.f, 0.f } ) {
+ return { offset.x, offset.y + (F32)canvas[1] };
+}
+
+inline VEC2 s_br( VEC2 offset = { 0.f, 0.f } ) {
+ return { offset.x + (F32)canvas[0], offset.y + (F32)canvas[1] };
+}
+
+inline VEC2 s_c( VEC2 offset = { 0.f, 0.f } ) {
+ return { offset.x + (F32)canvas[0] / 2, offset.y + (F32)canvas[1] / 2 };
+}
+
+inline VEC2 s_tc( VEC2 offset = { 0.f, 0.f } ) {
+ return { offset.x + (F32)canvas[0] / 2, 0 };
+}
+
+inline VEC2 s_bc( VEC2 offset = { 0.f, 0.f } ) {
+ return { offset.x + (F32)canvas[0] / 2, (F32)canvas[1] };
+}
+
+inline VEC2 s_cl( VEC2 offset = { 0.f, 0.f } ) {
+ return { 0, offset.y + (F32)canvas[1] / 2 };
+}
+
+inline VEC2 s_cr( VEC2 offset = { 0.f, 0.f } ) {
+ return { (F32)canvas[0], offset.y + (F32)canvas[1] / 2 };
+}
diff --git a/src/util/stb_image.h b/src/util/stb_image.h
new file mode 100644
index 0000000..5e807a0
--- /dev/null
+++ b/src/util/stb_image.h
@@ -0,0 +1,7987 @@
+/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb
+ no warranty implied; use at your own risk
+
+ Do this:
+ #define STB_IMAGE_IMPLEMENTATION
+ before you include this file in *one* C or C++ file to create the implementation.
+
+ // i.e. it should look like this:
+ #include ...
+ #include ...
+ #include ...
+ #define STB_IMAGE_IMPLEMENTATION
+ #include "stb_image.h"
+
+ You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.
+ And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free
+
+
+ QUICK NOTES:
+ Primarily of interest to game developers and other people who can
+ avoid problematic images and only need the trivial interface
+
+ JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
+ PNG 1/2/4/8/16-bit-per-channel
+
+ TGA (not sure what subset, if a subset)
+ BMP non-1bpp, non-RLE
+ PSD (composited view only, no extra channels, 8/16 bit-per-channel)
+
+ GIF (*comp always reports as 4-channel)
+ HDR (radiance rgbE format)
+ PIC (Softimage PIC)
+ PNM (PPM and PGM binary only)
+
+ Animated GIF still needs a proper API, but here's one way to do it:
+ http://gist.github.com/urraka/685d9a6340b26b830d49
+
+ - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
+ - decode from arbitrary I/O callbacks
+ - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
+
+ Full documentation under "DOCUMENTATION" below.
+
+
+LICENSE
+
+ See end of file for license information.
+
+RECENT REVISION HISTORY:
+
+ 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
+ 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
+ 2.26 (2020-07-13) many minor fixes
+ 2.25 (2020-02-02) fix warnings
+ 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically
+ 2.23 (2019-08-11) fix clang static analysis warning
+ 2.22 (2019-03-04) gif fixes, fix warnings
+ 2.21 (2019-02-25) fix typo in comment
+ 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
+ 2.19 (2018-02-11) fix warning
+ 2.18 (2018-01-30) fix warnings
+ 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings
+ 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes
+ 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC
+ 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
+ 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes
+ 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+ 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
+ RGB-format JPEG; remove white matting in PSD;
+ allocate large structures on the stack;
+ correct channel count for PNG & BMP
+ 2.10 (2016-01-22) avoid warning introduced in 2.09
+ 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
+
+ See end of file for full revision history.
+
+
+ ============================ Contributors =========================
+
+ Image formats Extensions, features
+ Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info)
+ Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info)
+ Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG)
+ Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks)
+ Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG)
+ Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip)
+ Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD)
+ github:urraka (animated gif) Junggon Kim (PNM comments)
+ Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA)
+ socks-the-fox (16-bit PNG)
+ Jeremy Sawicki (handle all ImageNet JPGs)
+ Optimizations & bugfixes Mikhail Morozov (1-bit BMP)
+ Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query)
+ Arseny Kapoulkine Simon Breuss (16-bit PNM)
+ John-Mark Allen
+ Carmelo J Fdez-Aguera
+
+ Bug & warning fixes
+ Marc LeBlanc David Woo Guillaume George Martins Mozeiko
+ Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski
+ Phil Jordan Dave Moore Roy Eltham
+ Hayaki Saito Nathan Reed Won Chun
+ Luke Graham Johan Duparc Nick Verigakis the Horde3D community
+ Thomas Ruf Ronny Chevalier github:rlyeh
+ Janez Zemva John Bartholomew Michal Cichon github:romigrou
+ Jonathan Blow Ken Hamada Tero Hanninen github:svdijk
+ Eugene Golushkov Laurent Gomila Cort Stratton github:snagar
+ Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex
+ Cass Everitt Ryamond Barbiero github:grim210
+ Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw
+ Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus
+ Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo
+ Julian Raschke Gregory Mullen Christian Floisand github:darealshinji
+ Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007
+ Brad Weinberger Matvey Cherevko github:mosra
+ Luca Sas Alexander Veselov Zack Middleton [reserved]
+ Ryan C. Gordon [reserved] [reserved]
+ DO NOT ADD YOUR NAME HERE
+
+ Jacko Dirks
+
+ To add your name to the credits, pick a random blank space in the middle and fill it.
+ 80% of merge conflicts on stb PRs are due to people adding their name at the end
+ of the credits.
+*/
+
+#ifndef STBI_INCLUDE_STB_IMAGE_H
+#define STBI_INCLUDE_STB_IMAGE_H
+
+// DOCUMENTATION
+//
+// Limitations:
+// - no 12-bit-per-channel JPEG
+// - no JPEGs with arithmetic coding
+// - GIF always returns *comp=4
+//
+// Basic usage (see HDR discussion below for HDR usage):
+// int x,y,n;
+// unsigned char *data = stbi_load(filename, &x, &y, &n, 0);
+// // ... process data if not NULL ...
+// // ... x = width, y = height, n = # 8-bit components per pixel ...
+// // ... replace '0' with '1'..'4' to force that many components per pixel
+// // ... but 'n' will always be the number that it would have been if you said 0
+// stbi_image_free(data);
+//
+// Standard parameters:
+// int *x -- outputs image width in pixels
+// int *y -- outputs image height in pixels
+// int *channels_in_file -- outputs # of image components in image file
+// int desired_channels -- if non-zero, # of image components requested in result
+//
+// The return value from an image loader is an 'unsigned char *' which points
+// to the pixel data, or NULL on an allocation failure or if the image is
+// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels,
+// with each pixel consisting of N interleaved 8-bit components; the first
+// pixel pointed to is top-left-most in the image. There is no padding between
+// image scanlines or between pixels, regardless of format. The number of
+// components N is 'desired_channels' if desired_channels is non-zero, or
+// *channels_in_file otherwise. If desired_channels is non-zero,
+// *channels_in_file has the number of components that _would_ have been
+// output otherwise. E.g. if you set desired_channels to 4, you will always
+// get RGBA output, but you can check *channels_in_file to see if it's trivially
+// opaque because e.g. there were only 3 channels in the source image.
+//
+// An output image with N components has the following components interleaved
+// in this order in each pixel:
+//
+// N=#comp components
+// 1 grey
+// 2 grey, alpha
+// 3 red, green, blue
+// 4 red, green, blue, alpha
+//
+// If image loading fails for any reason, the return value will be NULL,
+// and *x, *y, *channels_in_file will be unchanged. The function
+// stbi_failure_reason() can be queried for an extremely brief, end-user
+// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS
+// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly
+// more user-friendly ones.
+//
+// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.
+//
+// To query the width, height and component count of an image without having to
+// decode the full file, you can use the stbi_info family of functions:
+//
+// int x,y,n,ok;
+// ok = stbi_info(filename, &x, &y, &n);
+// // returns ok=1 and sets x, y, n if image is a supported format,
+// // 0 otherwise.
+//
+// Note that stb_image pervasively uses ints in its public API for sizes,
+// including sizes of memory buffers. This is now part of the API and thus
+// hard to change without causing breakage. As a result, the various image
+// loaders all have certain limits on image size; these differ somewhat
+// by format but generally boil down to either just under 2GB or just under
+// 1GB. When the decoded image would be larger than this, stb_image decoding
+// will fail.
+//
+// Additionally, stb_image will reject image files that have any of their
+// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS,
+// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit,
+// the only way to have an image with such dimensions load correctly
+// is for it to have a rather extreme aspect ratio. Either way, the
+// assumption here is that such larger images are likely to be malformed
+// or malicious. If you do need to load an image with individual dimensions
+// larger than that, and it still fits in the overall size limit, you can
+// #define STBI_MAX_DIMENSIONS on your own to be something larger.
+//
+// ===========================================================================
+//
+// UNICODE:
+//
+// If compiling for Windows and you wish to use Unicode filenames, compile
+// with
+// #define STBI_WINDOWS_UTF8
+// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert
+// Windows wchar_t filenames to utf8.
+//
+// ===========================================================================
+//
+// Philosophy
+//
+// stb libraries are designed with the following priorities:
+//
+// 1. easy to use
+// 2. easy to maintain
+// 3. good performance
+//
+// Sometimes I let "good performance" creep up in priority over "easy to maintain",
+// and for best performance I may provide less-easy-to-use APIs that give higher
+// performance, in addition to the easy-to-use ones. Nevertheless, it's important
+// to keep in mind that from the standpoint of you, a client of this library,
+// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all.
+//
+// Some secondary priorities arise directly from the first two, some of which
+// provide more explicit reasons why performance can't be emphasized.
+//
+// - Portable ("ease of use")
+// - Small source code footprint ("easy to maintain")
+// - No dependencies ("ease of use")
+//
+// ===========================================================================
+//
+// I/O callbacks
+//
+// I/O callbacks allow you to read from arbitrary sources, like packaged
+// files or some other source. Data read from callbacks are processed
+// through a small internal buffer (currently 128 bytes) to try to reduce
+// overhead.
+//
+// The three functions you must define are "read" (reads some bytes of data),
+// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end).
+//
+// ===========================================================================
+//
+// SIMD support
+//
+// The JPEG decoder will try to automatically use SIMD kernels on x86 when
+// supported by the compiler. For ARM Neon support, you must explicitly
+// request it.
+//
+// (The old do-it-yourself SIMD API is no longer supported in the current
+// code.)
+//
+// On x86, SSE2 will automatically be used when available based on a run-time
+// test; if not, the generic C versions are used as a fall-back. On ARM targets,
+// the typical path is to have separate builds for NEON and non-NEON devices
+// (at least this is true for iOS and Android). Therefore, the NEON support is
+// toggled by a build flag: define STBI_NEON to get NEON loops.
+//
+// If for some reason you do not want to use any of SIMD code, or if
+// you have issues compiling it, you can disable it entirely by
+// defining STBI_NO_SIMD.
+//
+// ===========================================================================
+//
+// HDR image support (disable by defining STBI_NO_HDR)
+//
+// stb_image supports loading HDR images in general, and currently the Radiance
+// .HDR file format specifically. You can still load any file through the existing
+// interface; if you attempt to load an HDR file, it will be automatically remapped
+// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;
+// both of these constants can be reconfigured through this interface:
+//
+// stbi_hdr_to_ldr_gamma(2.2f);
+// stbi_hdr_to_ldr_scale(1.0f);
+//
+// (note, do not use _inverse_ constants; stbi_image will invert them
+// appropriately).
+//
+// Additionally, there is a new, parallel interface for loading files as
+// (linear) floats to preserve the full dynamic range:
+//
+// float *data = stbi_loadf(filename, &x, &y, &n, 0);
+//
+// If you load LDR images through this interface, those images will
+// be promoted to floating point values, run through the inverse of
+// constants corresponding to the above:
+//
+// stbi_ldr_to_hdr_scale(1.0f);
+// stbi_ldr_to_hdr_gamma(2.2f);
+//
+// Finally, given a filename (or an open file or memory block--see header
+// file for details) containing image data, you can query for the "most
+// appropriate" interface to use (that is, whether the image is HDR or
+// not), using:
+//
+// stbi_is_hdr(char *filename);
+//
+// ===========================================================================
+//
+// iPhone PNG support:
+//
+// We optionally support converting iPhone-formatted PNGs (which store
+// premultiplied BGRA) back to RGB, even though they're internally encoded
+// differently. To enable this conversion, call
+// stbi_convert_iphone_png_to_rgb(1).
+//
+// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per
+// pixel to remove any premultiplied alpha *only* if the image file explicitly
+// says there's premultiplied data (currently only happens in iPhone images,
+// and only if iPhone convert-to-rgb processing is on).
+//
+// ===========================================================================
+//
+// ADDITIONAL CONFIGURATION
+//
+// - You can suppress implementation of any of the decoders to reduce
+// your code footprint by #defining one or more of the following
+// symbols before creating the implementation.
+//
+// STBI_NO_JPEG
+// STBI_NO_PNG
+// STBI_NO_BMP
+// STBI_NO_PSD
+// STBI_NO_TGA
+// STBI_NO_GIF
+// STBI_NO_HDR
+// STBI_NO_PIC
+// STBI_NO_PNM (.ppm and .pgm)
+//
+// - You can request *only* certain decoders and suppress all other ones
+// (this will be more forward-compatible, as addition of new decoders
+// doesn't require you to disable them explicitly):
+//
+// STBI_ONLY_JPEG
+// STBI_ONLY_PNG
+// STBI_ONLY_BMP
+// STBI_ONLY_PSD
+// STBI_ONLY_TGA
+// STBI_ONLY_GIF
+// STBI_ONLY_HDR
+// STBI_ONLY_PIC
+// STBI_ONLY_PNM (.ppm and .pgm)
+//
+// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still
+// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB
+//
+// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater
+// than that size (in either width or height) without further processing.
+// This is to let programs in the wild set an upper bound to prevent
+// denial-of-service attacks on untrusted data, as one could generate a
+// valid image of gigantic dimensions and force stb_image to allocate a
+// huge block of memory and spend disproportionate time decoding it. By
+// default this is set to (1 << 24), which is 16777216, but that's still
+// very big.
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif // STBI_NO_STDIO
+
+#define STBI_VERSION 1
+
+enum
+{
+ STBI_default = 0, // only used for desired_channels
+
+ STBI_grey = 1,
+ STBI_grey_alpha = 2,
+ STBI_rgb = 3,
+ STBI_rgb_alpha = 4
+};
+
+#include <stdlib.h>
+typedef unsigned char stbi_uc;
+typedef unsigned short stbi_us;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef STBIDEF
+#ifdef STB_IMAGE_STATIC
+#define STBIDEF static
+#else
+#define STBIDEF extern
+#endif
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// PRIMARY API - works on images of any type
+//
+
+//
+// load image by filename, open file, or memory buffer
+//
+
+typedef struct
+{
+ int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read
+ void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
+ int (*eof) (void *user); // returns nonzero if we are at end of file/data
+} stbi_io_callbacks;
+
+////////////////////////////////////
+//
+// 8-bits-per-channel interface
+//
+
+STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels);
+
+#ifndef STBI_NO_STDIO
+STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+// for stbi_load_from_file, file pointer is left pointing immediately after image
+#endif
+
+#ifndef STBI_NO_GIF
+STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
+#endif
+
+#ifdef STBI_WINDOWS_UTF8
+STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
+#endif
+
+////////////////////////////////////
+//
+// 16-bits-per-channel interface
+//
+
+STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);
+
+#ifndef STBI_NO_STDIO
+STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+#endif
+
+////////////////////////////////////
+//
+// float-per-channel interface
+//
+#ifndef STBI_NO_LINEAR
+ STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
+ STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);
+
+ #ifndef STBI_NO_STDIO
+ STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+ STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+ #endif
+#endif
+
+#ifndef STBI_NO_HDR
+ STBIDEF void stbi_hdr_to_ldr_gamma(float gamma);
+ STBIDEF void stbi_hdr_to_ldr_scale(float scale);
+#endif // STBI_NO_HDR
+
+#ifndef STBI_NO_LINEAR
+ STBIDEF void stbi_ldr_to_hdr_gamma(float gamma);
+ STBIDEF void stbi_ldr_to_hdr_scale(float scale);
+#endif // STBI_NO_LINEAR
+
+// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR
+STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);
+STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_is_hdr (char const *filename);
+STBIDEF int stbi_is_hdr_from_file(FILE *f);
+#endif // STBI_NO_STDIO
+
+
+// get a VERY brief reason for failure
+// on most compilers (and ALL modern mainstream compilers) this is threadsafe
+STBIDEF const char *stbi_failure_reason (void);
+
+// free the loaded image -- this is just free()
+STBIDEF void stbi_image_free (void *retval_from_stbi_load);
+
+// get image dimensions & components without fully decoding
+STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
+STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);
+STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len);
+STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user);
+
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp);
+STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp);
+STBIDEF int stbi_is_16_bit (char const *filename);
+STBIDEF int stbi_is_16_bit_from_file(FILE *f);
+#endif
+
+
+
+// for image formats that explicitly notate that they have premultiplied alpha,
+// we just return the colors as stored in the file. set this flag to force
+// unpremultiplication. results are undefined if the unpremultiply overflow.
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
+
+// indicate whether we should process iphone images back to canonical format,
+// or just pass them through "as-is"
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
+
+// flip the image vertically, so the first pixel in the output array is the bottom left
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);
+
+// as above, but only applies to images loaded on the thread that calls the function
+// this function is only available if your compiler supports thread-local variables;
+// calling it will fail to link if your compiler doesn't
+STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply);
+STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert);
+STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip);
+
+// ZLIB client - used by PNG, available for other purposes
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
+STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);
+STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
+STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
+STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+//
+//
+//// end header file /////////////////////////////////////////////////////
+#endif // STBI_INCLUDE_STB_IMAGE_H
+
+#ifdef STB_IMAGE_IMPLEMENTATION
+
+#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \
+ || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \
+ || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \
+ || defined(STBI_ONLY_ZLIB)
+ #ifndef STBI_ONLY_JPEG
+ #define STBI_NO_JPEG
+ #endif
+ #ifndef STBI_ONLY_PNG
+ #define STBI_NO_PNG
+ #endif
+ #ifndef STBI_ONLY_BMP
+ #define STBI_NO_BMP
+ #endif
+ #ifndef STBI_ONLY_PSD
+ #define STBI_NO_PSD
+ #endif
+ #ifndef STBI_ONLY_TGA
+ #define STBI_NO_TGA
+ #endif
+ #ifndef STBI_ONLY_GIF
+ #define STBI_NO_GIF
+ #endif
+ #ifndef STBI_ONLY_HDR
+ #define STBI_NO_HDR
+ #endif
+ #ifndef STBI_ONLY_PIC
+ #define STBI_NO_PIC
+ #endif
+ #ifndef STBI_ONLY_PNM
+ #define STBI_NO_PNM
+ #endif
+#endif
+
+#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB)
+#define STBI_NO_ZLIB
+#endif
+
+
+#include <stdarg.h>
+#include <stddef.h> // ptrdiff_t on osx
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+#include <math.h> // ldexp, pow
+#endif
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif
+
+#ifndef STBI_ASSERT
+#include <assert.h>
+#define STBI_ASSERT(x) assert(x)
+#endif
+
+#ifdef __cplusplus
+#define STBI_EXTERN extern "C"
+#else
+#define STBI_EXTERN extern
+#endif
+
+
+#ifndef _MSC_VER
+ #ifdef __cplusplus
+ #define stbi_inline inline
+ #else
+ #define stbi_inline
+ #endif
+#else
+ #define stbi_inline __forceinline
+#endif
+
+#ifndef STBI_NO_THREAD_LOCALS
+ #if defined(__cplusplus) && __cplusplus >= 201103L
+ #define STBI_THREAD_LOCAL thread_local
+ #elif defined(__GNUC__) && __GNUC__ < 5
+ #define STBI_THREAD_LOCAL __thread
+ #elif defined(_MSC_VER)
+ #define STBI_THREAD_LOCAL __declspec(thread)
+ #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
+ #define STBI_THREAD_LOCAL _Thread_local
+ #endif
+
+ #ifndef STBI_THREAD_LOCAL
+ #if defined(__GNUC__)
+ #define STBI_THREAD_LOCAL __thread
+ #endif
+ #endif
+#endif
+
+#if defined(_MSC_VER) || defined(__SYMBIAN32__)
+typedef unsigned short stbi__uint16;
+typedef signed short stbi__int16;
+typedef unsigned int stbi__uint32;
+typedef signed int stbi__int32;
+#else
+#include <stdint.h>
+typedef uint16_t stbi__uint16;
+typedef int16_t stbi__int16;
+typedef uint32_t stbi__uint32;
+typedef int32_t stbi__int32;
+#endif
+
+// should produce compiler error if size is wrong
+typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];
+
+#ifdef _MSC_VER
+#define STBI_NOTUSED(v) (void)(v)
+#else
+#define STBI_NOTUSED(v) (void)sizeof(v)
+#endif
+
+#ifdef _MSC_VER
+#define STBI_HAS_LROTL
+#endif
+
+#ifdef STBI_HAS_LROTL
+ #define stbi_lrot(x,y) _lrotl(x,y)
+#else
+ #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31)))
+#endif
+
+#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED))
+// ok
+#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED)
+// ok
+#else
+#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)."
+#endif
+
+#ifndef STBI_MALLOC
+#define STBI_MALLOC(sz) malloc(sz)
+#define STBI_REALLOC(p,newsz) realloc(p,newsz)
+#define STBI_FREE(p) free(p)
+#endif
+
+#ifndef STBI_REALLOC_SIZED
+#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz)
+#endif
+
+// x86/x64 detection
+#if defined(__x86_64__) || defined(_M_X64)
+#define STBI__X64_TARGET
+#elif defined(__i386) || defined(_M_IX86)
+#define STBI__X86_TARGET
+#endif
+
+#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)
+// gcc doesn't support sse2 intrinsics unless you compile with -msse2,
+// which in turn means it gets to use SSE2 everywhere. This is unfortunate,
+// but previous attempts to provide the SSE2 functions with runtime
+// detection caused numerous issues. The way architecture extensions are
+// exposed in GCC/Clang is, sadly, not really suited for one-file libs.
+// New behavior: if compiled with -msse2, we use SSE2 without any
+// detection; if not, we don't use it at all.
+#define STBI_NO_SIMD
+#endif
+
+#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD)
+// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET
+//
+// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the
+// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant.
+// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not
+// simultaneously enabling "-mstackrealign".
+//
+// See https://github.com/nothings/stb/issues/81 for more information.
+//
+// So default to no SSE2 on 32-bit MinGW. If you've read this far and added
+// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2.
+#define STBI_NO_SIMD
+#endif
+
+#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))
+#define STBI_SSE2
+#include <emmintrin.h>
+
+#ifdef _MSC_VER
+
+#if _MSC_VER >= 1400 // not VC6
+#include <intrin.h> // __cpuid
+static int stbi__cpuid3(void)
+{
+ int info[4];
+ __cpuid(info,1);
+ return info[3];
+}
+#else
+static int stbi__cpuid3(void)
+{
+ int res;
+ __asm {
+ mov eax,1
+ cpuid
+ mov res,edx
+ }
+ return res;
+}
+#endif
+
+#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
+
+#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
+static int stbi__sse2_available(void)
+{
+ int info3 = stbi__cpuid3();
+ return ((info3 >> 26) & 1) != 0;
+}
+#endif
+
+#else // assume GCC-style if not VC++
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+
+#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
+static int stbi__sse2_available(void)
+{
+ // If we're even attempting to compile this on GCC/Clang, that means
+ // -msse2 is on, which means the compiler is allowed to use SSE2
+ // instructions at will, and so are we.
+ return 1;
+}
+#endif
+
+#endif
+#endif
+
+// ARM NEON
+#if defined(STBI_NO_SIMD) && defined(STBI_NEON)
+#undef STBI_NEON
+#endif
+
+#ifdef STBI_NEON
+#include <arm_neon.h>
+#ifdef _MSC_VER
+#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
+#else
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+#endif
+#endif
+
+#ifndef STBI_SIMD_ALIGN
+#define STBI_SIMD_ALIGN(type, name) type name
+#endif
+
+#ifndef STBI_MAX_DIMENSIONS
+#define STBI_MAX_DIMENSIONS (1 << 24)
+#endif
+
+///////////////////////////////////////////////
+//
+// stbi__context struct and start_xxx functions
+
+// stbi__context structure is our basic context used by all images, so it
+// contains all the IO context, plus some basic image information
+typedef struct
+{
+ stbi__uint32 img_x, img_y;
+ int img_n, img_out_n;
+
+ stbi_io_callbacks io;
+ void *io_user_data;
+
+ int read_from_callbacks;
+ int buflen;
+ stbi_uc buffer_start[128];
+ int callback_already_read;
+
+ stbi_uc *img_buffer, *img_buffer_end;
+ stbi_uc *img_buffer_original, *img_buffer_original_end;
+} stbi__context;
+
+
+static void stbi__refill_buffer(stbi__context *s);
+
+// initialize a memory-decode context
+static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)
+{
+ s->io.read = NULL;
+ s->read_from_callbacks = 0;
+ s->callback_already_read = 0;
+ s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;
+ s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;
+}
+
+// initialize a callback-based context
+static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user)
+{
+ s->io = *c;
+ s->io_user_data = user;
+ s->buflen = sizeof(s->buffer_start);
+ s->read_from_callbacks = 1;
+ s->callback_already_read = 0;
+ s->img_buffer = s->img_buffer_original = s->buffer_start;
+ stbi__refill_buffer(s);
+ s->img_buffer_original_end = s->img_buffer_end;
+}
+
+#ifndef STBI_NO_STDIO
+
+static int stbi__stdio_read(void *user, char *data, int size)
+{
+ return (int) fread(data,1,size,(FILE*) user);
+}
+
+static void stbi__stdio_skip(void *user, int n)
+{
+ int ch;
+ fseek((FILE*) user, n, SEEK_CUR);
+ ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */
+ if (ch != EOF) {
+ ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */
+ }
+}
+
+static int stbi__stdio_eof(void *user)
+{
+ return feof((FILE*) user) || ferror((FILE *) user);
+}
+
+static stbi_io_callbacks stbi__stdio_callbacks =
+{
+ stbi__stdio_read,
+ stbi__stdio_skip,
+ stbi__stdio_eof,
+};
+
+static void stbi__start_file(stbi__context *s, FILE *f)
+{
+ stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f);
+}
+
+//static void stop_file(stbi__context *s) { }
+
+#endif // !STBI_NO_STDIO
+
+static void stbi__rewind(stbi__context *s)
+{
+ // conceptually rewind SHOULD rewind to the beginning of the stream,
+ // but we just rewind to the beginning of the initial buffer, because
+ // we only use it after doing 'test', which only ever looks at at most 92 bytes
+ s->img_buffer = s->img_buffer_original;
+ s->img_buffer_end = s->img_buffer_original_end;
+}
+
+enum
+{
+ STBI_ORDER_RGB,
+ STBI_ORDER_BGR
+};
+
+typedef struct
+{
+ int bits_per_channel;
+ int num_channels;
+ int channel_order;
+} stbi__result_info;
+
+#ifndef STBI_NO_JPEG
+static int stbi__jpeg_test(stbi__context *s);
+static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PNG
+static int stbi__png_test(stbi__context *s);
+static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp);
+static int stbi__png_is16(stbi__context *s);
+#endif
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_test(stbi__context *s);
+static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_TGA
+static int stbi__tga_test(stbi__context *s);
+static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_test(stbi__context *s);
+static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc);
+static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);
+static int stbi__psd_is16(stbi__context *s);
+#endif
+
+#ifndef STBI_NO_HDR
+static int stbi__hdr_test(stbi__context *s);
+static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_test(stbi__context *s);
+static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_GIF
+static int stbi__gif_test(stbi__context *s);
+static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
+static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PNM
+static int stbi__pnm_test(stbi__context *s);
+static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
+static int stbi__pnm_is16(stbi__context *s);
+#endif
+
+static
+#ifdef STBI_THREAD_LOCAL
+STBI_THREAD_LOCAL
+#endif
+const char *stbi__g_failure_reason;
+
+STBIDEF const char *stbi_failure_reason(void)
+{
+ return stbi__g_failure_reason;
+}
+
+#ifndef STBI_NO_FAILURE_STRINGS
+static int stbi__err(const char *str)
+{
+ stbi__g_failure_reason = str;
+ return 0;
+}
+#endif
+
+static void *stbi__malloc(size_t size)
+{
+ return STBI_MALLOC(size);
+}
+
+// stb_image uses ints pervasively, including for offset calculations.
+// therefore the largest decoded image size we can support with the
+// current code, even on 64-bit targets, is INT_MAX. this is not a
+// significant limitation for the intended use case.
+//
+// we do, however, need to make sure our size calculations don't
+// overflow. hence a few helper functions for size calculations that
+// multiply integers together, making sure that they're non-negative
+// and no overflow occurs.
+
+// return 1 if the sum is valid, 0 on overflow.
+// negative terms are considered invalid.
+static int stbi__addsizes_valid(int a, int b)
+{
+ if (b < 0) return 0;
+ // now 0 <= b <= INT_MAX, hence also
+ // 0 <= INT_MAX - b <= INTMAX.
+ // And "a + b <= INT_MAX" (which might overflow) is the
+ // same as a <= INT_MAX - b (no overflow)
+ return a <= INT_MAX - b;
+}
+
+// returns 1 if the product is valid, 0 on overflow.
+// negative factors are considered invalid.
+static int stbi__mul2sizes_valid(int a, int b)
+{
+ if (a < 0 || b < 0) return 0;
+ if (b == 0) return 1; // mul-by-0 is always safe
+ // portable way to check for no overflows in a*b
+ return a <= INT_MAX/b;
+}
+
+#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
+// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow
+static int stbi__mad2sizes_valid(int a, int b, int add)
+{
+ return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add);
+}
+#endif
+
+// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow
+static int stbi__mad3sizes_valid(int a, int b, int c, int add)
+{
+ return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
+ stbi__addsizes_valid(a*b*c, add);
+}
+
+// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM)
+static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)
+{
+ return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
+ stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add);
+}
+#endif
+
+#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
+// mallocs with size overflow checking
+static void *stbi__malloc_mad2(int a, int b, int add)
+{
+ if (!stbi__mad2sizes_valid(a, b, add)) return NULL;
+ return stbi__malloc(a*b + add);
+}
+#endif
+
+static void *stbi__malloc_mad3(int a, int b, int c, int add)
+{
+ if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL;
+ return stbi__malloc(a*b*c + add);
+}
+
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM)
+static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)
+{
+ if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL;
+ return stbi__malloc(a*b*c*d + add);
+}
+#endif
+
+// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow.
+static int stbi__addints_valid(int a, int b)
+{
+ if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow
+ if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0.
+ return a <= INT_MAX - b;
+}
+
+// returns 1 if the product of two signed shorts is valid, 0 on overflow.
+static int stbi__mul2shorts_valid(short a, short b)
+{
+ if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow
+ if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid
+ if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN
+ return a >= SHRT_MIN / b;
+}
+
+// stbi__err - error
+// stbi__errpf - error returning pointer to float
+// stbi__errpuc - error returning pointer to unsigned char
+
+#ifdef STBI_NO_FAILURE_STRINGS
+ #define stbi__err(x,y) 0
+#elif defined(STBI_FAILURE_USERMSG)
+ #define stbi__err(x,y) stbi__err(y)
+#else
+ #define stbi__err(x,y) stbi__err(x)
+#endif
+
+#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))
+#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))
+
+STBIDEF void stbi_image_free(void *retval_from_stbi_load)
+{
+ STBI_FREE(retval_from_stbi_load);
+}
+
+#ifndef STBI_NO_LINEAR
+static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
+#endif
+
+#ifndef STBI_NO_HDR
+static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp);
+#endif
+
+static int stbi__vertically_flip_on_load_global = 0;
+
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
+{
+ stbi__vertically_flip_on_load_global = flag_true_if_should_flip;
+}
+
+#ifndef STBI_THREAD_LOCAL
+#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global
+#else
+static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set;
+
+STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip)
+{
+ stbi__vertically_flip_on_load_local = flag_true_if_should_flip;
+ stbi__vertically_flip_on_load_set = 1;
+}
+
+#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \
+ ? stbi__vertically_flip_on_load_local \
+ : stbi__vertically_flip_on_load_global)
+#endif // STBI_THREAD_LOCAL
+
+static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
+{
+ memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields
+ ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed
+ ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order
+ ri->num_channels = 0;
+
+ // test the formats with a very explicit header first (at least a FOURCC
+ // or distinctive magic number first)
+ #ifndef STBI_NO_PNG
+ if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri);
+ #endif
+ #ifndef STBI_NO_BMP
+ if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri);
+ #endif
+ #ifndef STBI_NO_GIF
+ if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri);
+ #endif
+ #ifndef STBI_NO_PSD
+ if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc);
+ #else
+ STBI_NOTUSED(bpc);
+ #endif
+ #ifndef STBI_NO_PIC
+ if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri);
+ #endif
+
+ // then the formats that can end up attempting to load with just 1 or 2
+ // bytes matching expectations; these are prone to false positives, so
+ // try them later
+ #ifndef STBI_NO_JPEG
+ if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri);
+ #endif
+ #ifndef STBI_NO_PNM
+ if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri);
+ #endif
+
+ #ifndef STBI_NO_HDR
+ if (stbi__hdr_test(s)) {
+ float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri);
+ return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);
+ }
+ #endif
+
+ #ifndef STBI_NO_TGA
+ // test tga last because it's a crappy test!
+ if (stbi__tga_test(s))
+ return stbi__tga_load(s,x,y,comp,req_comp, ri);
+ #endif
+
+ return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt");
+}
+
+static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels)
+{
+ int i;
+ int img_len = w * h * channels;
+ stbi_uc *reduced;
+
+ reduced = (stbi_uc *) stbi__malloc(img_len);
+ if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory");
+
+ for (i = 0; i < img_len; ++i)
+ reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling
+
+ STBI_FREE(orig);
+ return reduced;
+}
+
+static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels)
+{
+ int i;
+ int img_len = w * h * channels;
+ stbi__uint16 *enlarged;
+
+ enlarged = (stbi__uint16 *) stbi__malloc(img_len*2);
+ if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
+
+ for (i = 0; i < img_len; ++i)
+ enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff
+
+ STBI_FREE(orig);
+ return enlarged;
+}
+
+static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel)
+{
+ int row;
+ size_t bytes_per_row = (size_t)w * bytes_per_pixel;
+ stbi_uc temp[2048];
+ stbi_uc *bytes = (stbi_uc *)image;
+
+ for (row = 0; row < (h>>1); row++) {
+ stbi_uc *row0 = bytes + row*bytes_per_row;
+ stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row;
+ // swap row0 with row1
+ size_t bytes_left = bytes_per_row;
+ while (bytes_left) {
+ size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);
+ memcpy(temp, row0, bytes_copy);
+ memcpy(row0, row1, bytes_copy);
+ memcpy(row1, temp, bytes_copy);
+ row0 += bytes_copy;
+ row1 += bytes_copy;
+ bytes_left -= bytes_copy;
+ }
+ }
+}
+
+#ifndef STBI_NO_GIF
+static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel)
+{
+ int slice;
+ int slice_size = w * h * bytes_per_pixel;
+
+ stbi_uc *bytes = (stbi_uc *)image;
+ for (slice = 0; slice < z; ++slice) {
+ stbi__vertical_flip(bytes, w, h, bytes_per_pixel);
+ bytes += slice_size;
+ }
+}
+#endif
+
+static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__result_info ri;
+ void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);
+
+ if (result == NULL)
+ return NULL;
+
+ // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.
+ STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);
+
+ if (ri.bits_per_channel != 8) {
+ result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
+ ri.bits_per_channel = 8;
+ }
+
+ // @TODO: move stbi__convert_format to here
+
+ if (stbi__vertically_flip_on_load) {
+ int channels = req_comp ? req_comp : *comp;
+ stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc));
+ }
+
+ return (unsigned char *) result;
+}
+
+static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__result_info ri;
+ void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);
+
+ if (result == NULL)
+ return NULL;
+
+ // it is the responsibility of the loaders to make sure we get either 8 or 16 bit.
+ STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16);
+
+ if (ri.bits_per_channel != 16) {
+ result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
+ ri.bits_per_channel = 16;
+ }
+
+ // @TODO: move stbi__convert_format16 to here
+ // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision
+
+ if (stbi__vertically_flip_on_load) {
+ int channels = req_comp ? req_comp : *comp;
+ stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16));
+ }
+
+ return (stbi__uint16 *) result;
+}
+
+#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR)
+static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
+{
+ if (stbi__vertically_flip_on_load && result != NULL) {
+ int channels = req_comp ? req_comp : *comp;
+ stbi__vertical_flip(result, *x, *y, channels * sizeof(float));
+ }
+}
+#endif
+
+#ifndef STBI_NO_STDIO
+
+#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
+STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
+STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);
+#endif
+
+#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
+STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
+{
+ return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
+}
+#endif
+
+static FILE *stbi__fopen(char const *filename, char const *mode)
+{
+ FILE *f;
+#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
+ wchar_t wMode[64];
+ wchar_t wFilename[1024];
+ if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename)))
+ return 0;
+
+ if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))
+ return 0;
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+ if (0 != _wfopen_s(&f, wFilename, wMode))
+ f = 0;
+#else
+ f = _wfopen(wFilename, wMode);
+#endif
+
+#elif defined(_MSC_VER) && _MSC_VER >= 1400
+ if (0 != fopen_s(&f, filename, mode))
+ f=0;
+#else
+ f = fopen(filename, mode);
+#endif
+ return f;
+}
+
+
+STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+ FILE *f = stbi__fopen(filename, "rb");
+ unsigned char *result;
+ if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
+ result = stbi_load_from_file(f,x,y,comp,req_comp);
+ fclose(f);
+ return result;
+}
+
+STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char *result;
+ stbi__context s;
+ stbi__start_file(&s,f);
+ result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+ if (result) {
+ // need to 'unget' all the characters in the IO buffer
+ fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
+ }
+ return result;
+}
+
+STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__uint16 *result;
+ stbi__context s;
+ stbi__start_file(&s,f);
+ result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp);
+ if (result) {
+ // need to 'unget' all the characters in the IO buffer
+ fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
+ }
+ return result;
+}
+
+STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+ FILE *f = stbi__fopen(filename, "rb");
+ stbi__uint16 *result;
+ if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file");
+ result = stbi_load_from_file_16(f,x,y,comp,req_comp);
+ fclose(f);
+ return result;
+}
+
+
+#endif //!STBI_NO_STDIO
+
+STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels)
+{
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
+}
+
+STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels)
+{
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user);
+ return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
+}
+
+STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+}
+
+STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_GIF
+STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
+{
+ unsigned char *result;
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+
+ result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp);
+ if (stbi__vertically_flip_on_load) {
+ stbi__vertical_flip_slices( result, *x, *y, *z, *comp );
+ }
+
+ return result;
+}
+#endif
+
+#ifndef STBI_NO_LINEAR
+static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char *data;
+ #ifndef STBI_NO_HDR
+ if (stbi__hdr_test(s)) {
+ stbi__result_info ri;
+ float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri);
+ if (hdr_data)
+ stbi__float_postprocess(hdr_data,x,y,comp,req_comp);
+ return hdr_data;
+ }
+ #endif
+ data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp);
+ if (data)
+ return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);
+ return stbi__errpf("unknown image type", "Image not of any known type, or corrupt");
+}
+
+STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+
+STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+ float *result;
+ FILE *f = stbi__fopen(filename, "rb");
+ if (!f) return stbi__errpf("can't fopen", "Unable to open file");
+ result = stbi_loadf_from_file(f,x,y,comp,req_comp);
+ fclose(f);
+ return result;
+}
+
+STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_file(&s,f);
+ return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+#endif // !STBI_NO_STDIO
+
+#endif // !STBI_NO_LINEAR
+
+// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is
+// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always
+// reports false!
+
+STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)
+{
+ #ifndef STBI_NO_HDR
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__hdr_test(&s);
+ #else
+ STBI_NOTUSED(buffer);
+ STBI_NOTUSED(len);
+ return 0;
+ #endif
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_is_hdr (char const *filename)
+{
+ FILE *f = stbi__fopen(filename, "rb");
+ int result=0;
+ if (f) {
+ result = stbi_is_hdr_from_file(f);
+ fclose(f);
+ }
+ return result;
+}
+
+STBIDEF int stbi_is_hdr_from_file(FILE *f)
+{
+ #ifndef STBI_NO_HDR
+ long pos = ftell(f);
+ int res;
+ stbi__context s;
+ stbi__start_file(&s,f);
+ res = stbi__hdr_test(&s);
+ fseek(f, pos, SEEK_SET);
+ return res;
+ #else
+ STBI_NOTUSED(f);
+ return 0;
+ #endif
+}
+#endif // !STBI_NO_STDIO
+
+STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)
+{
+ #ifndef STBI_NO_HDR
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi__hdr_test(&s);
+ #else
+ STBI_NOTUSED(clbk);
+ STBI_NOTUSED(user);
+ return 0;
+ #endif
+}
+
+#ifndef STBI_NO_LINEAR
+static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f;
+
+STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }
+STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }
+#endif
+
+static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f;
+
+STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; }
+STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; }
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Common code used by all image loaders
+//
+
+enum
+{
+ STBI__SCAN_load=0,
+ STBI__SCAN_type,
+ STBI__SCAN_header
+};
+
+static void stbi__refill_buffer(stbi__context *s)
+{
+ int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);
+ s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original);
+ if (n == 0) {
+ // at end of file, treat same as if from memory, but need to handle case
+ // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file
+ s->read_from_callbacks = 0;
+ s->img_buffer = s->buffer_start;
+ s->img_buffer_end = s->buffer_start+1;
+ *s->img_buffer = 0;
+ } else {
+ s->img_buffer = s->buffer_start;
+ s->img_buffer_end = s->buffer_start + n;
+ }
+}
+
+stbi_inline static stbi_uc stbi__get8(stbi__context *s)
+{
+ if (s->img_buffer < s->img_buffer_end)
+ return *s->img_buffer++;
+ if (s->read_from_callbacks) {
+ stbi__refill_buffer(s);
+ return *s->img_buffer++;
+ }
+ return 0;
+}
+
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
+stbi_inline static int stbi__at_eof(stbi__context *s)
+{
+ if (s->io.read) {
+ if (!(s->io.eof)(s->io_user_data)) return 0;
+ // if feof() is true, check if buffer = end
+ // special case: we've only got the special 0 character at the end
+ if (s->read_from_callbacks == 0) return 1;
+ }
+
+ return s->img_buffer >= s->img_buffer_end;
+}
+#endif
+
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC)
+// nothing
+#else
+static void stbi__skip(stbi__context *s, int n)
+{
+ if (n == 0) return; // already there!
+ if (n < 0) {
+ s->img_buffer = s->img_buffer_end;
+ return;
+ }
+ if (s->io.read) {
+ int blen = (int) (s->img_buffer_end - s->img_buffer);
+ if (blen < n) {
+ s->img_buffer = s->img_buffer_end;
+ (s->io.skip)(s->io_user_data, n - blen);
+ return;
+ }
+ }
+ s->img_buffer += n;
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM)
+// nothing
+#else
+static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
+{
+ if (s->io.read) {
+ int blen = (int) (s->img_buffer_end - s->img_buffer);
+ if (blen < n) {
+ int res, count;
+
+ memcpy(buffer, s->img_buffer, blen);
+
+ count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);
+ res = (count == (n-blen));
+ s->img_buffer = s->img_buffer_end;
+ return res;
+ }
+ }
+
+ if (s->img_buffer+n <= s->img_buffer_end) {
+ memcpy(buffer, s->img_buffer, n);
+ s->img_buffer += n;
+ return 1;
+ } else
+ return 0;
+}
+#endif
+
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
+// nothing
+#else
+static int stbi__get16be(stbi__context *s)
+{
+ int z = stbi__get8(s);
+ return (z << 8) + stbi__get8(s);
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
+// nothing
+#else
+static stbi__uint32 stbi__get32be(stbi__context *s)
+{
+ stbi__uint32 z = stbi__get16be(s);
+ return (z << 16) + stbi__get16be(s);
+}
+#endif
+
+#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)
+// nothing
+#else
+static int stbi__get16le(stbi__context *s)
+{
+ int z = stbi__get8(s);
+ return z + (stbi__get8(s) << 8);
+}
+#endif
+
+#ifndef STBI_NO_BMP
+static stbi__uint32 stbi__get32le(stbi__context *s)
+{
+ stbi__uint32 z = stbi__get16le(s);
+ z += (stbi__uint32)stbi__get16le(s) << 16;
+ return z;
+}
+#endif
+
+#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings
+
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
+//////////////////////////////////////////////////////////////////////////////
+//
+// generic converter from built-in img_n to req_comp
+// individual types do this automatically as much as possible (e.g. jpeg
+// does all cases internally since it needs to colorspace convert anyway,
+// and it never has alpha, so very few cases ). png can automatically
+// interleave an alpha=255 channel, but falls back to this for other cases
+//
+// assume data buffer is malloced, so malloc a new one and free that one
+// only failure mode is malloc failing
+
+static stbi_uc stbi__compute_y(int r, int g, int b)
+{
+ return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8);
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
+static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)
+{
+ int i,j;
+ unsigned char *good;
+
+ if (req_comp == img_n) return data;
+ STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
+
+ good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0);
+ if (good == NULL) {
+ STBI_FREE(data);
+ return stbi__errpuc("outofmem", "Out of memory");
+ }
+
+ for (j=0; j < (int) y; ++j) {
+ unsigned char *src = data + j * x * img_n ;
+ unsigned char *dest = good + j * x * req_comp;
+
+ #define STBI__COMBO(a,b) ((a)*8+(b))
+ #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
+ // convert source image with img_n components to one with req_comp components;
+ // avoid switch per pixel, so use switch per scanline and massive macros
+ switch (STBI__COMBO(img_n, req_comp)) {
+ STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break;
+ STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
+ STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break;
+ STBI__CASE(2,1) { dest[0]=src[0]; } break;
+ STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
+ STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break;
+ STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break;
+ STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break;
+ STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break;
+ STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break;
+ STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break;
+ STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break;
+ default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion");
+ }
+ #undef STBI__CASE
+ }
+
+ STBI_FREE(data);
+ return good;
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
+// nothing
+#else
+static stbi__uint16 stbi__compute_y_16(int r, int g, int b)
+{
+ return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8);
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
+// nothing
+#else
+static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y)
+{
+ int i,j;
+ stbi__uint16 *good;
+
+ if (req_comp == img_n) return data;
+ STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
+
+ good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2);
+ if (good == NULL) {
+ STBI_FREE(data);
+ return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
+ }
+
+ for (j=0; j < (int) y; ++j) {
+ stbi__uint16 *src = data + j * x * img_n ;
+ stbi__uint16 *dest = good + j * x * req_comp;
+
+ #define STBI__COMBO(a,b) ((a)*8+(b))
+ #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
+ // convert source image with img_n components to one with req_comp components;
+ // avoid switch per pixel, so use switch per scanline and massive macros
+ switch (STBI__COMBO(img_n, req_comp)) {
+ STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break;
+ STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
+ STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break;
+ STBI__CASE(2,1) { dest[0]=src[0]; } break;
+ STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break;
+ STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break;
+ STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break;
+ STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break;
+ STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break;
+ STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break;
+ STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break;
+ STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break;
+ default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion");
+ }
+ #undef STBI__CASE
+ }
+
+ STBI_FREE(data);
+ return good;
+}
+#endif
+
+#ifndef STBI_NO_LINEAR
+static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
+{
+ int i,k,n;
+ float *output;
+ if (!data) return NULL;
+ output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0);
+ if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); }
+ // compute number of non-alpha components
+ if (comp & 1) n = comp; else n = comp-1;
+ for (i=0; i < x*y; ++i) {
+ for (k=0; k < n; ++k) {
+ output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale);
+ }
+ }
+ if (n < comp) {
+ for (i=0; i < x*y; ++i) {
+ output[i*comp + n] = data[i*comp + n]/255.0f;
+ }
+ }
+ STBI_FREE(data);
+ return output;
+}
+#endif
+
+#ifndef STBI_NO_HDR
+#define stbi__float2int(x) ((int) (x))
+static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp)
+{
+ int i,k,n;
+ stbi_uc *output;
+ if (!data) return NULL;
+ output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0);
+ if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); }
+ // compute number of non-alpha components
+ if (comp & 1) n = comp; else n = comp-1;
+ for (i=0; i < x*y; ++i) {
+ for (k=0; k < n; ++k) {
+ float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f;
+ if (z < 0) z = 0;
+ if (z > 255) z = 255;
+ output[i*comp + k] = (stbi_uc) stbi__float2int(z);
+ }
+ if (k < comp) {
+ float z = data[i*comp+k] * 255 + 0.5f;
+ if (z < 0) z = 0;
+ if (z > 255) z = 255;
+ output[i*comp + k] = (stbi_uc) stbi__float2int(z);
+ }
+ }
+ STBI_FREE(data);
+ return output;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// "baseline" JPEG/JFIF decoder
+//
+// simple implementation
+// - doesn't support delayed output of y-dimension
+// - simple interface (only one output format: 8-bit interleaved RGB)
+// - doesn't try to recover corrupt jpegs
+// - doesn't allow partial loading, loading multiple at once
+// - still fast on x86 (copying globals into locals doesn't help x86)
+// - allocates lots of intermediate memory (full size of all components)
+// - non-interleaved case requires this anyway
+// - allows good upsampling (see next)
+// high-quality
+// - upsampled channels are bilinearly interpolated, even across blocks
+// - quality integer IDCT derived from IJG's 'slow'
+// performance
+// - fast huffman; reasonable integer IDCT
+// - some SIMD kernels for common paths on targets with SSE2/NEON
+// - uses a lot of intermediate memory, could cache poorly
+
+#ifndef STBI_NO_JPEG
+
+// huffman decoding acceleration
+#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache
+
+typedef struct
+{
+ stbi_uc fast[1 << FAST_BITS];
+ // weirdly, repacking this into AoS is a 10% speed loss, instead of a win
+ stbi__uint16 code[256];
+ stbi_uc values[256];
+ stbi_uc size[257];
+ unsigned int maxcode[18];
+ int delta[17]; // old 'firstsymbol' - old 'firstcode'
+} stbi__huffman;
+
+typedef struct
+{
+ stbi__context *s;
+ stbi__huffman huff_dc[4];
+ stbi__huffman huff_ac[4];
+ stbi__uint16 dequant[4][64];
+ stbi__int16 fast_ac[4][1 << FAST_BITS];
+
+// sizes for components, interleaved MCUs
+ int img_h_max, img_v_max;
+ int img_mcu_x, img_mcu_y;
+ int img_mcu_w, img_mcu_h;
+
+// definition of jpeg image component
+ struct
+ {
+ int id;
+ int h,v;
+ int tq;
+ int hd,ha;
+ int dc_pred;
+
+ int x,y,w2,h2;
+ stbi_uc *data;
+ void *raw_data, *raw_coeff;
+ stbi_uc *linebuf;
+ short *coeff; // progressive only
+ int coeff_w, coeff_h; // number of 8x8 coefficient blocks
+ } img_comp[4];
+
+ stbi__uint32 code_buffer; // jpeg entropy-coded buffer
+ int code_bits; // number of valid bits
+ unsigned char marker; // marker seen while filling entropy buffer
+ int nomore; // flag if we saw a marker so must stop
+
+ int progressive;
+ int spec_start;
+ int spec_end;
+ int succ_high;
+ int succ_low;
+ int eob_run;
+ int jfif;
+ int app14_color_transform; // Adobe APP14 tag
+ int rgb;
+
+ int scan_n, order[4];
+ int restart_interval, todo;
+
+// kernels
+ void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]);
+ void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step);
+ stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs);
+} stbi__jpeg;
+
+static int stbi__build_huffman(stbi__huffman *h, int *count)
+{
+ int i,j,k=0;
+ unsigned int code;
+ // build size list for each symbol (from JPEG spec)
+ for (i=0; i < 16; ++i) {
+ for (j=0; j < count[i]; ++j) {
+ h->size[k++] = (stbi_uc) (i+1);
+ if(k >= 257) return stbi__err("bad size list","Corrupt JPEG");
+ }
+ }
+ h->size[k] = 0;
+
+ // compute actual symbols (from jpeg spec)
+ code = 0;
+ k = 0;
+ for(j=1; j <= 16; ++j) {
+ // compute delta to add to code to compute symbol id
+ h->delta[j] = k - code;
+ if (h->size[k] == j) {
+ while (h->size[k] == j)
+ h->code[k++] = (stbi__uint16) (code++);
+ if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG");
+ }
+ // compute largest code + 1 for this size, preshifted as needed later
+ h->maxcode[j] = code << (16-j);
+ code <<= 1;
+ }
+ h->maxcode[j] = 0xffffffff;
+
+ // build non-spec acceleration table; 255 is flag for not-accelerated
+ memset(h->fast, 255, 1 << FAST_BITS);
+ for (i=0; i < k; ++i) {
+ int s = h->size[i];
+ if (s <= FAST_BITS) {
+ int c = h->code[i] << (FAST_BITS-s);
+ int m = 1 << (FAST_BITS-s);
+ for (j=0; j < m; ++j) {
+ h->fast[c+j] = (stbi_uc) i;
+ }
+ }
+ }
+ return 1;
+}
+
+// build a table that decodes both magnitude and value of small ACs in
+// one go.
+static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h)
+{
+ int i;
+ for (i=0; i < (1 << FAST_BITS); ++i) {
+ stbi_uc fast = h->fast[i];
+ fast_ac[i] = 0;
+ if (fast < 255) {
+ int rs = h->values[fast];
+ int run = (rs >> 4) & 15;
+ int magbits = rs & 15;
+ int len = h->size[fast];
+
+ if (magbits && len + magbits <= FAST_BITS) {
+ // magnitude code followed by receive_extend code
+ int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits);
+ int m = 1 << (magbits - 1);
+ if (k < m) k += (~0U << magbits) + 1;
+ // if the result is small enough, we can fit it in fast_ac table
+ if (k >= -128 && k <= 127)
+ fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits));
+ }
+ }
+ }
+}
+
+static void stbi__grow_buffer_unsafe(stbi__jpeg *j)
+{
+ do {
+ unsigned int b = j->nomore ? 0 : stbi__get8(j->s);
+ if (b == 0xff) {
+ int c = stbi__get8(j->s);
+ while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes
+ if (c != 0) {
+ j->marker = (unsigned char) c;
+ j->nomore = 1;
+ return;
+ }
+ }
+ j->code_buffer |= b << (24 - j->code_bits);
+ j->code_bits += 8;
+ } while (j->code_bits <= 24);
+}
+
+// (1 << n) - 1
+static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};
+
+// decode a jpeg huffman value from the bitstream
+stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)
+{
+ unsigned int temp;
+ int c,k;
+
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+
+ // look at the top FAST_BITS and determine what symbol ID it is,
+ // if the code is <= FAST_BITS
+ c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+ k = h->fast[c];
+ if (k < 255) {
+ int s = h->size[k];
+ if (s > j->code_bits)
+ return -1;
+ j->code_buffer <<= s;
+ j->code_bits -= s;
+ return h->values[k];
+ }
+
+ // naive test is to shift the code_buffer down so k bits are
+ // valid, then test against maxcode. To speed this up, we've
+ // preshifted maxcode left so that it has (16-k) 0s at the
+ // end; in other words, regardless of the number of bits, it
+ // wants to be compared against something shifted to have 16;
+ // that way we don't need to shift inside the loop.
+ temp = j->code_buffer >> 16;
+ for (k=FAST_BITS+1 ; ; ++k)
+ if (temp < h->maxcode[k])
+ break;
+ if (k == 17) {
+ // error! code not found
+ j->code_bits -= 16;
+ return -1;
+ }
+
+ if (k > j->code_bits)
+ return -1;
+
+ // convert the huffman code to the symbol id
+ c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
+ if(c < 0 || c >= 256) // symbol id out of bounds!
+ return -1;
+ STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);
+
+ // convert the id to a symbol
+ j->code_bits -= k;
+ j->code_buffer <<= k;
+ return h->values[c];
+}
+
+// bias[n] = (-1<<n) + 1
+static const int stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767};
+
+// combined JPEG 'receive' and JPEG 'extend', since baseline
+// always extends everything it receives.
+stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
+{
+ unsigned int k;
+ int sgn;
+ if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
+ if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
+
+ sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative)
+ k = stbi_lrot(j->code_buffer, n);
+ j->code_buffer = k & ~stbi__bmask[n];
+ k &= stbi__bmask[n];
+ j->code_bits -= n;
+ return k + (stbi__jbias[n] & (sgn - 1));
+}
+
+// get some unsigned bits
+stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)
+{
+ unsigned int k;
+ if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
+ if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
+ k = stbi_lrot(j->code_buffer, n);
+ j->code_buffer = k & ~stbi__bmask[n];
+ k &= stbi__bmask[n];
+ j->code_bits -= n;
+ return k;
+}
+
+stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)
+{
+ unsigned int k;
+ if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);
+ if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing
+ k = j->code_buffer;
+ j->code_buffer <<= 1;
+ --j->code_bits;
+ return k & 0x80000000;
+}
+
+// given a value that's at position X in the zigzag stream,
+// where does it appear in the 8x8 matrix coded as row-major?
+static const stbi_uc stbi__jpeg_dezigzag[64+15] =
+{
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+ // let corrupt input sample past end
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63
+};
+
+// decode one 64-entry block--
+static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant)
+{
+ int diff,dc,k;
+ int t;
+
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+ t = stbi__jpeg_huff_decode(j, hdc);
+ if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG");
+
+ // 0 all the ac values now so we can do it 32-bits at a time
+ memset(data,0,64*sizeof(data[0]));
+
+ diff = t ? stbi__extend_receive(j, t) : 0;
+ if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG");
+ dc = j->img_comp[b].dc_pred + diff;
+ j->img_comp[b].dc_pred = dc;
+ if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+ data[0] = (short) (dc * dequant[0]);
+
+ // decode AC components, see JPEG spec
+ k = 1;
+ do {
+ unsigned int zig;
+ int c,r,s;
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+ c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+ r = fac[c];
+ if (r) { // fast-AC path
+ k += (r >> 4) & 15; // run
+ s = r & 15; // combined length
+ if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
+ j->code_buffer <<= s;
+ j->code_bits -= s;
+ // decode into unzigzag'd location
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) ((r >> 8) * dequant[zig]);
+ } else {
+ int rs = stbi__jpeg_huff_decode(j, hac);
+ if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+ s = rs & 15;
+ r = rs >> 4;
+ if (s == 0) {
+ if (rs != 0xf0) break; // end block
+ k += 16;
+ } else {
+ k += r;
+ // decode into unzigzag'd location
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]);
+ }
+ }
+ } while (k < 64);
+ return 1;
+}
+
+static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b)
+{
+ int diff,dc;
+ int t;
+ if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+
+ if (j->succ_high == 0) {
+ // first scan for DC coefficient, must be first
+ memset(data,0,64*sizeof(data[0])); // 0 all the ac values now
+ t = stbi__jpeg_huff_decode(j, hdc);
+ if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+ diff = t ? stbi__extend_receive(j, t) : 0;
+
+ if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG");
+ dc = j->img_comp[b].dc_pred + diff;
+ j->img_comp[b].dc_pred = dc;
+ if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+ data[0] = (short) (dc * (1 << j->succ_low));
+ } else {
+ // refinement scan for DC coefficient
+ if (stbi__jpeg_get_bit(j))
+ data[0] += (short) (1 << j->succ_low);
+ }
+ return 1;
+}
+
+// @OPTIMIZE: store non-zigzagged during the decode passes,
+// and only de-zigzag when dequantizing
+static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac)
+{
+ int k;
+ if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+
+ if (j->succ_high == 0) {
+ int shift = j->succ_low;
+
+ if (j->eob_run) {
+ --j->eob_run;
+ return 1;
+ }
+
+ k = j->spec_start;
+ do {
+ unsigned int zig;
+ int c,r,s;
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+ c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+ r = fac[c];
+ if (r) { // fast-AC path
+ k += (r >> 4) & 15; // run
+ s = r & 15; // combined length
+ if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
+ j->code_buffer <<= s;
+ j->code_bits -= s;
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) ((r >> 8) * (1 << shift));
+ } else {
+ int rs = stbi__jpeg_huff_decode(j, hac);
+ if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+ s = rs & 15;
+ r = rs >> 4;
+ if (s == 0) {
+ if (r < 15) {
+ j->eob_run = (1 << r);
+ if (r)
+ j->eob_run += stbi__jpeg_get_bits(j, r);
+ --j->eob_run;
+ break;
+ }
+ k += 16;
+ } else {
+ k += r;
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift));
+ }
+ }
+ } while (k <= j->spec_end);
+ } else {
+ // refinement scan for these AC coefficients
+
+ short bit = (short) (1 << j->succ_low);
+
+ if (j->eob_run) {
+ --j->eob_run;
+ for (k = j->spec_start; k <= j->spec_end; ++k) {
+ short *p = &data[stbi__jpeg_dezigzag[k]];
+ if (*p != 0)
+ if (stbi__jpeg_get_bit(j))
+ if ((*p & bit)==0) {
+ if (*p > 0)
+ *p += bit;
+ else
+ *p -= bit;
+ }
+ }
+ } else {
+ k = j->spec_start;
+ do {
+ int r,s;
+ int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh
+ if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+ s = rs & 15;
+ r = rs >> 4;
+ if (s == 0) {
+ if (r < 15) {
+ j->eob_run = (1 << r) - 1;
+ if (r)
+ j->eob_run += stbi__jpeg_get_bits(j, r);
+ r = 64; // force end of block
+ } else {
+ // r=15 s=0 should write 16 0s, so we just do
+ // a run of 15 0s and then write s (which is 0),
+ // so we don't have to do anything special here
+ }
+ } else {
+ if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG");
+ // sign bit
+ if (stbi__jpeg_get_bit(j))
+ s = bit;
+ else
+ s = -bit;
+ }
+
+ // advance by r
+ while (k <= j->spec_end) {
+ short *p = &data[stbi__jpeg_dezigzag[k++]];
+ if (*p != 0) {
+ if (stbi__jpeg_get_bit(j))
+ if ((*p & bit)==0) {
+ if (*p > 0)
+ *p += bit;
+ else
+ *p -= bit;
+ }
+ } else {
+ if (r == 0) {
+ *p = (short) s;
+ break;
+ }
+ --r;
+ }
+ }
+ } while (k <= j->spec_end);
+ }
+ }
+ return 1;
+}
+
+// take a -128..127 value and stbi__clamp it and convert to 0..255
+stbi_inline static stbi_uc stbi__clamp(int x)
+{
+ // trick to use a single test to catch both cases
+ if ((unsigned int) x > 255) {
+ if (x < 0) return 0;
+ if (x > 255) return 255;
+ }
+ return (stbi_uc) x;
+}
+
+#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5)))
+#define stbi__fsh(x) ((x) * 4096)
+
+// derived from jidctint -- DCT_ISLOW
+#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \
+ int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \
+ p2 = s2; \
+ p3 = s6; \
+ p1 = (p2+p3) * stbi__f2f(0.5411961f); \
+ t2 = p1 + p3*stbi__f2f(-1.847759065f); \
+ t3 = p1 + p2*stbi__f2f( 0.765366865f); \
+ p2 = s0; \
+ p3 = s4; \
+ t0 = stbi__fsh(p2+p3); \
+ t1 = stbi__fsh(p2-p3); \
+ x0 = t0+t3; \
+ x3 = t0-t3; \
+ x1 = t1+t2; \
+ x2 = t1-t2; \
+ t0 = s7; \
+ t1 = s5; \
+ t2 = s3; \
+ t3 = s1; \
+ p3 = t0+t2; \
+ p4 = t1+t3; \
+ p1 = t0+t3; \
+ p2 = t1+t2; \
+ p5 = (p3+p4)*stbi__f2f( 1.175875602f); \
+ t0 = t0*stbi__f2f( 0.298631336f); \
+ t1 = t1*stbi__f2f( 2.053119869f); \
+ t2 = t2*stbi__f2f( 3.072711026f); \
+ t3 = t3*stbi__f2f( 1.501321110f); \
+ p1 = p5 + p1*stbi__f2f(-0.899976223f); \
+ p2 = p5 + p2*stbi__f2f(-2.562915447f); \
+ p3 = p3*stbi__f2f(-1.961570560f); \
+ p4 = p4*stbi__f2f(-0.390180644f); \
+ t3 += p1+p4; \
+ t2 += p2+p3; \
+ t1 += p2+p4; \
+ t0 += p1+p3;
+
+static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64])
+{
+ int i,val[64],*v=val;
+ stbi_uc *o;
+ short *d = data;
+
+ // columns
+ for (i=0; i < 8; ++i,++d, ++v) {
+ // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
+ if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
+ && d[40]==0 && d[48]==0 && d[56]==0) {
+ // no shortcut 0 seconds
+ // (1|2|3|4|5|6|7)==0 0 seconds
+ // all separate -0.047 seconds
+ // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds
+ int dcterm = d[0]*4;
+ v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
+ } else {
+ STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56])
+ // constants scaled things up by 1<<12; let's bring them back
+ // down, but keep 2 extra bits of precision
+ x0 += 512; x1 += 512; x2 += 512; x3 += 512;
+ v[ 0] = (x0+t3) >> 10;
+ v[56] = (x0-t3) >> 10;
+ v[ 8] = (x1+t2) >> 10;
+ v[48] = (x1-t2) >> 10;
+ v[16] = (x2+t1) >> 10;
+ v[40] = (x2-t1) >> 10;
+ v[24] = (x3+t0) >> 10;
+ v[32] = (x3-t0) >> 10;
+ }
+ }
+
+ for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) {
+ // no fast case since the first 1D IDCT spread components out
+ STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])
+ // constants scaled things up by 1<<12, plus we had 1<<2 from first
+ // loop, plus horizontal and vertical each scale by sqrt(8) so together
+ // we've got an extra 1<<3, so 1<<17 total we need to remove.
+ // so we want to round that, which means adding 0.5 * 1<<17,
+ // aka 65536. Also, we'll end up with -128 to 127 that we want
+ // to encode as 0..255 by adding 128, so we'll add that before the shift
+ x0 += 65536 + (128<<17);
+ x1 += 65536 + (128<<17);
+ x2 += 65536 + (128<<17);
+ x3 += 65536 + (128<<17);
+ // tried computing the shifts into temps, or'ing the temps to see
+ // if any were out of range, but that was slower
+ o[0] = stbi__clamp((x0+t3) >> 17);
+ o[7] = stbi__clamp((x0-t3) >> 17);
+ o[1] = stbi__clamp((x1+t2) >> 17);
+ o[6] = stbi__clamp((x1-t2) >> 17);
+ o[2] = stbi__clamp((x2+t1) >> 17);
+ o[5] = stbi__clamp((x2-t1) >> 17);
+ o[3] = stbi__clamp((x3+t0) >> 17);
+ o[4] = stbi__clamp((x3-t0) >> 17);
+ }
+}
+
+#ifdef STBI_SSE2
+// sse2 integer IDCT. not the fastest possible implementation but it
+// produces bit-identical results to the generic C version so it's
+// fully "transparent".
+static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
+{
+ // This is constructed to match our regular (generic) integer IDCT exactly.
+ __m128i row0, row1, row2, row3, row4, row5, row6, row7;
+ __m128i tmp;
+
+ // dot product constant: even elems=x, odd elems=y
+ #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y))
+
+ // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit)
+ // out(1) = c1[even]*x + c1[odd]*y
+ #define dct_rot(out0,out1, x,y,c0,c1) \
+ __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \
+ __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \
+ __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \
+ __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \
+ __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \
+ __m128i out1##_h = _mm_madd_epi16(c0##hi, c1)
+
+ // out = in << 12 (in 16-bit, out 32-bit)
+ #define dct_widen(out, in) \
+ __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \
+ __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4)
+
+ // wide add
+ #define dct_wadd(out, a, b) \
+ __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \
+ __m128i out##_h = _mm_add_epi32(a##_h, b##_h)
+
+ // wide sub
+ #define dct_wsub(out, a, b) \
+ __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \
+ __m128i out##_h = _mm_sub_epi32(a##_h, b##_h)
+
+ // butterfly a/b, add bias, then shift by "s" and pack
+ #define dct_bfly32o(out0, out1, a,b,bias,s) \
+ { \
+ __m128i abiased_l = _mm_add_epi32(a##_l, bias); \
+ __m128i abiased_h = _mm_add_epi32(a##_h, bias); \
+ dct_wadd(sum, abiased, b); \
+ dct_wsub(dif, abiased, b); \
+ out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \
+ out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \
+ }
+
+ // 8-bit interleave step (for transposes)
+ #define dct_interleave8(a, b) \
+ tmp = a; \
+ a = _mm_unpacklo_epi8(a, b); \
+ b = _mm_unpackhi_epi8(tmp, b)
+
+ // 16-bit interleave step (for transposes)
+ #define dct_interleave16(a, b) \
+ tmp = a; \
+ a = _mm_unpacklo_epi16(a, b); \
+ b = _mm_unpackhi_epi16(tmp, b)
+
+ #define dct_pass(bias,shift) \
+ { \
+ /* even part */ \
+ dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \
+ __m128i sum04 = _mm_add_epi16(row0, row4); \
+ __m128i dif04 = _mm_sub_epi16(row0, row4); \
+ dct_widen(t0e, sum04); \
+ dct_widen(t1e, dif04); \
+ dct_wadd(x0, t0e, t3e); \
+ dct_wsub(x3, t0e, t3e); \
+ dct_wadd(x1, t1e, t2e); \
+ dct_wsub(x2, t1e, t2e); \
+ /* odd part */ \
+ dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \
+ dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \
+ __m128i sum17 = _mm_add_epi16(row1, row7); \
+ __m128i sum35 = _mm_add_epi16(row3, row5); \
+ dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \
+ dct_wadd(x4, y0o, y4o); \
+ dct_wadd(x5, y1o, y5o); \
+ dct_wadd(x6, y2o, y5o); \
+ dct_wadd(x7, y3o, y4o); \
+ dct_bfly32o(row0,row7, x0,x7,bias,shift); \
+ dct_bfly32o(row1,row6, x1,x6,bias,shift); \
+ dct_bfly32o(row2,row5, x2,x5,bias,shift); \
+ dct_bfly32o(row3,row4, x3,x4,bias,shift); \
+ }
+
+ __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));
+ __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f));
+ __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f));
+ __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));
+ __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f));
+ __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f));
+ __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f));
+ __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f));
+
+ // rounding biases in column/row passes, see stbi__idct_block for explanation.
+ __m128i bias_0 = _mm_set1_epi32(512);
+ __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17));
+
+ // load
+ row0 = _mm_load_si128((const __m128i *) (data + 0*8));
+ row1 = _mm_load_si128((const __m128i *) (data + 1*8));
+ row2 = _mm_load_si128((const __m128i *) (data + 2*8));
+ row3 = _mm_load_si128((const __m128i *) (data + 3*8));
+ row4 = _mm_load_si128((const __m128i *) (data + 4*8));
+ row5 = _mm_load_si128((const __m128i *) (data + 5*8));
+ row6 = _mm_load_si128((const __m128i *) (data + 6*8));
+ row7 = _mm_load_si128((const __m128i *) (data + 7*8));
+
+ // column pass
+ dct_pass(bias_0, 10);
+
+ {
+ // 16bit 8x8 transpose pass 1
+ dct_interleave16(row0, row4);
+ dct_interleave16(row1, row5);
+ dct_interleave16(row2, row6);
+ dct_interleave16(row3, row7);
+
+ // transpose pass 2
+ dct_interleave16(row0, row2);
+ dct_interleave16(row1, row3);
+ dct_interleave16(row4, row6);
+ dct_interleave16(row5, row7);
+
+ // transpose pass 3
+ dct_interleave16(row0, row1);
+ dct_interleave16(row2, row3);
+ dct_interleave16(row4, row5);
+ dct_interleave16(row6, row7);
+ }
+
+ // row pass
+ dct_pass(bias_1, 17);
+
+ {
+ // pack
+ __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7
+ __m128i p1 = _mm_packus_epi16(row2, row3);
+ __m128i p2 = _mm_packus_epi16(row4, row5);
+ __m128i p3 = _mm_packus_epi16(row6, row7);
+
+ // 8bit 8x8 transpose pass 1
+ dct_interleave8(p0, p2); // a0e0a1e1...
+ dct_interleave8(p1, p3); // c0g0c1g1...
+
+ // transpose pass 2
+ dct_interleave8(p0, p1); // a0c0e0g0...
+ dct_interleave8(p2, p3); // b0d0f0h0...
+
+ // transpose pass 3
+ dct_interleave8(p0, p2); // a0b0c0d0...
+ dct_interleave8(p1, p3); // a4b4c4d4...
+
+ // store
+ _mm_storel_epi64((__m128i *) out, p0); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, p2); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, p1); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, p3); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e));
+ }
+
+#undef dct_const
+#undef dct_rot
+#undef dct_widen
+#undef dct_wadd
+#undef dct_wsub
+#undef dct_bfly32o
+#undef dct_interleave8
+#undef dct_interleave16
+#undef dct_pass
+}
+
+#endif // STBI_SSE2
+
+#ifdef STBI_NEON
+
+// NEON integer IDCT. should produce bit-identical
+// results to the generic C version.
+static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
+{
+ int16x8_t row0, row1, row2, row3, row4, row5, row6, row7;
+
+ int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f));
+ int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f));
+ int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f));
+ int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f));
+ int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f));
+ int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f));
+ int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f));
+ int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f));
+ int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f));
+ int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f));
+ int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f));
+ int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f));
+
+#define dct_long_mul(out, inq, coeff) \
+ int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \
+ int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff)
+
+#define dct_long_mac(out, acc, inq, coeff) \
+ int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \
+ int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff)
+
+#define dct_widen(out, inq) \
+ int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \
+ int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12)
+
+// wide add
+#define dct_wadd(out, a, b) \
+ int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \
+ int32x4_t out##_h = vaddq_s32(a##_h, b##_h)
+
+// wide sub
+#define dct_wsub(out, a, b) \
+ int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \
+ int32x4_t out##_h = vsubq_s32(a##_h, b##_h)
+
+// butterfly a/b, then shift using "shiftop" by "s" and pack
+#define dct_bfly32o(out0,out1, a,b,shiftop,s) \
+ { \
+ dct_wadd(sum, a, b); \
+ dct_wsub(dif, a, b); \
+ out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \
+ out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \
+ }
+
+#define dct_pass(shiftop, shift) \
+ { \
+ /* even part */ \
+ int16x8_t sum26 = vaddq_s16(row2, row6); \
+ dct_long_mul(p1e, sum26, rot0_0); \
+ dct_long_mac(t2e, p1e, row6, rot0_1); \
+ dct_long_mac(t3e, p1e, row2, rot0_2); \
+ int16x8_t sum04 = vaddq_s16(row0, row4); \
+ int16x8_t dif04 = vsubq_s16(row0, row4); \
+ dct_widen(t0e, sum04); \
+ dct_widen(t1e, dif04); \
+ dct_wadd(x0, t0e, t3e); \
+ dct_wsub(x3, t0e, t3e); \
+ dct_wadd(x1, t1e, t2e); \
+ dct_wsub(x2, t1e, t2e); \
+ /* odd part */ \
+ int16x8_t sum15 = vaddq_s16(row1, row5); \
+ int16x8_t sum17 = vaddq_s16(row1, row7); \
+ int16x8_t sum35 = vaddq_s16(row3, row5); \
+ int16x8_t sum37 = vaddq_s16(row3, row7); \
+ int16x8_t sumodd = vaddq_s16(sum17, sum35); \
+ dct_long_mul(p5o, sumodd, rot1_0); \
+ dct_long_mac(p1o, p5o, sum17, rot1_1); \
+ dct_long_mac(p2o, p5o, sum35, rot1_2); \
+ dct_long_mul(p3o, sum37, rot2_0); \
+ dct_long_mul(p4o, sum15, rot2_1); \
+ dct_wadd(sump13o, p1o, p3o); \
+ dct_wadd(sump24o, p2o, p4o); \
+ dct_wadd(sump23o, p2o, p3o); \
+ dct_wadd(sump14o, p1o, p4o); \
+ dct_long_mac(x4, sump13o, row7, rot3_0); \
+ dct_long_mac(x5, sump24o, row5, rot3_1); \
+ dct_long_mac(x6, sump23o, row3, rot3_2); \
+ dct_long_mac(x7, sump14o, row1, rot3_3); \
+ dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \
+ dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \
+ dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \
+ dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \
+ }
+
+ // load
+ row0 = vld1q_s16(data + 0*8);
+ row1 = vld1q_s16(data + 1*8);
+ row2 = vld1q_s16(data + 2*8);
+ row3 = vld1q_s16(data + 3*8);
+ row4 = vld1q_s16(data + 4*8);
+ row5 = vld1q_s16(data + 5*8);
+ row6 = vld1q_s16(data + 6*8);
+ row7 = vld1q_s16(data + 7*8);
+
+ // add DC bias
+ row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0));
+
+ // column pass
+ dct_pass(vrshrn_n_s32, 10);
+
+ // 16bit 8x8 transpose
+ {
+// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively.
+// whether compilers actually get this is another story, sadly.
+#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; }
+#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); }
+#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); }
+
+ // pass 1
+ dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6
+ dct_trn16(row2, row3);
+ dct_trn16(row4, row5);
+ dct_trn16(row6, row7);
+
+ // pass 2
+ dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4
+ dct_trn32(row1, row3);
+ dct_trn32(row4, row6);
+ dct_trn32(row5, row7);
+
+ // pass 3
+ dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0
+ dct_trn64(row1, row5);
+ dct_trn64(row2, row6);
+ dct_trn64(row3, row7);
+
+#undef dct_trn16
+#undef dct_trn32
+#undef dct_trn64
+ }
+
+ // row pass
+ // vrshrn_n_s32 only supports shifts up to 16, we need
+ // 17. so do a non-rounding shift of 16 first then follow
+ // up with a rounding shift by 1.
+ dct_pass(vshrn_n_s32, 16);
+
+ {
+ // pack and round
+ uint8x8_t p0 = vqrshrun_n_s16(row0, 1);
+ uint8x8_t p1 = vqrshrun_n_s16(row1, 1);
+ uint8x8_t p2 = vqrshrun_n_s16(row2, 1);
+ uint8x8_t p3 = vqrshrun_n_s16(row3, 1);
+ uint8x8_t p4 = vqrshrun_n_s16(row4, 1);
+ uint8x8_t p5 = vqrshrun_n_s16(row5, 1);
+ uint8x8_t p6 = vqrshrun_n_s16(row6, 1);
+ uint8x8_t p7 = vqrshrun_n_s16(row7, 1);
+
+ // again, these can translate into one instruction, but often don't.
+#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; }
+#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); }
+#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); }
+
+ // sadly can't use interleaved stores here since we only write
+ // 8 bytes to each scan line!
+
+ // 8x8 8-bit transpose pass 1
+ dct_trn8_8(p0, p1);
+ dct_trn8_8(p2, p3);
+ dct_trn8_8(p4, p5);
+ dct_trn8_8(p6, p7);
+
+ // pass 2
+ dct_trn8_16(p0, p2);
+ dct_trn8_16(p1, p3);
+ dct_trn8_16(p4, p6);
+ dct_trn8_16(p5, p7);
+
+ // pass 3
+ dct_trn8_32(p0, p4);
+ dct_trn8_32(p1, p5);
+ dct_trn8_32(p2, p6);
+ dct_trn8_32(p3, p7);
+
+ // store
+ vst1_u8(out, p0); out += out_stride;
+ vst1_u8(out, p1); out += out_stride;
+ vst1_u8(out, p2); out += out_stride;
+ vst1_u8(out, p3); out += out_stride;
+ vst1_u8(out, p4); out += out_stride;
+ vst1_u8(out, p5); out += out_stride;
+ vst1_u8(out, p6); out += out_stride;
+ vst1_u8(out, p7);
+
+#undef dct_trn8_8
+#undef dct_trn8_16
+#undef dct_trn8_32
+ }
+
+#undef dct_long_mul
+#undef dct_long_mac
+#undef dct_widen
+#undef dct_wadd
+#undef dct_wsub
+#undef dct_bfly32o
+#undef dct_pass
+}
+
+#endif // STBI_NEON
+
+#define STBI__MARKER_none 0xff
+// if there's a pending marker from the entropy stream, return that
+// otherwise, fetch from the stream and get a marker. if there's no
+// marker, return 0xff, which is never a valid marker value
+static stbi_uc stbi__get_marker(stbi__jpeg *j)
+{
+ stbi_uc x;
+ if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; }
+ x = stbi__get8(j->s);
+ if (x != 0xff) return STBI__MARKER_none;
+ while (x == 0xff)
+ x = stbi__get8(j->s); // consume repeated 0xff fill bytes
+ return x;
+}
+
+// in each scan, we'll have scan_n components, and the order
+// of the components is specified by order[]
+#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7)
+
+// after a restart interval, stbi__jpeg_reset the entropy decoder and
+// the dc prediction
+static void stbi__jpeg_reset(stbi__jpeg *j)
+{
+ j->code_bits = 0;
+ j->code_buffer = 0;
+ j->nomore = 0;
+ j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0;
+ j->marker = STBI__MARKER_none;
+ j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;
+ j->eob_run = 0;
+ // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,
+ // since we don't even allow 1<<30 pixels
+}
+
+static int stbi__parse_entropy_coded_data(stbi__jpeg *z)
+{
+ stbi__jpeg_reset(z);
+ if (!z->progressive) {
+ if (z->scan_n == 1) {
+ int i,j;
+ STBI_SIMD_ALIGN(short, data[64]);
+ int n = z->order[0];
+ // non-interleaved data, we just need to process one block at a time,
+ // in trivial scanline order
+ // number of blocks to do just depends on how many actual "pixels" this
+ // component has, independent of interleaved MCU blocking and such
+ int w = (z->img_comp[n].x+7) >> 3;
+ int h = (z->img_comp[n].y+7) >> 3;
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; ++i) {
+ int ha = z->img_comp[n].ha;
+ if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
+ z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
+ // every data block is an MCU, so countdown the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+ // if it's NOT a restart, then just bail, so we get corrupt data
+ // rather than no data
+ if (!STBI__RESTART(z->marker)) return 1;
+ stbi__jpeg_reset(z);
+ }
+ }
+ }
+ return 1;
+ } else { // interleaved
+ int i,j,k,x,y;
+ STBI_SIMD_ALIGN(short, data[64]);
+ for (j=0; j < z->img_mcu_y; ++j) {
+ for (i=0; i < z->img_mcu_x; ++i) {
+ // scan an interleaved mcu... process scan_n components in order
+ for (k=0; k < z->scan_n; ++k) {
+ int n = z->order[k];
+ // scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component
+ for (y=0; y < z->img_comp[n].v; ++y) {
+ for (x=0; x < z->img_comp[n].h; ++x) {
+ int x2 = (i*z->img_comp[n].h + x)*8;
+ int y2 = (j*z->img_comp[n].v + y)*8;
+ int ha = z->img_comp[n].ha;
+ if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
+ z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data);
+ }
+ }
+ }
+ // after all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+ if (!STBI__RESTART(z->marker)) return 1;
+ stbi__jpeg_reset(z);
+ }
+ }
+ }
+ return 1;
+ }
+ } else {
+ if (z->scan_n == 1) {
+ int i,j;
+ int n = z->order[0];
+ // non-interleaved data, we just need to process one block at a time,
+ // in trivial scanline order
+ // number of blocks to do just depends on how many actual "pixels" this
+ // component has, independent of interleaved MCU blocking and such
+ int w = (z->img_comp[n].x+7) >> 3;
+ int h = (z->img_comp[n].y+7) >> 3;
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; ++i) {
+ short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
+ if (z->spec_start == 0) {
+ if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
+ return 0;
+ } else {
+ int ha = z->img_comp[n].ha;
+ if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha]))
+ return 0;
+ }
+ // every data block is an MCU, so countdown the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+ if (!STBI__RESTART(z->marker)) return 1;
+ stbi__jpeg_reset(z);
+ }
+ }
+ }
+ return 1;
+ } else { // interleaved
+ int i,j,k,x,y;
+ for (j=0; j < z->img_mcu_y; ++j) {
+ for (i=0; i < z->img_mcu_x; ++i) {
+ // scan an interleaved mcu... process scan_n components in order
+ for (k=0; k < z->scan_n; ++k) {
+ int n = z->order[k];
+ // scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component
+ for (y=0; y < z->img_comp[n].v; ++y) {
+ for (x=0; x < z->img_comp[n].h; ++x) {
+ int x2 = (i*z->img_comp[n].h + x);
+ int y2 = (j*z->img_comp[n].v + y);
+ short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w);
+ if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
+ return 0;
+ }
+ }
+ }
+ // after all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+ if (!STBI__RESTART(z->marker)) return 1;
+ stbi__jpeg_reset(z);
+ }
+ }
+ }
+ return 1;
+ }
+ }
+}
+
+static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant)
+{
+ int i;
+ for (i=0; i < 64; ++i)
+ data[i] *= dequant[i];
+}
+
+static void stbi__jpeg_finish(stbi__jpeg *z)
+{
+ if (z->progressive) {
+ // dequantize and idct the data
+ int i,j,n;
+ for (n=0; n < z->s->img_n; ++n) {
+ int w = (z->img_comp[n].x+7) >> 3;
+ int h = (z->img_comp[n].y+7) >> 3;
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; ++i) {
+ short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
+ stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);
+ z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
+ }
+ }
+ }
+ }
+}
+
+static int stbi__process_marker(stbi__jpeg *z, int m)
+{
+ int L;
+ switch (m) {
+ case STBI__MARKER_none: // no marker found
+ return stbi__err("expected marker","Corrupt JPEG");
+
+ case 0xDD: // DRI - specify restart interval
+ if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG");
+ z->restart_interval = stbi__get16be(z->s);
+ return 1;
+
+ case 0xDB: // DQT - define quantization table
+ L = stbi__get16be(z->s)-2;
+ while (L > 0) {
+ int q = stbi__get8(z->s);
+ int p = q >> 4, sixteen = (p != 0);
+ int t = q & 15,i;
+ if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG");
+ if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG");
+
+ for (i=0; i < 64; ++i)
+ z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s));
+ L -= (sixteen ? 129 : 65);
+ }
+ return L==0;
+
+ case 0xC4: // DHT - define huffman table
+ L = stbi__get16be(z->s)-2;
+ while (L > 0) {
+ stbi_uc *v;
+ int sizes[16],i,n=0;
+ int q = stbi__get8(z->s);
+ int tc = q >> 4;
+ int th = q & 15;
+ if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG");
+ for (i=0; i < 16; ++i) {
+ sizes[i] = stbi__get8(z->s);
+ n += sizes[i];
+ }
+ if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values!
+ L -= 17;
+ if (tc == 0) {
+ if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;
+ v = z->huff_dc[th].values;
+ } else {
+ if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0;
+ v = z->huff_ac[th].values;
+ }
+ for (i=0; i < n; ++i)
+ v[i] = stbi__get8(z->s);
+ if (tc != 0)
+ stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th);
+ L -= n;
+ }
+ return L==0;
+ }
+
+ // check for comment block or APP blocks
+ if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {
+ L = stbi__get16be(z->s);
+ if (L < 2) {
+ if (m == 0xFE)
+ return stbi__err("bad COM len","Corrupt JPEG");
+ else
+ return stbi__err("bad APP len","Corrupt JPEG");
+ }
+ L -= 2;
+
+ if (m == 0xE0 && L >= 5) { // JFIF APP0 segment
+ static const unsigned char tag[5] = {'J','F','I','F','\0'};
+ int ok = 1;
+ int i;
+ for (i=0; i < 5; ++i)
+ if (stbi__get8(z->s) != tag[i])
+ ok = 0;
+ L -= 5;
+ if (ok)
+ z->jfif = 1;
+ } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment
+ static const unsigned char tag[6] = {'A','d','o','b','e','\0'};
+ int ok = 1;
+ int i;
+ for (i=0; i < 6; ++i)
+ if (stbi__get8(z->s) != tag[i])
+ ok = 0;
+ L -= 6;
+ if (ok) {
+ stbi__get8(z->s); // version
+ stbi__get16be(z->s); // flags0
+ stbi__get16be(z->s); // flags1
+ z->app14_color_transform = stbi__get8(z->s); // color transform
+ L -= 6;
+ }
+ }
+
+ stbi__skip(z->s, L);
+ return 1;
+ }
+
+ return stbi__err("unknown marker","Corrupt JPEG");
+}
+
+// after we see SOS
+static int stbi__process_scan_header(stbi__jpeg *z)
+{
+ int i;
+ int Ls = stbi__get16be(z->s);
+ z->scan_n = stbi__get8(z->s);
+ if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG");
+ if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG");
+ for (i=0; i < z->scan_n; ++i) {
+ int id = stbi__get8(z->s), which;
+ int q = stbi__get8(z->s);
+ for (which = 0; which < z->s->img_n; ++which)
+ if (z->img_comp[which].id == id)
+ break;
+ if (which == z->s->img_n) return 0; // no match
+ z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG");
+ z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG");
+ z->order[i] = which;
+ }
+
+ {
+ int aa;
+ z->spec_start = stbi__get8(z->s);
+ z->spec_end = stbi__get8(z->s); // should be 63, but might be 0
+ aa = stbi__get8(z->s);
+ z->succ_high = (aa >> 4);
+ z->succ_low = (aa & 15);
+ if (z->progressive) {
+ if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13)
+ return stbi__err("bad SOS", "Corrupt JPEG");
+ } else {
+ if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG");
+ if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG");
+ z->spec_end = 63;
+ }
+ }
+
+ return 1;
+}
+
+static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why)
+{
+ int i;
+ for (i=0; i < ncomp; ++i) {
+ if (z->img_comp[i].raw_data) {
+ STBI_FREE(z->img_comp[i].raw_data);
+ z->img_comp[i].raw_data = NULL;
+ z->img_comp[i].data = NULL;
+ }
+ if (z->img_comp[i].raw_coeff) {
+ STBI_FREE(z->img_comp[i].raw_coeff);
+ z->img_comp[i].raw_coeff = 0;
+ z->img_comp[i].coeff = 0;
+ }
+ if (z->img_comp[i].linebuf) {
+ STBI_FREE(z->img_comp[i].linebuf);
+ z->img_comp[i].linebuf = NULL;
+ }
+ }
+ return why;
+}
+
+static int stbi__process_frame_header(stbi__jpeg *z, int scan)
+{
+ stbi__context *s = z->s;
+ int Lf,p,i,q, h_max=1,v_max=1,c;
+ Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG
+ p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline
+ s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG
+ s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires
+ if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
+ if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
+ c = stbi__get8(s);
+ if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG");
+ s->img_n = c;
+ for (i=0; i < c; ++i) {
+ z->img_comp[i].data = NULL;
+ z->img_comp[i].linebuf = NULL;
+ }
+
+ if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG");
+
+ z->rgb = 0;
+ for (i=0; i < s->img_n; ++i) {
+ static const unsigned char rgb[3] = { 'R', 'G', 'B' };
+ z->img_comp[i].id = stbi__get8(s);
+ if (s->img_n == 3 && z->img_comp[i].id == rgb[i])
+ ++z->rgb;
+ q = stbi__get8(s);
+ z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG");
+ z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG");
+ z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG");
+ }
+
+ if (scan != STBI__SCAN_load) return 1;
+
+ if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode");
+
+ for (i=0; i < s->img_n; ++i) {
+ if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;
+ if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;
+ }
+
+ // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios
+ // and I've never seen a non-corrupted JPEG file actually use them
+ for (i=0; i < s->img_n; ++i) {
+ if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG");
+ if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG");
+ }
+
+ // compute interleaved mcu info
+ z->img_h_max = h_max;
+ z->img_v_max = v_max;
+ z->img_mcu_w = h_max * 8;
+ z->img_mcu_h = v_max * 8;
+ // these sizes can't be more than 17 bits
+ z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;
+ z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;
+
+ for (i=0; i < s->img_n; ++i) {
+ // number of effective pixels (e.g. for non-interleaved MCU)
+ z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;
+ z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;
+ // to simplify generation, we'll allocate enough memory to decode
+ // the bogus oversized data from using interleaved MCUs and their
+ // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't
+ // discard the extra data until colorspace conversion
+ //
+ // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier)
+ // so these muls can't overflow with 32-bit ints (which we require)
+ z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;
+ z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;
+ z->img_comp[i].coeff = 0;
+ z->img_comp[i].raw_coeff = 0;
+ z->img_comp[i].linebuf = NULL;
+ z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15);
+ if (z->img_comp[i].raw_data == NULL)
+ return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
+ // align blocks for idct using mmx/sse
+ z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);
+ if (z->progressive) {
+ // w2, h2 are multiples of 8 (see above)
+ z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8;
+ z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8;
+ z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15);
+ if (z->img_comp[i].raw_coeff == NULL)
+ return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
+ z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);
+ }
+ }
+
+ return 1;
+}
+
+// use comparisons since in some cases we handle more than one case (e.g. SOF)
+#define stbi__DNL(x) ((x) == 0xdc)
+#define stbi__SOI(x) ((x) == 0xd8)
+#define stbi__EOI(x) ((x) == 0xd9)
+#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2)
+#define stbi__SOS(x) ((x) == 0xda)
+
+#define stbi__SOF_progressive(x) ((x) == 0xc2)
+
+static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
+{
+ int m;
+ z->jfif = 0;
+ z->app14_color_transform = -1; // valid values are 0,1,2
+ z->marker = STBI__MARKER_none; // initialize cached marker to empty
+ m = stbi__get_marker(z);
+ if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG");
+ if (scan == STBI__SCAN_type) return 1;
+ m = stbi__get_marker(z);
+ while (!stbi__SOF(m)) {
+ if (!stbi__process_marker(z,m)) return 0;
+ m = stbi__get_marker(z);
+ while (m == STBI__MARKER_none) {
+ // some files have extra padding after their blocks, so ok, we'll scan
+ if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG");
+ m = stbi__get_marker(z);
+ }
+ }
+ z->progressive = stbi__SOF_progressive(m);
+ if (!stbi__process_frame_header(z, scan)) return 0;
+ return 1;
+}
+
+static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
+{
+ // some JPEGs have junk at end, skip over it but if we find what looks
+ // like a valid marker, resume there
+ while (!stbi__at_eof(j->s)) {
+ int x = stbi__get8(j->s);
+ while (x == 255) { // might be a marker
+ if (stbi__at_eof(j->s)) return STBI__MARKER_none;
+ x = stbi__get8(j->s);
+ if (x != 0x00 && x != 0xff) {
+ // not a stuffed zero or lead-in to another marker, looks
+ // like an actual marker, return it
+ return x;
+ }
+ // stuffed zero has x=0 now which ends the loop, meaning we go
+ // back to regular scan loop.
+ // repeated 0xff keeps trying to read the next byte of the marker.
+ }
+ }
+ return STBI__MARKER_none;
+}
+
+// decode image to YCbCr format
+static int stbi__decode_jpeg_image(stbi__jpeg *j)
+{
+ int m;
+ for (m = 0; m < 4; m++) {
+ j->img_comp[m].raw_data = NULL;
+ j->img_comp[m].raw_coeff = NULL;
+ }
+ j->restart_interval = 0;
+ if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;
+ m = stbi__get_marker(j);
+ while (!stbi__EOI(m)) {
+ if (stbi__SOS(m)) {
+ if (!stbi__process_scan_header(j)) return 0;
+ if (!stbi__parse_entropy_coded_data(j)) return 0;
+ if (j->marker == STBI__MARKER_none ) {
+ j->marker = stbi__skip_jpeg_junk_at_end(j);
+ // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
+ }
+ m = stbi__get_marker(j);
+ if (STBI__RESTART(m))
+ m = stbi__get_marker(j);
+ } else if (stbi__DNL(m)) {
+ int Ld = stbi__get16be(j->s);
+ stbi__uint32 NL = stbi__get16be(j->s);
+ if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG");
+ if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG");
+ m = stbi__get_marker(j);
+ } else {
+ if (!stbi__process_marker(j, m)) return 1;
+ m = stbi__get_marker(j);
+ }
+ }
+ if (j->progressive)
+ stbi__jpeg_finish(j);
+ return 1;
+}
+
+// static jfif-centered resampling (across block boundaries)
+
+typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1,
+ int w, int hs);
+
+#define stbi__div4(x) ((stbi_uc) ((x) >> 2))
+
+static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ STBI_NOTUSED(out);
+ STBI_NOTUSED(in_far);
+ STBI_NOTUSED(w);
+ STBI_NOTUSED(hs);
+ return in_near;
+}
+
+static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // need to generate two samples vertically for every one in input
+ int i;
+ STBI_NOTUSED(hs);
+ for (i=0; i < w; ++i)
+ out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2);
+ return out;
+}
+
+static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // need to generate two samples horizontally for every one in input
+ int i;
+ stbi_uc *input = in_near;
+
+ if (w == 1) {
+ // if only one sample, can't do any interpolation
+ out[0] = out[1] = input[0];
+ return out;
+ }
+
+ out[0] = input[0];
+ out[1] = stbi__div4(input[0]*3 + input[1] + 2);
+ for (i=1; i < w-1; ++i) {
+ int n = 3*input[i]+2;
+ out[i*2+0] = stbi__div4(n+input[i-1]);
+ out[i*2+1] = stbi__div4(n+input[i+1]);
+ }
+ out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2);
+ out[i*2+1] = input[w-1];
+
+ STBI_NOTUSED(in_far);
+ STBI_NOTUSED(hs);
+
+ return out;
+}
+
+#define stbi__div16(x) ((stbi_uc) ((x) >> 4))
+
+static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // need to generate 2x2 samples for every one in input
+ int i,t0,t1;
+ if (w == 1) {
+ out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
+ return out;
+ }
+
+ t1 = 3*in_near[0] + in_far[0];
+ out[0] = stbi__div4(t1+2);
+ for (i=1; i < w; ++i) {
+ t0 = t1;
+ t1 = 3*in_near[i]+in_far[i];
+ out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
+ out[i*2 ] = stbi__div16(3*t1 + t0 + 8);
+ }
+ out[w*2-1] = stbi__div4(t1+2);
+
+ STBI_NOTUSED(hs);
+
+ return out;
+}
+
+#if defined(STBI_SSE2) || defined(STBI_NEON)
+static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // need to generate 2x2 samples for every one in input
+ int i=0,t0,t1;
+
+ if (w == 1) {
+ out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
+ return out;
+ }
+
+ t1 = 3*in_near[0] + in_far[0];
+ // process groups of 8 pixels for as long as we can.
+ // note we can't handle the last pixel in a row in this loop
+ // because we need to handle the filter boundary conditions.
+ for (; i < ((w-1) & ~7); i += 8) {
+#if defined(STBI_SSE2)
+ // load and perform the vertical filtering pass
+ // this uses 3*x + y = 4*x + (y - x)
+ __m128i zero = _mm_setzero_si128();
+ __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i));
+ __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i));
+ __m128i farw = _mm_unpacklo_epi8(farb, zero);
+ __m128i nearw = _mm_unpacklo_epi8(nearb, zero);
+ __m128i diff = _mm_sub_epi16(farw, nearw);
+ __m128i nears = _mm_slli_epi16(nearw, 2);
+ __m128i curr = _mm_add_epi16(nears, diff); // current row
+
+ // horizontal filter works the same based on shifted vers of current
+ // row. "prev" is current row shifted right by 1 pixel; we need to
+ // insert the previous pixel value (from t1).
+ // "next" is current row shifted left by 1 pixel, with first pixel
+ // of next block of 8 pixels added in.
+ __m128i prv0 = _mm_slli_si128(curr, 2);
+ __m128i nxt0 = _mm_srli_si128(curr, 2);
+ __m128i prev = _mm_insert_epi16(prv0, t1, 0);
+ __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7);
+
+ // horizontal filter, polyphase implementation since it's convenient:
+ // even pixels = 3*cur + prev = cur*4 + (prev - cur)
+ // odd pixels = 3*cur + next = cur*4 + (next - cur)
+ // note the shared term.
+ __m128i bias = _mm_set1_epi16(8);
+ __m128i curs = _mm_slli_epi16(curr, 2);
+ __m128i prvd = _mm_sub_epi16(prev, curr);
+ __m128i nxtd = _mm_sub_epi16(next, curr);
+ __m128i curb = _mm_add_epi16(curs, bias);
+ __m128i even = _mm_add_epi16(prvd, curb);
+ __m128i odd = _mm_add_epi16(nxtd, curb);
+
+ // interleave even and odd pixels, then undo scaling.
+ __m128i int0 = _mm_unpacklo_epi16(even, odd);
+ __m128i int1 = _mm_unpackhi_epi16(even, odd);
+ __m128i de0 = _mm_srli_epi16(int0, 4);
+ __m128i de1 = _mm_srli_epi16(int1, 4);
+
+ // pack and write output
+ __m128i outv = _mm_packus_epi16(de0, de1);
+ _mm_storeu_si128((__m128i *) (out + i*2), outv);
+#elif defined(STBI_NEON)
+ // load and perform the vertical filtering pass
+ // this uses 3*x + y = 4*x + (y - x)
+ uint8x8_t farb = vld1_u8(in_far + i);
+ uint8x8_t nearb = vld1_u8(in_near + i);
+ int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb));
+ int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2));
+ int16x8_t curr = vaddq_s16(nears, diff); // current row
+
+ // horizontal filter works the same based on shifted vers of current
+ // row. "prev" is current row shifted right by 1 pixel; we need to
+ // insert the previous pixel value (from t1).
+ // "next" is current row shifted left by 1 pixel, with first pixel
+ // of next block of 8 pixels added in.
+ int16x8_t prv0 = vextq_s16(curr, curr, 7);
+ int16x8_t nxt0 = vextq_s16(curr, curr, 1);
+ int16x8_t prev = vsetq_lane_s16(t1, prv0, 0);
+ int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7);
+
+ // horizontal filter, polyphase implementation since it's convenient:
+ // even pixels = 3*cur + prev = cur*4 + (prev - cur)
+ // odd pixels = 3*cur + next = cur*4 + (next - cur)
+ // note the shared term.
+ int16x8_t curs = vshlq_n_s16(curr, 2);
+ int16x8_t prvd = vsubq_s16(prev, curr);
+ int16x8_t nxtd = vsubq_s16(next, curr);
+ int16x8_t even = vaddq_s16(curs, prvd);
+ int16x8_t odd = vaddq_s16(curs, nxtd);
+
+ // undo scaling and round, then store with even/odd phases interleaved
+ uint8x8x2_t o;
+ o.val[0] = vqrshrun_n_s16(even, 4);
+ o.val[1] = vqrshrun_n_s16(odd, 4);
+ vst2_u8(out + i*2, o);
+#endif
+
+ // "previous" value for next iter
+ t1 = 3*in_near[i+7] + in_far[i+7];
+ }
+
+ t0 = t1;
+ t1 = 3*in_near[i] + in_far[i];
+ out[i*2] = stbi__div16(3*t1 + t0 + 8);
+
+ for (++i; i < w; ++i) {
+ t0 = t1;
+ t1 = 3*in_near[i]+in_far[i];
+ out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
+ out[i*2 ] = stbi__div16(3*t1 + t0 + 8);
+ }
+ out[w*2-1] = stbi__div4(t1+2);
+
+ STBI_NOTUSED(hs);
+
+ return out;
+}
+#endif
+
+static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // resample with nearest-neighbor
+ int i,j;
+ STBI_NOTUSED(in_far);
+ for (i=0; i < w; ++i)
+ for (j=0; j < hs; ++j)
+ out[i*hs+j] = in_near[i];
+ return out;
+}
+
+// this is a reduced-precision calculation of YCbCr-to-RGB introduced
+// to make sure the code produces the same results in both SIMD and scalar
+#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8)
+static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step)
+{
+ int i;
+ for (i=0; i < count; ++i) {
+ int y_fixed = (y[i] << 20) + (1<<19); // rounding
+ int r,g,b;
+ int cr = pcr[i] - 128;
+ int cb = pcb[i] - 128;
+ r = y_fixed + cr* stbi__float2fixed(1.40200f);
+ g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
+ b = y_fixed + cb* stbi__float2fixed(1.77200f);
+ r >>= 20;
+ g >>= 20;
+ b >>= 20;
+ if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+ if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+ if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+ out[0] = (stbi_uc)r;
+ out[1] = (stbi_uc)g;
+ out[2] = (stbi_uc)b;
+ out[3] = 255;
+ out += step;
+ }
+}
+
+#if defined(STBI_SSE2) || defined(STBI_NEON)
+static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step)
+{
+ int i = 0;
+
+#ifdef STBI_SSE2
+ // step == 3 is pretty ugly on the final interleave, and i'm not convinced
+ // it's useful in practice (you wouldn't use it for textures, for example).
+ // so just accelerate step == 4 case.
+ if (step == 4) {
+ // this is a fairly straightforward implementation and not super-optimized.
+ __m128i signflip = _mm_set1_epi8(-0x80);
+ __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f));
+ __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f));
+ __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f));
+ __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f));
+ __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128);
+ __m128i xw = _mm_set1_epi16(255); // alpha channel
+
+ for (; i+7 < count; i += 8) {
+ // load
+ __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i));
+ __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i));
+ __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i));
+ __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128
+ __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128
+
+ // unpack to short (and left-shift cr, cb by 8)
+ __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes);
+ __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased);
+ __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased);
+
+ // color transform
+ __m128i yws = _mm_srli_epi16(yw, 4);
+ __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw);
+ __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw);
+ __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1);
+ __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1);
+ __m128i rws = _mm_add_epi16(cr0, yws);
+ __m128i gwt = _mm_add_epi16(cb0, yws);
+ __m128i bws = _mm_add_epi16(yws, cb1);
+ __m128i gws = _mm_add_epi16(gwt, cr1);
+
+ // descale
+ __m128i rw = _mm_srai_epi16(rws, 4);
+ __m128i bw = _mm_srai_epi16(bws, 4);
+ __m128i gw = _mm_srai_epi16(gws, 4);
+
+ // back to byte, set up for transpose
+ __m128i brb = _mm_packus_epi16(rw, bw);
+ __m128i gxb = _mm_packus_epi16(gw, xw);
+
+ // transpose to interleave channels
+ __m128i t0 = _mm_unpacklo_epi8(brb, gxb);
+ __m128i t1 = _mm_unpackhi_epi8(brb, gxb);
+ __m128i o0 = _mm_unpacklo_epi16(t0, t1);
+ __m128i o1 = _mm_unpackhi_epi16(t0, t1);
+
+ // store
+ _mm_storeu_si128((__m128i *) (out + 0), o0);
+ _mm_storeu_si128((__m128i *) (out + 16), o1);
+ out += 32;
+ }
+ }
+#endif
+
+#ifdef STBI_NEON
+ // in this version, step=3 support would be easy to add. but is there demand?
+ if (step == 4) {
+ // this is a fairly straightforward implementation and not super-optimized.
+ uint8x8_t signflip = vdup_n_u8(0x80);
+ int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f));
+ int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f));
+ int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f));
+ int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f));
+
+ for (; i+7 < count; i += 8) {
+ // load
+ uint8x8_t y_bytes = vld1_u8(y + i);
+ uint8x8_t cr_bytes = vld1_u8(pcr + i);
+ uint8x8_t cb_bytes = vld1_u8(pcb + i);
+ int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip));
+ int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip));
+
+ // expand to s16
+ int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4));
+ int16x8_t crw = vshll_n_s8(cr_biased, 7);
+ int16x8_t cbw = vshll_n_s8(cb_biased, 7);
+
+ // color transform
+ int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0);
+ int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0);
+ int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1);
+ int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1);
+ int16x8_t rws = vaddq_s16(yws, cr0);
+ int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1);
+ int16x8_t bws = vaddq_s16(yws, cb1);
+
+ // undo scaling, round, convert to byte
+ uint8x8x4_t o;
+ o.val[0] = vqrshrun_n_s16(rws, 4);
+ o.val[1] = vqrshrun_n_s16(gws, 4);
+ o.val[2] = vqrshrun_n_s16(bws, 4);
+ o.val[3] = vdup_n_u8(255);
+
+ // store, interleaving r/g/b/a
+ vst4_u8(out, o);
+ out += 8*4;
+ }
+ }
+#endif
+
+ for (; i < count; ++i) {
+ int y_fixed = (y[i] << 20) + (1<<19); // rounding
+ int r,g,b;
+ int cr = pcr[i] - 128;
+ int cb = pcb[i] - 128;
+ r = y_fixed + cr* stbi__float2fixed(1.40200f);
+ g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
+ b = y_fixed + cb* stbi__float2fixed(1.77200f);
+ r >>= 20;
+ g >>= 20;
+ b >>= 20;
+ if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+ if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+ if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+ out[0] = (stbi_uc)r;
+ out[1] = (stbi_uc)g;
+ out[2] = (stbi_uc)b;
+ out[3] = 255;
+ out += step;
+ }
+}
+#endif
+
+// set up the kernels
+static void stbi__setup_jpeg(stbi__jpeg *j)
+{
+ j->idct_block_kernel = stbi__idct_block;
+ j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row;
+ j->resample_row_hv_2_kernel = stbi__resample_row_hv_2;
+
+#ifdef STBI_SSE2
+ if (stbi__sse2_available()) {
+ j->idct_block_kernel = stbi__idct_simd;
+ j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+ j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+ }
+#endif
+
+#ifdef STBI_NEON
+ j->idct_block_kernel = stbi__idct_simd;
+ j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+ j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+#endif
+}
+
+// clean up the temporary component buffers
+static void stbi__cleanup_jpeg(stbi__jpeg *j)
+{
+ stbi__free_jpeg_components(j, j->s->img_n, 0);
+}
+
+typedef struct
+{
+ resample_row_func resample;
+ stbi_uc *line0,*line1;
+ int hs,vs; // expansion factor in each axis
+ int w_lores; // horizontal pixels pre-expansion
+ int ystep; // how far through vertical expansion we are
+ int ypos; // which pre-expansion row we're on
+} stbi__resample;
+
+// fast 0..255 * 0..255 => 0..255 rounded multiplication
+static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y)
+{
+ unsigned int t = x*y + 128;
+ return (stbi_uc) ((t + (t >>8)) >> 8);
+}
+
+static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
+{
+ int n, decode_n, is_rgb;
+ z->s->img_n = 0; // make stbi__cleanup_jpeg safe
+
+ // validate req_comp
+ if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
+
+ // load a jpeg image from whichever source, but leave in YCbCr format
+ if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; }
+
+ // determine actual number of components to generate
+ n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1;
+
+ is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif));
+
+ if (z->s->img_n == 3 && n < 3 && !is_rgb)
+ decode_n = 1;
+ else
+ decode_n = z->s->img_n;
+
+ // nothing to do if no components requested; check this now to avoid
+ // accessing uninitialized coutput[0] later
+ if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; }
+
+ // resample and color-convert
+ {
+ int k;
+ unsigned int i,j;
+ stbi_uc *output;
+ stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL };
+
+ stbi__resample res_comp[4];
+
+ for (k=0; k < decode_n; ++k) {
+ stbi__resample *r = &res_comp[k];
+
+ // allocate line buffer big enough for upsampling off the edges
+ // with upsample factor of 4
+ z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3);
+ if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
+
+ r->hs = z->img_h_max / z->img_comp[k].h;
+ r->vs = z->img_v_max / z->img_comp[k].v;
+ r->ystep = r->vs >> 1;
+ r->w_lores = (z->s->img_x + r->hs-1) / r->hs;
+ r->ypos = 0;
+ r->line0 = r->line1 = z->img_comp[k].data;
+
+ if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;
+ else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2;
+ else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2;
+ else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel;
+ else r->resample = stbi__resample_row_generic;
+ }
+
+ // can't error after this so, this is safe
+ output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1);
+ if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
+
+ // now go ahead and resample
+ for (j=0; j < z->s->img_y; ++j) {
+ stbi_uc *out = output + n * z->s->img_x * j;
+ for (k=0; k < decode_n; ++k) {
+ stbi__resample *r = &res_comp[k];
+ int y_bot = r->ystep >= (r->vs >> 1);
+ coutput[k] = r->resample(z->img_comp[k].linebuf,
+ y_bot ? r->line1 : r->line0,
+ y_bot ? r->line0 : r->line1,
+ r->w_lores, r->hs);
+ if (++r->ystep >= r->vs) {
+ r->ystep = 0;
+ r->line0 = r->line1;
+ if (++r->ypos < z->img_comp[k].y)
+ r->line1 += z->img_comp[k].w2;
+ }
+ }
+ if (n >= 3) {
+ stbi_uc *y = coutput[0];
+ if (z->s->img_n == 3) {
+ if (is_rgb) {
+ for (i=0; i < z->s->img_x; ++i) {
+ out[0] = y[i];
+ out[1] = coutput[1][i];
+ out[2] = coutput[2][i];
+ out[3] = 255;
+ out += n;
+ }
+ } else {
+ z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+ }
+ } else if (z->s->img_n == 4) {
+ if (z->app14_color_transform == 0) { // CMYK
+ for (i=0; i < z->s->img_x; ++i) {
+ stbi_uc m = coutput[3][i];
+ out[0] = stbi__blinn_8x8(coutput[0][i], m);
+ out[1] = stbi__blinn_8x8(coutput[1][i], m);
+ out[2] = stbi__blinn_8x8(coutput[2][i], m);
+ out[3] = 255;
+ out += n;
+ }
+ } else if (z->app14_color_transform == 2) { // YCCK
+ z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+ for (i=0; i < z->s->img_x; ++i) {
+ stbi_uc m = coutput[3][i];
+ out[0] = stbi__blinn_8x8(255 - out[0], m);
+ out[1] = stbi__blinn_8x8(255 - out[1], m);
+ out[2] = stbi__blinn_8x8(255 - out[2], m);
+ out += n;
+ }
+ } else { // YCbCr + alpha? Ignore the fourth channel for now
+ z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+ }
+ } else
+ for (i=0; i < z->s->img_x; ++i) {
+ out[0] = out[1] = out[2] = y[i];
+ out[3] = 255; // not used if n==3
+ out += n;
+ }
+ } else {
+ if (is_rgb) {
+ if (n == 1)
+ for (i=0; i < z->s->img_x; ++i)
+ *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
+ else {
+ for (i=0; i < z->s->img_x; ++i, out += 2) {
+ out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
+ out[1] = 255;
+ }
+ }
+ } else if (z->s->img_n == 4 && z->app14_color_transform == 0) {
+ for (i=0; i < z->s->img_x; ++i) {
+ stbi_uc m = coutput[3][i];
+ stbi_uc r = stbi__blinn_8x8(coutput[0][i], m);
+ stbi_uc g = stbi__blinn_8x8(coutput[1][i], m);
+ stbi_uc b = stbi__blinn_8x8(coutput[2][i], m);
+ out[0] = stbi__compute_y(r, g, b);
+ out[1] = 255;
+ out += n;
+ }
+ } else if (z->s->img_n == 4 && z->app14_color_transform == 2) {
+ for (i=0; i < z->s->img_x; ++i) {
+ out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]);
+ out[1] = 255;
+ out += n;
+ }
+ } else {
+ stbi_uc *y = coutput[0];
+ if (n == 1)
+ for (i=0; i < z->s->img_x; ++i) out[i] = y[i];
+ else
+ for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; }
+ }
+ }
+ }
+ stbi__cleanup_jpeg(z);
+ *out_x = z->s->img_x;
+ *out_y = z->s->img_y;
+ if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output
+ return output;
+ }
+}
+
+static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+ unsigned char* result;
+ stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
+ if (!j) return stbi__errpuc("outofmem", "Out of memory");
+ memset(j, 0, sizeof(stbi__jpeg));
+ STBI_NOTUSED(ri);
+ j->s = s;
+ stbi__setup_jpeg(j);
+ result = load_jpeg_image(j, x,y,comp,req_comp);
+ STBI_FREE(j);
+ return result;
+}
+
+static int stbi__jpeg_test(stbi__context *s)
+{
+ int r;
+ stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));
+ if (!j) return stbi__err("outofmem", "Out of memory");
+ memset(j, 0, sizeof(stbi__jpeg));
+ j->s = s;
+ stbi__setup_jpeg(j);
+ r = stbi__decode_jpeg_header(j, STBI__SCAN_type);
+ stbi__rewind(s);
+ STBI_FREE(j);
+ return r;
+}
+
+static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)
+{
+ if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) {
+ stbi__rewind( j->s );
+ return 0;
+ }
+ if (x) *x = j->s->img_x;
+ if (y) *y = j->s->img_y;
+ if (comp) *comp = j->s->img_n >= 3 ? 3 : 1;
+ return 1;
+}
+
+static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int result;
+ stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
+ if (!j) return stbi__err("outofmem", "Out of memory");
+ memset(j, 0, sizeof(stbi__jpeg));
+ j->s = s;
+ result = stbi__jpeg_info_raw(j, x, y, comp);
+ STBI_FREE(j);
+ return result;
+}
+#endif
+
+// public domain zlib decode v0.2 Sean Barrett 2006-11-18
+// simple implementation
+// - all input must be provided in an upfront buffer
+// - all output is written to a single output buffer (can malloc/realloc)
+// performance
+// - fast huffman
+
+#ifndef STBI_NO_ZLIB
+
+// fast-way is faster to check than jpeg huffman, but slow way is slower
+#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables
+#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1)
+#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet
+
+// zlib-style huffman encoding
+// (jpegs packs from left, zlib from right, so can't share code)
+typedef struct
+{
+ stbi__uint16 fast[1 << STBI__ZFAST_BITS];
+ stbi__uint16 firstcode[16];
+ int maxcode[17];
+ stbi__uint16 firstsymbol[16];
+ stbi_uc size[STBI__ZNSYMS];
+ stbi__uint16 value[STBI__ZNSYMS];
+} stbi__zhuffman;
+
+stbi_inline static int stbi__bitreverse16(int n)
+{
+ n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
+ n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
+ n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
+ n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
+ return n;
+}
+
+stbi_inline static int stbi__bit_reverse(int v, int bits)
+{
+ STBI_ASSERT(bits <= 16);
+ // to bit reverse n bits, reverse 16 and shift
+ // e.g. 11 bits, bit reverse and shift away 5
+ return stbi__bitreverse16(v) >> (16-bits);
+}
+
+static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num)
+{
+ int i,k=0;
+ int code, next_code[16], sizes[17];
+
+ // DEFLATE spec for generating codes
+ memset(sizes, 0, sizeof(sizes));
+ memset(z->fast, 0, sizeof(z->fast));
+ for (i=0; i < num; ++i)
+ ++sizes[sizelist[i]];
+ sizes[0] = 0;
+ for (i=1; i < 16; ++i)
+ if (sizes[i] > (1 << i))
+ return stbi__err("bad sizes", "Corrupt PNG");
+ code = 0;
+ for (i=1; i < 16; ++i) {
+ next_code[i] = code;
+ z->firstcode[i] = (stbi__uint16) code;
+ z->firstsymbol[i] = (stbi__uint16) k;
+ code = (code + sizes[i]);
+ if (sizes[i])
+ if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG");
+ z->maxcode[i] = code << (16-i); // preshift for inner loop
+ code <<= 1;
+ k += sizes[i];
+ }
+ z->maxcode[16] = 0x10000; // sentinel
+ for (i=0; i < num; ++i) {
+ int s = sizelist[i];
+ if (s) {
+ int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];
+ stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i);
+ z->size [c] = (stbi_uc ) s;
+ z->value[c] = (stbi__uint16) i;
+ if (s <= STBI__ZFAST_BITS) {
+ int j = stbi__bit_reverse(next_code[s],s);
+ while (j < (1 << STBI__ZFAST_BITS)) {
+ z->fast[j] = fastv;
+ j += (1 << s);
+ }
+ }
+ ++next_code[s];
+ }
+ }
+ return 1;
+}
+
+// zlib-from-memory implementation for PNG reading
+// because PNG allows splitting the zlib stream arbitrarily,
+// and it's annoying structurally to have PNG call ZLIB call PNG,
+// we require PNG read all the IDATs and combine them into a single
+// memory buffer
+
+typedef struct
+{
+ stbi_uc *zbuffer, *zbuffer_end;
+ int num_bits;
+ stbi__uint32 code_buffer;
+
+ char *zout;
+ char *zout_start;
+ char *zout_end;
+ int z_expandable;
+
+ stbi__zhuffman z_length, z_distance;
+} stbi__zbuf;
+
+stbi_inline static int stbi__zeof(stbi__zbuf *z)
+{
+ return (z->zbuffer >= z->zbuffer_end);
+}
+
+stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z)
+{
+ return stbi__zeof(z) ? 0 : *z->zbuffer++;
+}
+
+static void stbi__fill_bits(stbi__zbuf *z)
+{
+ do {
+ if (z->code_buffer >= (1U << z->num_bits)) {
+ z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */
+ return;
+ }
+ z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits;
+ z->num_bits += 8;
+ } while (z->num_bits <= 24);
+}
+
+stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n)
+{
+ unsigned int k;
+ if (z->num_bits < n) stbi__fill_bits(z);
+ k = z->code_buffer & ((1 << n) - 1);
+ z->code_buffer >>= n;
+ z->num_bits -= n;
+ return k;
+}
+
+static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)
+{
+ int b,s,k;
+ // not resolved by fast table, so compute it the slow way
+ // use jpeg approach, which requires MSbits at top
+ k = stbi__bit_reverse(a->code_buffer, 16);
+ for (s=STBI__ZFAST_BITS+1; ; ++s)
+ if (k < z->maxcode[s])
+ break;
+ if (s >= 16) return -1; // invalid code!
+ // code size is s, so:
+ b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];
+ if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere!
+ if (z->size[b] != s) return -1; // was originally an assert, but report failure instead.
+ a->code_buffer >>= s;
+ a->num_bits -= s;
+ return z->value[b];
+}
+
+stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
+{
+ int b,s;
+ if (a->num_bits < 16) {
+ if (stbi__zeof(a)) {
+ return -1; /* report error for unexpected end of data. */
+ }
+ stbi__fill_bits(a);
+ }
+ b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
+ if (b) {
+ s = b >> 9;
+ a->code_buffer >>= s;
+ a->num_bits -= s;
+ return b & 511;
+ }
+ return stbi__zhuffman_decode_slowpath(a, z);
+}
+
+static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes
+{
+ char *q;
+ unsigned int cur, limit, old_limit;
+ z->zout = zout;
+ if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG");
+ cur = (unsigned int) (z->zout - z->zout_start);
+ limit = old_limit = (unsigned) (z->zout_end - z->zout_start);
+ if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory");
+ while (cur + n > limit) {
+ if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory");
+ limit *= 2;
+ }
+ q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);
+ STBI_NOTUSED(old_limit);
+ if (q == NULL) return stbi__err("outofmem", "Out of memory");
+ z->zout_start = q;
+ z->zout = q + cur;
+ z->zout_end = q + limit;
+ return 1;
+}
+
+static const int stbi__zlength_base[31] = {
+ 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,0,0 };
+
+static const int stbi__zlength_extra[31]=
+{ 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,0,0 };
+
+static const int stbi__zdist_base[32] = { 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,0,0};
+
+static const int stbi__zdist_extra[32] =
+{ 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};
+
+static int stbi__parse_huffman_block(stbi__zbuf *a)
+{
+ char *zout = a->zout;
+ for(;;) {
+ int z = stbi__zhuffman_decode(a, &a->z_length);
+ if (z < 256) {
+ if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes
+ if (zout >= a->zout_end) {
+ if (!stbi__zexpand(a, zout, 1)) return 0;
+ zout = a->zout;
+ }
+ *zout++ = (char) z;
+ } else {
+ stbi_uc *p;
+ int len,dist;
+ if (z == 256) {
+ a->zout = zout;
+ return 1;
+ }
+ if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
+ z -= 257;
+ len = stbi__zlength_base[z];
+ if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);
+ z = stbi__zhuffman_decode(a, &a->z_distance);
+ if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data
+ dist = stbi__zdist_base[z];
+ if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
+ if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
+ if (zout + len > a->zout_end) {
+ if (!stbi__zexpand(a, zout, len)) return 0;
+ zout = a->zout;
+ }
+ p = (stbi_uc *) (zout - dist);
+ if (dist == 1) { // run of one byte; common in images.
+ stbi_uc v = *p;
+ if (len) { do *zout++ = v; while (--len); }
+ } else {
+ if (len) { do *zout++ = *p++; while (--len); }
+ }
+ }
+ }
+}
+
+static int stbi__compute_huffman_codes(stbi__zbuf *a)
+{
+ static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
+ stbi__zhuffman z_codelength;
+ stbi_uc lencodes[286+32+137];//padding for maximum single op
+ stbi_uc codelength_sizes[19];
+ int i,n;
+
+ int hlit = stbi__zreceive(a,5) + 257;
+ int hdist = stbi__zreceive(a,5) + 1;
+ int hclen = stbi__zreceive(a,4) + 4;
+ int ntot = hlit + hdist;
+
+ memset(codelength_sizes, 0, sizeof(codelength_sizes));
+ for (i=0; i < hclen; ++i) {
+ int s = stbi__zreceive(a,3);
+ codelength_sizes[length_dezigzag[i]] = (stbi_uc) s;
+ }
+ if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;
+
+ n = 0;
+ while (n < ntot) {
+ int c = stbi__zhuffman_decode(a, &z_codelength);
+ if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG");
+ if (c < 16)
+ lencodes[n++] = (stbi_uc) c;
+ else {
+ stbi_uc fill = 0;
+ if (c == 16) {
+ c = stbi__zreceive(a,2)+3;
+ if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG");
+ fill = lencodes[n-1];
+ } else if (c == 17) {
+ c = stbi__zreceive(a,3)+3;
+ } else if (c == 18) {
+ c = stbi__zreceive(a,7)+11;
+ } else {
+ return stbi__err("bad codelengths", "Corrupt PNG");
+ }
+ if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG");
+ memset(lencodes+n, fill, c);
+ n += c;
+ }
+ }
+ if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG");
+ if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;
+ if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;
+ return 1;
+}
+
+static int stbi__parse_uncompressed_block(stbi__zbuf *a)
+{
+ stbi_uc header[4];
+ int len,nlen,k;
+ if (a->num_bits & 7)
+ stbi__zreceive(a, a->num_bits & 7); // discard
+ // drain the bit-packed data into header
+ k = 0;
+ while (a->num_bits > 0) {
+ header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check
+ a->code_buffer >>= 8;
+ a->num_bits -= 8;
+ }
+ if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG");
+ // now fill header the normal way
+ while (k < 4)
+ header[k++] = stbi__zget8(a);
+ len = header[1] * 256 + header[0];
+ nlen = header[3] * 256 + header[2];
+ if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG");
+ if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG");
+ if (a->zout + len > a->zout_end)
+ if (!stbi__zexpand(a, a->zout, len)) return 0;
+ memcpy(a->zout, a->zbuffer, len);
+ a->zbuffer += len;
+ a->zout += len;
+ return 1;
+}
+
+static int stbi__parse_zlib_header(stbi__zbuf *a)
+{
+ int cmf = stbi__zget8(a);
+ int cm = cmf & 15;
+ /* int cinfo = cmf >> 4; */
+ int flg = stbi__zget8(a);
+ if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
+ if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
+ if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png
+ if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png
+ // window = 1 << (8 + cinfo)... but who cares, we fully buffer output
+ return 1;
+}
+
+static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] =
+{
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8
+};
+static const stbi_uc stbi__zdefault_distance[32] =
+{
+ 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5
+};
+/*
+Init algorithm:
+{
+ int i; // use <= to match clearly with spec
+ for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8;
+ for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9;
+ for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7;
+ for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8;
+
+ for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5;
+}
+*/
+
+static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
+{
+ int final, type;
+ if (parse_header)
+ if (!stbi__parse_zlib_header(a)) return 0;
+ a->num_bits = 0;
+ a->code_buffer = 0;
+ do {
+ final = stbi__zreceive(a,1);
+ type = stbi__zreceive(a,2);
+ if (type == 0) {
+ if (!stbi__parse_uncompressed_block(a)) return 0;
+ } else if (type == 3) {
+ return 0;
+ } else {
+ if (type == 1) {
+ // use fixed code lengths
+ if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0;
+ if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0;
+ } else {
+ if (!stbi__compute_huffman_codes(a)) return 0;
+ }
+ if (!stbi__parse_huffman_block(a)) return 0;
+ }
+ } while (!final);
+ return 1;
+}
+
+static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)
+{
+ a->zout_start = obuf;
+ a->zout = obuf;
+ a->zout_end = obuf + olen;
+ a->z_expandable = exp;
+
+ return stbi__parse_zlib(a, parse_header);
+}
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)
+{
+ stbi__zbuf a;
+ char *p = (char *) stbi__malloc(initial_size);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi_uc *) buffer;
+ a.zbuffer_end = (stbi_uc *) buffer + len;
+ if (stbi__do_zlib(&a, p, initial_size, 1, 1)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ STBI_FREE(a.zout_start);
+ return NULL;
+ }
+}
+
+STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)
+{
+ return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
+}
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)
+{
+ stbi__zbuf a;
+ char *p = (char *) stbi__malloc(initial_size);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi_uc *) buffer;
+ a.zbuffer_end = (stbi_uc *) buffer + len;
+ if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ STBI_FREE(a.zout_start);
+ return NULL;
+ }
+}
+
+STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)
+{
+ stbi__zbuf a;
+ a.zbuffer = (stbi_uc *) ibuffer;
+ a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
+ if (stbi__do_zlib(&a, obuffer, olen, 0, 1))
+ return (int) (a.zout - a.zout_start);
+ else
+ return -1;
+}
+
+STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)
+{
+ stbi__zbuf a;
+ char *p = (char *) stbi__malloc(16384);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi_uc *) buffer;
+ a.zbuffer_end = (stbi_uc *) buffer+len;
+ if (stbi__do_zlib(&a, p, 16384, 1, 0)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ STBI_FREE(a.zout_start);
+ return NULL;
+ }
+}
+
+STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)
+{
+ stbi__zbuf a;
+ a.zbuffer = (stbi_uc *) ibuffer;
+ a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
+ if (stbi__do_zlib(&a, obuffer, olen, 0, 0))
+ return (int) (a.zout - a.zout_start);
+ else
+ return -1;
+}
+#endif
+
+// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18
+// simple implementation
+// - only 8-bit samples
+// - no CRC checking
+// - allocates lots of intermediate memory
+// - avoids problem of streaming data between subsystems
+// - avoids explicit window management
+// performance
+// - uses stb_zlib, a PD zlib implementation with fast huffman decoding
+
+#ifndef STBI_NO_PNG
+typedef struct
+{
+ stbi__uint32 length;
+ stbi__uint32 type;
+} stbi__pngchunk;
+
+static stbi__pngchunk stbi__get_chunk_header(stbi__context *s)
+{
+ stbi__pngchunk c;
+ c.length = stbi__get32be(s);
+ c.type = stbi__get32be(s);
+ return c;
+}
+
+static int stbi__check_png_header(stbi__context *s)
+{
+ static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 };
+ int i;
+ for (i=0; i < 8; ++i)
+ if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG");
+ return 1;
+}
+
+typedef struct
+{
+ stbi__context *s;
+ stbi_uc *idata, *expanded, *out;
+ int depth;
+} stbi__png;
+
+
+enum {
+ STBI__F_none=0,
+ STBI__F_sub=1,
+ STBI__F_up=2,
+ STBI__F_avg=3,
+ STBI__F_paeth=4,
+ // synthetic filters used for first scanline to avoid needing a dummy row of 0s
+ STBI__F_avg_first,
+ STBI__F_paeth_first
+};
+
+static stbi_uc first_row_filter[5] =
+{
+ STBI__F_none,
+ STBI__F_sub,
+ STBI__F_none,
+ STBI__F_avg_first,
+ STBI__F_paeth_first
+};
+
+static int stbi__paeth(int a, int b, int c)
+{
+ int p = a + b - c;
+ int pa = abs(p-a);
+ int pb = abs(p-b);
+ int pc = abs(p-c);
+ if (pa <= pb && pa <= pc) return a;
+ if (pb <= pc) return b;
+ return c;
+}
+
+static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
+
+// create the png data from post-deflated data
+static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
+{
+ int bytes = (depth == 16? 2 : 1);
+ stbi__context *s = a->s;
+ stbi__uint32 i,j,stride = x*out_n*bytes;
+ stbi__uint32 img_len, img_width_bytes;
+ int k;
+ int img_n = s->img_n; // copy it into a local for later
+
+ int output_bytes = out_n*bytes;
+ int filter_bytes = img_n*bytes;
+ int width = x;
+
+ STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
+ a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
+ if (!a->out) return stbi__err("outofmem", "Out of memory");
+
+ if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
+ img_width_bytes = (((img_n * x * depth) + 7) >> 3);
+ img_len = (img_width_bytes + 1) * y;
+
+ // we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
+ // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros),
+ // so just check for raw_len < img_len always.
+ if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
+
+ for (j=0; j < y; ++j) {
+ stbi_uc *cur = a->out + stride*j;
+ stbi_uc *prior;
+ int filter = *raw++;
+
+ if (filter > 4)
+ return stbi__err("invalid filter","Corrupt PNG");
+
+ if (depth < 8) {
+ if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG");
+ cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
+ filter_bytes = 1;
+ width = img_width_bytes;
+ }
+ prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
+
+ // if first row, use special filter that doesn't sample previous row
+ if (j == 0) filter = first_row_filter[filter];
+
+ // handle first byte explicitly
+ for (k=0; k < filter_bytes; ++k) {
+ switch (filter) {
+ case STBI__F_none : cur[k] = raw[k]; break;
+ case STBI__F_sub : cur[k] = raw[k]; break;
+ case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
+ case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
+ case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
+ case STBI__F_avg_first : cur[k] = raw[k]; break;
+ case STBI__F_paeth_first: cur[k] = raw[k]; break;
+ }
+ }
+
+ if (depth == 8) {
+ if (img_n != out_n)
+ cur[img_n] = 255; // first pixel
+ raw += img_n;
+ cur += out_n;
+ prior += out_n;
+ } else if (depth == 16) {
+ if (img_n != out_n) {
+ cur[filter_bytes] = 255; // first pixel top byte
+ cur[filter_bytes+1] = 255; // first pixel bottom byte
+ }
+ raw += filter_bytes;
+ cur += output_bytes;
+ prior += output_bytes;
+ } else {
+ raw += 1;
+ cur += 1;
+ prior += 1;
+ }
+
+ // this is a little gross, so that we don't switch per-pixel or per-component
+ if (depth < 8 || img_n == out_n) {
+ int nk = (width - 1)*filter_bytes;
+ #define STBI__CASE(f) \
+ case f: \
+ for (k=0; k < nk; ++k)
+ switch (filter) {
+ // "none" filter turns into a memcpy here; make that explicit.
+ case STBI__F_none: memcpy(cur, raw, nk); break;
+ STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
+ STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
+ STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
+ STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
+ STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
+ STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
+ }
+ #undef STBI__CASE
+ raw += nk;
+ } else {
+ STBI_ASSERT(img_n+1 == out_n);
+ #define STBI__CASE(f) \
+ case f: \
+ for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
+ for (k=0; k < filter_bytes; ++k)
+ switch (filter) {
+ STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break;
+ STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
+ STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
+ STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
+ STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
+ STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
+ STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
+ }
+ #undef STBI__CASE
+
+ // the loop above sets the high byte of the pixels' alpha, but for
+ // 16 bit png files we also need the low byte set. we'll do that here.
+ if (depth == 16) {
+ cur = a->out + stride*j; // start at the beginning of the row again
+ for (i=0; i < x; ++i,cur+=output_bytes) {
+ cur[filter_bytes+1] = 255;
+ }
+ }
+ }
+ }
+
+ // we make a separate pass to expand bits to pixels; for performance,
+ // this could run two scanlines behind the above code, so it won't
+ // intefere with filtering but will still be in the cache.
+ if (depth < 8) {
+ for (j=0; j < y; ++j) {
+ stbi_uc *cur = a->out + stride*j;
+ stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
+ // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
+ // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
+ stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
+
+ // note that the final byte might overshoot and write more data than desired.
+ // we can allocate enough data that this never writes out of memory, but it
+ // could also overwrite the next scanline. can it overwrite non-empty data
+ // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
+ // so we need to explicitly clamp the final ones
+
+ if (depth == 4) {
+ for (k=x*img_n; k >= 2; k-=2, ++in) {
+ *cur++ = scale * ((*in >> 4) );
+ *cur++ = scale * ((*in ) & 0x0f);
+ }
+ if (k > 0) *cur++ = scale * ((*in >> 4) );
+ } else if (depth == 2) {
+ for (k=x*img_n; k >= 4; k-=4, ++in) {
+ *cur++ = scale * ((*in >> 6) );
+ *cur++ = scale * ((*in >> 4) & 0x03);
+ *cur++ = scale * ((*in >> 2) & 0x03);
+ *cur++ = scale * ((*in ) & 0x03);
+ }
+ if (k > 0) *cur++ = scale * ((*in >> 6) );
+ if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
+ if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
+ } else if (depth == 1) {
+ for (k=x*img_n; k >= 8; k-=8, ++in) {
+ *cur++ = scale * ((*in >> 7) );
+ *cur++ = scale * ((*in >> 6) & 0x01);
+ *cur++ = scale * ((*in >> 5) & 0x01);
+ *cur++ = scale * ((*in >> 4) & 0x01);
+ *cur++ = scale * ((*in >> 3) & 0x01);
+ *cur++ = scale * ((*in >> 2) & 0x01);
+ *cur++ = scale * ((*in >> 1) & 0x01);
+ *cur++ = scale * ((*in ) & 0x01);
+ }
+ if (k > 0) *cur++ = scale * ((*in >> 7) );
+ if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
+ if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
+ if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
+ if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
+ if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
+ if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
+ }
+ if (img_n != out_n) {
+ int q;
+ // insert alpha = 255
+ cur = a->out + stride*j;
+ if (img_n == 1) {
+ for (q=x-1; q >= 0; --q) {
+ cur[q*2+1] = 255;
+ cur[q*2+0] = cur[q];
+ }
+ } else {
+ STBI_ASSERT(img_n == 3);
+ for (q=x-1; q >= 0; --q) {
+ cur[q*4+3] = 255;
+ cur[q*4+2] = cur[q*3+2];
+ cur[q*4+1] = cur[q*3+1];
+ cur[q*4+0] = cur[q*3+0];
+ }
+ }
+ }
+ }
+ } else if (depth == 16) {
+ // force the image data from big-endian to platform-native.
+ // this is done in a separate pass due to the decoding relying
+ // on the data being untouched, but could probably be done
+ // per-line during decode if care is taken.
+ stbi_uc *cur = a->out;
+ stbi__uint16 *cur16 = (stbi__uint16*)cur;
+
+ for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
+ *cur16 = (cur[0] << 8) | cur[1];
+ }
+ }
+
+ return 1;
+}
+
+static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced)
+{
+ int bytes = (depth == 16 ? 2 : 1);
+ int out_bytes = out_n * bytes;
+ stbi_uc *final;
+ int p;
+ if (!interlaced)
+ return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);
+
+ // de-interlacing
+ final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);
+ if (!final) return stbi__err("outofmem", "Out of memory");
+ for (p=0; p < 7; ++p) {
+ int xorig[] = { 0,4,0,2,0,1,0 };
+ int yorig[] = { 0,0,4,0,2,0,1 };
+ int xspc[] = { 8,8,4,4,2,2,1 };
+ int yspc[] = { 8,8,8,4,4,2,2 };
+ int i,j,x,y;
+ // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1
+ x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];
+ y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];
+ if (x && y) {
+ stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;
+ if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {
+ STBI_FREE(final);
+ return 0;
+ }
+ for (j=0; j < y; ++j) {
+ for (i=0; i < x; ++i) {
+ int out_y = j*yspc[p]+yorig[p];
+ int out_x = i*xspc[p]+xorig[p];
+ memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes,
+ a->out + (j*x+i)*out_bytes, out_bytes);
+ }
+ }
+ STBI_FREE(a->out);
+ image_data += img_len;
+ image_data_len -= img_len;
+ }
+ }
+ a->out = final;
+
+ return 1;
+}
+
+static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)
+{
+ stbi__context *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi_uc *p = z->out;
+
+ // compute color-based transparency, assuming we've
+ // already got 255 as the alpha value in the output
+ STBI_ASSERT(out_n == 2 || out_n == 4);
+
+ if (out_n == 2) {
+ for (i=0; i < pixel_count; ++i) {
+ p[1] = (p[0] == tc[0] ? 0 : 255);
+ p += 2;
+ }
+ } else {
+ for (i=0; i < pixel_count; ++i) {
+ if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+ p[3] = 0;
+ p += 4;
+ }
+ }
+ return 1;
+}
+
+static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)
+{
+ stbi__context *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi__uint16 *p = (stbi__uint16*) z->out;
+
+ // compute color-based transparency, assuming we've
+ // already got 65535 as the alpha value in the output
+ STBI_ASSERT(out_n == 2 || out_n == 4);
+
+ if (out_n == 2) {
+ for (i = 0; i < pixel_count; ++i) {
+ p[1] = (p[0] == tc[0] ? 0 : 65535);
+ p += 2;
+ }
+ } else {
+ for (i = 0; i < pixel_count; ++i) {
+ if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+ p[3] = 0;
+ p += 4;
+ }
+ }
+ return 1;
+}
+
+static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)
+{
+ stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
+ stbi_uc *p, *temp_out, *orig = a->out;
+
+ p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0);
+ if (p == NULL) return stbi__err("outofmem", "Out of memory");
+
+ // between here and free(out) below, exitting would leak
+ temp_out = p;
+
+ if (pal_img_n == 3) {
+ for (i=0; i < pixel_count; ++i) {
+ int n = orig[i]*4;
+ p[0] = palette[n ];
+ p[1] = palette[n+1];
+ p[2] = palette[n+2];
+ p += 3;
+ }
+ } else {
+ for (i=0; i < pixel_count; ++i) {
+ int n = orig[i]*4;
+ p[0] = palette[n ];
+ p[1] = palette[n+1];
+ p[2] = palette[n+2];
+ p[3] = palette[n+3];
+ p += 4;
+ }
+ }
+ STBI_FREE(a->out);
+ a->out = temp_out;
+
+ STBI_NOTUSED(len);
+
+ return 1;
+}
+
+static int stbi__unpremultiply_on_load_global = 0;
+static int stbi__de_iphone_flag_global = 0;
+
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
+{
+ stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply;
+}
+
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
+{
+ stbi__de_iphone_flag_global = flag_true_if_should_convert;
+}
+
+#ifndef STBI_THREAD_LOCAL
+#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global
+#define stbi__de_iphone_flag stbi__de_iphone_flag_global
+#else
+static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set;
+static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set;
+
+STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)
+{
+ stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply;
+ stbi__unpremultiply_on_load_set = 1;
+}
+
+STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert)
+{
+ stbi__de_iphone_flag_local = flag_true_if_should_convert;
+ stbi__de_iphone_flag_set = 1;
+}
+
+#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \
+ ? stbi__unpremultiply_on_load_local \
+ : stbi__unpremultiply_on_load_global)
+#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \
+ ? stbi__de_iphone_flag_local \
+ : stbi__de_iphone_flag_global)
+#endif // STBI_THREAD_LOCAL
+
+static void stbi__de_iphone(stbi__png *z)
+{
+ stbi__context *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi_uc *p = z->out;
+
+ if (s->img_out_n == 3) { // convert bgr to rgb
+ for (i=0; i < pixel_count; ++i) {
+ stbi_uc t = p[0];
+ p[0] = p[2];
+ p[2] = t;
+ p += 3;
+ }
+ } else {
+ STBI_ASSERT(s->img_out_n == 4);
+ if (stbi__unpremultiply_on_load) {
+ // convert bgr to rgb and unpremultiply
+ for (i=0; i < pixel_count; ++i) {
+ stbi_uc a = p[3];
+ stbi_uc t = p[0];
+ if (a) {
+ stbi_uc half = a / 2;
+ p[0] = (p[2] * 255 + half) / a;
+ p[1] = (p[1] * 255 + half) / a;
+ p[2] = ( t * 255 + half) / a;
+ } else {
+ p[0] = p[2];
+ p[2] = t;
+ }
+ p += 4;
+ }
+ } else {
+ // convert bgr to rgb
+ for (i=0; i < pixel_count; ++i) {
+ stbi_uc t = p[0];
+ p[0] = p[2];
+ p[2] = t;
+ p += 4;
+ }
+ }
+ }
+}
+
+#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d))
+
+static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
+{
+ stbi_uc palette[1024], pal_img_n=0;
+ stbi_uc has_trans=0, tc[3]={0};
+ stbi__uint16 tc16[3];
+ stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
+ int first=1,k,interlace=0, color=0, is_iphone=0;
+ stbi__context *s = z->s;
+
+ z->expanded = NULL;
+ z->idata = NULL;
+ z->out = NULL;
+
+ if (!stbi__check_png_header(s)) return 0;
+
+ if (scan == STBI__SCAN_type) return 1;
+
+ for (;;) {
+ stbi__pngchunk c = stbi__get_chunk_header(s);
+ switch (c.type) {
+ case STBI__PNG_TYPE('C','g','B','I'):
+ is_iphone = 1;
+ stbi__skip(s, c.length);
+ break;
+ case STBI__PNG_TYPE('I','H','D','R'): {
+ int comp,filter;
+ if (!first) return stbi__err("multiple IHDR","Corrupt PNG");
+ first = 0;
+ if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG");
+ s->img_x = stbi__get32be(s);
+ s->img_y = stbi__get32be(s);
+ if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
+ if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
+ z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only");
+ color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG");
+ if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG");
+ if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG");
+ comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG");
+ filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG");
+ interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG");
+ if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG");
+ if (!pal_img_n) {
+ s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
+ if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
+ } else {
+ // if paletted, then pal_n is our final components, and
+ // img_n is # components to decompress/filter.
+ s->img_n = 1;
+ if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG");
+ }
+ // even with SCAN_header, have to scan to see if we have a tRNS
+ break;
+ }
+
+ case STBI__PNG_TYPE('P','L','T','E'): {
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG");
+ pal_len = c.length / 3;
+ if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG");
+ for (i=0; i < pal_len; ++i) {
+ palette[i*4+0] = stbi__get8(s);
+ palette[i*4+1] = stbi__get8(s);
+ palette[i*4+2] = stbi__get8(s);
+ palette[i*4+3] = 255;
+ }
+ break;
+ }
+
+ case STBI__PNG_TYPE('t','R','N','S'): {
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG");
+ if (pal_img_n) {
+ if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; }
+ if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG");
+ if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG");
+ pal_img_n = 4;
+ for (i=0; i < c.length; ++i)
+ palette[i*4+3] = stbi__get8(s);
+ } else {
+ if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG");
+ if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG");
+ has_trans = 1;
+ // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.
+ if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }
+ if (z->depth == 16) {
+ for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
+ } else {
+ for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
+ }
+ }
+ break;
+ }
+
+ case STBI__PNG_TYPE('I','D','A','T'): {
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG");
+ if (scan == STBI__SCAN_header) {
+ // header scan definitely stops at first IDAT
+ if (pal_img_n)
+ s->img_n = pal_img_n;
+ return 1;
+ }
+ if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes");
+ if ((int)(ioff + c.length) < (int)ioff) return 0;
+ if (ioff + c.length > idata_limit) {
+ stbi__uint32 idata_limit_old = idata_limit;
+ stbi_uc *p;
+ if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
+ while (ioff + c.length > idata_limit)
+ idata_limit *= 2;
+ STBI_NOTUSED(idata_limit_old);
+ p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory");
+ z->idata = p;
+ }
+ if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG");
+ ioff += c.length;
+ break;
+ }
+
+ case STBI__PNG_TYPE('I','E','N','D'): {
+ stbi__uint32 raw_len, bpl;
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if (scan != STBI__SCAN_load) return 1;
+ if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG");
+ // initial guess for decoded data size to avoid unnecessary reallocs
+ bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component
+ raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;
+ z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);
+ if (z->expanded == NULL) return 0; // zlib should set error
+ STBI_FREE(z->idata); z->idata = NULL;
+ if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
+ s->img_out_n = s->img_n+1;
+ else
+ s->img_out_n = s->img_n;
+ if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;
+ if (has_trans) {
+ if (z->depth == 16) {
+ if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;
+ } else {
+ if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;
+ }
+ }
+ if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)
+ stbi__de_iphone(z);
+ if (pal_img_n) {
+ // pal_img_n == 3 or 4
+ s->img_n = pal_img_n; // record the actual colors we had
+ s->img_out_n = pal_img_n;
+ if (req_comp >= 3) s->img_out_n = req_comp;
+ if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n))
+ return 0;
+ } else if (has_trans) {
+ // non-paletted image with tRNS -> source image has (constant) alpha
+ ++s->img_n;
+ }
+ STBI_FREE(z->expanded); z->expanded = NULL;
+ // end of PNG chunk, read and skip CRC
+ stbi__get32be(s);
+ return 1;
+ }
+
+ default:
+ // if critical, fail
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if ((c.type & (1 << 29)) == 0) {
+ #ifndef STBI_NO_FAILURE_STRINGS
+ // not threadsafe
+ static char invalid_chunk[] = "XXXX PNG chunk not known";
+ invalid_chunk[0] = STBI__BYTECAST(c.type >> 24);
+ invalid_chunk[1] = STBI__BYTECAST(c.type >> 16);
+ invalid_chunk[2] = STBI__BYTECAST(c.type >> 8);
+ invalid_chunk[3] = STBI__BYTECAST(c.type >> 0);
+ #endif
+ return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type");
+ }
+ stbi__skip(s, c.length);
+ break;
+ }
+ // end of PNG chunk, read and skip CRC
+ stbi__get32be(s);
+ }
+}
+
+static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri)
+{
+ void *result=NULL;
+ if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
+ if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
+ if (p->depth <= 8)
+ ri->bits_per_channel = 8;
+ else if (p->depth == 16)
+ ri->bits_per_channel = 16;
+ else
+ return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth");
+ result = p->out;
+ p->out = NULL;
+ if (req_comp && req_comp != p->s->img_out_n) {
+ if (ri->bits_per_channel == 8)
+ result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+ else
+ result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+ p->s->img_out_n = req_comp;
+ if (result == NULL) return result;
+ }
+ *x = p->s->img_x;
+ *y = p->s->img_y;
+ if (n) *n = p->s->img_n;
+ }
+ STBI_FREE(p->out); p->out = NULL;
+ STBI_FREE(p->expanded); p->expanded = NULL;
+ STBI_FREE(p->idata); p->idata = NULL;
+
+ return result;
+}
+
+static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+ stbi__png p;
+ p.s = s;
+ return stbi__do_png(&p, x,y,comp,req_comp, ri);
+}
+
+static int stbi__png_test(stbi__context *s)
+{
+ int r;
+ r = stbi__check_png_header(s);
+ stbi__rewind(s);
+ return r;
+}
+
+static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp)
+{
+ if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) {
+ stbi__rewind( p->s );
+ return 0;
+ }
+ if (x) *x = p->s->img_x;
+ if (y) *y = p->s->img_y;
+ if (comp) *comp = p->s->img_n;
+ return 1;
+}
+
+static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ stbi__png p;
+ p.s = s;
+ return stbi__png_info_raw(&p, x, y, comp);
+}
+
+static int stbi__png_is16(stbi__context *s)
+{
+ stbi__png p;
+ p.s = s;
+ if (!stbi__png_info_raw(&p, NULL, NULL, NULL))
+ return 0;
+ if (p.depth != 16) {
+ stbi__rewind(p.s);
+ return 0;
+ }
+ return 1;
+}
+#endif
+
+// Microsoft/Windows BMP image
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_test_raw(stbi__context *s)
+{
+ int r;
+ int sz;
+ if (stbi__get8(s) != 'B') return 0;
+ if (stbi__get8(s) != 'M') return 0;
+ stbi__get32le(s); // discard filesize
+ stbi__get16le(s); // discard reserved
+ stbi__get16le(s); // discard reserved
+ stbi__get32le(s); // discard data offset
+ sz = stbi__get32le(s);
+ r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124);
+ return r;
+}
+
+static int stbi__bmp_test(stbi__context *s)
+{
+ int r = stbi__bmp_test_raw(s);
+ stbi__rewind(s);
+ return r;
+}
+
+
+// returns 0..31 for the highest set bit
+static int stbi__high_bit(unsigned int z)
+{
+ int n=0;
+ if (z == 0) return -1;
+ if (z >= 0x10000) { n += 16; z >>= 16; }
+ if (z >= 0x00100) { n += 8; z >>= 8; }
+ if (z >= 0x00010) { n += 4; z >>= 4; }
+ if (z >= 0x00004) { n += 2; z >>= 2; }
+ if (z >= 0x00002) { n += 1;/* >>= 1;*/ }
+ return n;
+}
+
+static int stbi__bitcount(unsigned int a)
+{
+ a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2
+ a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4
+ a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits
+ a = (a + (a >> 8)); // max 16 per 8 bits
+ a = (a + (a >> 16)); // max 32 per 8 bits
+ return a & 0xff;
+}
+
+// extract an arbitrarily-aligned N-bit value (N=bits)
+// from v, and then make it 8-bits long and fractionally
+// extend it to full full range.
+static int stbi__shiftsigned(unsigned int v, int shift, int bits)
+{
+ static unsigned int mul_table[9] = {
+ 0,
+ 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/,
+ 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/,
+ };
+ static unsigned int shift_table[9] = {
+ 0, 0,0,1,0,2,4,6,0,
+ };
+ if (shift < 0)
+ v <<= -shift;
+ else
+ v >>= shift;
+ STBI_ASSERT(v < 256);
+ v >>= (8-bits);
+ STBI_ASSERT(bits >= 0 && bits <= 8);
+ return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits];
+}
+
+typedef struct
+{
+ int bpp, offset, hsz;
+ unsigned int mr,mg,mb,ma, all_a;
+ int extra_read;
+} stbi__bmp_data;
+
+static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress)
+{
+ // BI_BITFIELDS specifies masks explicitly, don't override
+ if (compress == 3)
+ return 1;
+
+ if (compress == 0) {
+ if (info->bpp == 16) {
+ info->mr = 31u << 10;
+ info->mg = 31u << 5;
+ info->mb = 31u << 0;
+ } else if (info->bpp == 32) {
+ info->mr = 0xffu << 16;
+ info->mg = 0xffu << 8;
+ info->mb = 0xffu << 0;
+ info->ma = 0xffu << 24;
+ info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0
+ } else {
+ // otherwise, use defaults, which is all-0
+ info->mr = info->mg = info->mb = info->ma = 0;
+ }
+ return 1;
+ }
+ return 0; // error
+}
+
+static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
+{
+ int hsz;
+ if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP");
+ stbi__get32le(s); // discard filesize
+ stbi__get16le(s); // discard reserved
+ stbi__get16le(s); // discard reserved
+ info->offset = stbi__get32le(s);
+ info->hsz = hsz = stbi__get32le(s);
+ info->mr = info->mg = info->mb = info->ma = 0;
+ info->extra_read = 14;
+
+ if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP");
+
+ if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown");
+ if (hsz == 12) {
+ s->img_x = stbi__get16le(s);
+ s->img_y = stbi__get16le(s);
+ } else {
+ s->img_x = stbi__get32le(s);
+ s->img_y = stbi__get32le(s);
+ }
+ if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP");
+ info->bpp = stbi__get16le(s);
+ if (hsz != 12) {
+ int compress = stbi__get32le(s);
+ if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
+ if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes
+ if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel
+ stbi__get32le(s); // discard sizeof
+ stbi__get32le(s); // discard hres
+ stbi__get32le(s); // discard vres
+ stbi__get32le(s); // discard colorsused
+ stbi__get32le(s); // discard max important
+ if (hsz == 40 || hsz == 56) {
+ if (hsz == 56) {
+ stbi__get32le(s);
+ stbi__get32le(s);
+ stbi__get32le(s);
+ stbi__get32le(s);
+ }
+ if (info->bpp == 16 || info->bpp == 32) {
+ if (compress == 0) {
+ stbi__bmp_set_mask_defaults(info, compress);
+ } else if (compress == 3) {
+ info->mr = stbi__get32le(s);
+ info->mg = stbi__get32le(s);
+ info->mb = stbi__get32le(s);
+ info->extra_read += 12;
+ // not documented, but generated by photoshop and handled by mspaint
+ if (info->mr == info->mg && info->mg == info->mb) {
+ // ?!?!?
+ return stbi__errpuc("bad BMP", "bad BMP");
+ }
+ } else
+ return stbi__errpuc("bad BMP", "bad BMP");
+ }
+ } else {
+ // V4/V5 header
+ int i;
+ if (hsz != 108 && hsz != 124)
+ return stbi__errpuc("bad BMP", "bad BMP");
+ info->mr = stbi__get32le(s);
+ info->mg = stbi__get32le(s);
+ info->mb = stbi__get32le(s);
+ info->ma = stbi__get32le(s);
+ if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs
+ stbi__bmp_set_mask_defaults(info, compress);
+ stbi__get32le(s); // discard color space
+ for (i=0; i < 12; ++i)
+ stbi__get32le(s); // discard color space parameters
+ if (hsz == 124) {
+ stbi__get32le(s); // discard rendering intent
+ stbi__get32le(s); // discard offset of profile data
+ stbi__get32le(s); // discard size of profile data
+ stbi__get32le(s); // discard reserved
+ }
+ }
+ }
+ return (void *) 1;
+}
+
+
+static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+ stbi_uc *out;
+ unsigned int mr=0,mg=0,mb=0,ma=0, all_a;
+ stbi_uc pal[256][4];
+ int psize=0,i,j,width;
+ int flip_vertically, pad, target;
+ stbi__bmp_data info;
+ STBI_NOTUSED(ri);
+
+ info.all_a = 255;
+ if (stbi__bmp_parse_header(s, &info) == NULL)
+ return NULL; // error code already set
+
+ flip_vertically = ((int) s->img_y) > 0;
+ s->img_y = abs((int) s->img_y);
+
+ if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+ if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+
+ mr = info.mr;
+ mg = info.mg;
+ mb = info.mb;
+ ma = info.ma;
+ all_a = info.all_a;
+
+ if (info.hsz == 12) {
+ if (info.bpp < 24)
+ psize = (info.offset - info.extra_read - 24) / 3;
+ } else {
+ if (info.bpp < 16)
+ psize = (info.offset - info.extra_read - info.hsz) >> 2;
+ }
+ if (psize == 0) {
+ // accept some number of extra bytes after the header, but if the offset points either to before
+ // the header ends or implies a large amount of extra data, reject the file as malformed
+ int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original);
+ int header_limit = 1024; // max we actually read is below 256 bytes currently.
+ int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size.
+ if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) {
+ return stbi__errpuc("bad header", "Corrupt BMP");
+ }
+ // we established that bytes_read_so_far is positive and sensible.
+ // the first half of this test rejects offsets that are either too small positives, or
+ // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn
+ // ensures the number computed in the second half of the test can't overflow.
+ if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) {
+ return stbi__errpuc("bad offset", "Corrupt BMP");
+ } else {
+ stbi__skip(s, info.offset - bytes_read_so_far);
+ }
+ }
+
+ if (info.bpp == 24 && ma == 0xff000000)
+ s->img_n = 3;
+ else
+ s->img_n = ma ? 4 : 3;
+ if (req_comp && req_comp >= 3) // we can directly decode 3 or 4
+ target = req_comp;
+ else
+ target = s->img_n; // if they want monochrome, we'll post-convert
+
+ // sanity-check size
+ if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0))
+ return stbi__errpuc("too large", "Corrupt BMP");
+
+ out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0);
+ if (!out) return stbi__errpuc("outofmem", "Out of memory");
+ if (info.bpp < 16) {
+ int z=0;
+ if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); }
+ for (i=0; i < psize; ++i) {
+ pal[i][2] = stbi__get8(s);
+ pal[i][1] = stbi__get8(s);
+ pal[i][0] = stbi__get8(s);
+ if (info.hsz != 12) stbi__get8(s);
+ pal[i][3] = 255;
+ }
+ stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
+ if (info.bpp == 1) width = (s->img_x + 7) >> 3;
+ else if (info.bpp == 4) width = (s->img_x + 1) >> 1;
+ else if (info.bpp == 8) width = s->img_x;
+ else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); }
+ pad = (-width)&3;
+ if (info.bpp == 1) {
+ for (j=0; j < (int) s->img_y; ++j) {
+ int bit_offset = 7, v = stbi__get8(s);
+ for (i=0; i < (int) s->img_x; ++i) {
+ int color = (v>>bit_offset)&0x1;
+ out[z++] = pal[color][0];
+ out[z++] = pal[color][1];
+ out[z++] = pal[color][2];
+ if (target == 4) out[z++] = 255;
+ if (i+1 == (int) s->img_x) break;
+ if((--bit_offset) < 0) {
+ bit_offset = 7;
+ v = stbi__get8(s);
+ }
+ }
+ stbi__skip(s, pad);
+ }
+ } else {
+ for (j=0; j < (int) s->img_y; ++j) {
+ for (i=0; i < (int) s->img_x; i += 2) {
+ int v=stbi__get8(s),v2=0;
+ if (info.bpp == 4) {
+ v2 = v & 15;
+ v >>= 4;
+ }
+ out[z++] = pal[v][0];
+ out[z++] = pal[v][1];
+ out[z++] = pal[v][2];
+ if (target == 4) out[z++] = 255;
+ if (i+1 == (int) s->img_x) break;
+ v = (info.bpp == 8) ? stbi__get8(s) : v2;
+ out[z++] = pal[v][0];
+ out[z++] = pal[v][1];
+ out[z++] = pal[v][2];
+ if (target == 4) out[z++] = 255;
+ }
+ stbi__skip(s, pad);
+ }
+ }
+ } else {
+ int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;
+ int z = 0;
+ int easy=0;
+ stbi__skip(s, info.offset - info.extra_read - info.hsz);
+ if (info.bpp == 24) width = 3 * s->img_x;
+ else if (info.bpp == 16) width = 2*s->img_x;
+ else /* bpp = 32 and pad = 0 */ width=0;
+ pad = (-width) & 3;
+ if (info.bpp == 24) {
+ easy = 1;
+ } else if (info.bpp == 32) {
+ if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
+ easy = 2;
+ }
+ if (!easy) {
+ if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
+ // right shift amt to put high bit in position #7
+ rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr);
+ gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg);
+ bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb);
+ ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma);
+ if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
+ }
+ for (j=0; j < (int) s->img_y; ++j) {
+ if (easy) {
+ for (i=0; i < (int) s->img_x; ++i) {
+ unsigned char a;
+ out[z+2] = stbi__get8(s);
+ out[z+1] = stbi__get8(s);
+ out[z+0] = stbi__get8(s);
+ z += 3;
+ a = (easy == 2 ? stbi__get8(s) : 255);
+ all_a |= a;
+ if (target == 4) out[z++] = a;
+ }
+ } else {
+ int bpp = info.bpp;
+ for (i=0; i < (int) s->img_x; ++i) {
+ stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s));
+ unsigned int a;
+ out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount));
+ out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));
+ out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));
+ a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
+ all_a |= a;
+ if (target == 4) out[z++] = STBI__BYTECAST(a);
+ }
+ }
+ stbi__skip(s, pad);
+ }
+ }
+
+ // if alpha channel is all 0s, replace with all 255s
+ if (target == 4 && all_a == 0)
+ for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)
+ out[i] = 255;
+
+ if (flip_vertically) {
+ stbi_uc t;
+ for (j=0; j < (int) s->img_y>>1; ++j) {
+ stbi_uc *p1 = out + j *s->img_x*target;
+ stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;
+ for (i=0; i < (int) s->img_x*target; ++i) {
+ t = p1[i]; p1[i] = p2[i]; p2[i] = t;
+ }
+ }
+ }
+
+ if (req_comp && req_comp != target) {
+ out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y);
+ if (out == NULL) return out; // stbi__convert_format frees input on failure
+ }
+
+ *x = s->img_x;
+ *y = s->img_y;
+ if (comp) *comp = s->img_n;
+ return out;
+}
+#endif
+
+// Targa Truevision - TGA
+// by Jonathan Dummer
+#ifndef STBI_NO_TGA
+// returns STBI_rgb or whatever, 0 on error
+static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)
+{
+ // only RGB or RGBA (incl. 16bit) or grey allowed
+ if (is_rgb16) *is_rgb16 = 0;
+ switch(bits_per_pixel) {
+ case 8: return STBI_grey;
+ case 16: if(is_grey) return STBI_grey_alpha;
+ // fallthrough
+ case 15: if(is_rgb16) *is_rgb16 = 1;
+ return STBI_rgb;
+ case 24: // fallthrough
+ case 32: return bits_per_pixel/8;
+ default: return 0;
+ }
+}
+
+static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp;
+ int sz, tga_colormap_type;
+ stbi__get8(s); // discard Offset
+ tga_colormap_type = stbi__get8(s); // colormap type
+ if( tga_colormap_type > 1 ) {
+ stbi__rewind(s);
+ return 0; // only RGB or indexed allowed
+ }
+ tga_image_type = stbi__get8(s); // image type
+ if ( tga_colormap_type == 1 ) { // colormapped (paletted) image
+ if (tga_image_type != 1 && tga_image_type != 9) {
+ stbi__rewind(s);
+ return 0;
+ }
+ stbi__skip(s,4); // skip index of first colormap entry and number of entries
+ sz = stbi__get8(s); // check bits per palette color entry
+ if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) {
+ stbi__rewind(s);
+ return 0;
+ }
+ stbi__skip(s,4); // skip image x and y origin
+ tga_colormap_bpp = sz;
+ } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE
+ if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) {
+ stbi__rewind(s);
+ return 0; // only RGB or grey allowed, +/- RLE
+ }
+ stbi__skip(s,9); // skip colormap specification and image x/y origin
+ tga_colormap_bpp = 0;
+ }
+ tga_w = stbi__get16le(s);
+ if( tga_w < 1 ) {
+ stbi__rewind(s);
+ return 0; // test width
+ }
+ tga_h = stbi__get16le(s);
+ if( tga_h < 1 ) {
+ stbi__rewind(s);
+ return 0; // test height
+ }
+ tga_bits_per_pixel = stbi__get8(s); // bits per pixel
+ stbi__get8(s); // ignore alpha bits
+ if (tga_colormap_bpp != 0) {
+ if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) {
+ // when using a colormap, tga_bits_per_pixel is the size of the indexes
+ // I don't think anything but 8 or 16bit indexes makes sense
+ stbi__rewind(s);
+ return 0;
+ }
+ tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);
+ } else {
+ tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL);
+ }
+ if(!tga_comp) {
+ stbi__rewind(s);
+ return 0;
+ }
+ if (x) *x = tga_w;
+ if (y) *y = tga_h;
+ if (comp) *comp = tga_comp;
+ return 1; // seems to have passed everything
+}
+
+static int stbi__tga_test(stbi__context *s)
+{
+ int res = 0;
+ int sz, tga_color_type;
+ stbi__get8(s); // discard Offset
+ tga_color_type = stbi__get8(s); // color type
+ if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed
+ sz = stbi__get8(s); // image type
+ if ( tga_color_type == 1 ) { // colormapped (paletted) image
+ if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9
+ stbi__skip(s,4); // skip index of first colormap entry and number of entries
+ sz = stbi__get8(s); // check bits per palette color entry
+ if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
+ stbi__skip(s,4); // skip image x and y origin
+ } else { // "normal" image w/o colormap
+ if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE
+ stbi__skip(s,9); // skip colormap specification and image x/y origin
+ }
+ if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width
+ if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height
+ sz = stbi__get8(s); // bits per pixel
+ if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index
+ if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
+
+ res = 1; // if we got this far, everything's good and we can return 1 instead of 0
+
+errorEnd:
+ stbi__rewind(s);
+ return res;
+}
+
+// read 16bit value and convert to 24bit RGB
+static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)
+{
+ stbi__uint16 px = (stbi__uint16)stbi__get16le(s);
+ stbi__uint16 fiveBitMask = 31;
+ // we have 3 channels with 5bits each
+ int r = (px >> 10) & fiveBitMask;
+ int g = (px >> 5) & fiveBitMask;
+ int b = px & fiveBitMask;
+ // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later
+ out[0] = (stbi_uc)((r * 255)/31);
+ out[1] = (stbi_uc)((g * 255)/31);
+ out[2] = (stbi_uc)((b * 255)/31);
+
+ // some people claim that the most significant bit might be used for alpha
+ // (possibly if an alpha-bit is set in the "image descriptor byte")
+ // but that only made 16bit test images completely translucent..
+ // so let's treat all 15 and 16bit TGAs as RGB with no alpha.
+}
+
+static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+ // read in the TGA header stuff
+ int tga_offset = stbi__get8(s);
+ int tga_indexed = stbi__get8(s);
+ int tga_image_type = stbi__get8(s);
+ int tga_is_RLE = 0;
+ int tga_palette_start = stbi__get16le(s);
+ int tga_palette_len = stbi__get16le(s);
+ int tga_palette_bits = stbi__get8(s);
+ int tga_x_origin = stbi__get16le(s);
+ int tga_y_origin = stbi__get16le(s);
+ int tga_width = stbi__get16le(s);
+ int tga_height = stbi__get16le(s);
+ int tga_bits_per_pixel = stbi__get8(s);
+ int tga_comp, tga_rgb16=0;
+ int tga_inverted = stbi__get8(s);
+ // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?)
+ // image data
+ unsigned char *tga_data;
+ unsigned char *tga_palette = NULL;
+ int i, j;
+ unsigned char raw_data[4] = {0};
+ int RLE_count = 0;
+ int RLE_repeating = 0;
+ int read_next_pixel = 1;
+ STBI_NOTUSED(ri);
+ STBI_NOTUSED(tga_x_origin); // @TODO
+ STBI_NOTUSED(tga_y_origin); // @TODO
+
+ if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+ if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+
+ // do a tiny bit of precessing
+ if ( tga_image_type >= 8 )
+ {
+ tga_image_type -= 8;
+ tga_is_RLE = 1;
+ }
+ tga_inverted = 1 - ((tga_inverted >> 5) & 1);
+
+ // If I'm paletted, then I'll use the number of bits from the palette
+ if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);
+ else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16);
+
+ if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency
+ return stbi__errpuc("bad format", "Can't find out TGA pixelformat");
+
+ // tga info
+ *x = tga_width;
+ *y = tga_height;
+ if (comp) *comp = tga_comp;
+
+ if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0))
+ return stbi__errpuc("too large", "Corrupt TGA");
+
+ tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0);
+ if (!tga_data) return stbi__errpuc("outofmem", "Out of memory");
+
+ // skip to the data's starting position (offset usually = 0)
+ stbi__skip(s, tga_offset );
+
+ if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) {
+ for (i=0; i < tga_height; ++i) {
+ int row = tga_inverted ? tga_height -i - 1 : i;
+ stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;
+ stbi__getn(s, tga_row, tga_width * tga_comp);
+ }
+ } else {
+ // do I need to load a palette?
+ if ( tga_indexed)
+ {
+ if (tga_palette_len == 0) { /* you have to have at least one entry! */
+ STBI_FREE(tga_data);
+ return stbi__errpuc("bad palette", "Corrupt TGA");
+ }
+
+ // any data to skip? (offset usually = 0)
+ stbi__skip(s, tga_palette_start );
+ // load the palette
+ tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0);
+ if (!tga_palette) {
+ STBI_FREE(tga_data);
+ return stbi__errpuc("outofmem", "Out of memory");
+ }
+ if (tga_rgb16) {
+ stbi_uc *pal_entry = tga_palette;
+ STBI_ASSERT(tga_comp == STBI_rgb);
+ for (i=0; i < tga_palette_len; ++i) {
+ stbi__tga_read_rgb16(s, pal_entry);
+ pal_entry += tga_comp;
+ }
+ } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) {
+ STBI_FREE(tga_data);
+ STBI_FREE(tga_palette);
+ return stbi__errpuc("bad palette", "Corrupt TGA");
+ }
+ }
+ // load the data
+ for (i=0; i < tga_width * tga_height; ++i)
+ {
+ // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk?
+ if ( tga_is_RLE )
+ {
+ if ( RLE_count == 0 )
+ {
+ // yep, get the next byte as a RLE command
+ int RLE_cmd = stbi__get8(s);
+ RLE_count = 1 + (RLE_cmd & 127);
+ RLE_repeating = RLE_cmd >> 7;
+ read_next_pixel = 1;
+ } else if ( !RLE_repeating )
+ {
+ read_next_pixel = 1;
+ }
+ } else
+ {
+ read_next_pixel = 1;
+ }
+ // OK, if I need to read a pixel, do it now
+ if ( read_next_pixel )
+ {
+ // load however much data we did have
+ if ( tga_indexed )
+ {
+ // read in index, then perform the lookup
+ int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s);
+ if ( pal_idx >= tga_palette_len ) {
+ // invalid index
+ pal_idx = 0;
+ }
+ pal_idx *= tga_comp;
+ for (j = 0; j < tga_comp; ++j) {
+ raw_data[j] = tga_palette[pal_idx+j];
+ }
+ } else if(tga_rgb16) {
+ STBI_ASSERT(tga_comp == STBI_rgb);
+ stbi__tga_read_rgb16(s, raw_data);
+ } else {
+ // read in the data raw
+ for (j = 0; j < tga_comp; ++j) {
+ raw_data[j] = stbi__get8(s);
+ }
+ }
+ // clear the reading flag for the next pixel
+ read_next_pixel = 0;
+ } // end of reading a pixel
+
+ // copy data
+ for (j = 0; j < tga_comp; ++j)
+ tga_data[i*tga_comp+j] = raw_data[j];
+
+ // in case we're in RLE mode, keep counting down
+ --RLE_count;
+ }
+ // do I need to invert the image?
+ if ( tga_inverted )
+ {
+ for (j = 0; j*2 < tga_height; ++j)
+ {
+ int index1 = j * tga_width * tga_comp;
+ int index2 = (tga_height - 1 - j) * tga_width * tga_comp;
+ for (i = tga_width * tga_comp; i > 0; --i)
+ {
+ unsigned char temp = tga_data[index1];
+ tga_data[index1] = tga_data[index2];
+ tga_data[index2] = temp;
+ ++index1;
+ ++index2;
+ }
+ }
+ }
+ // clear my palette, if I had one
+ if ( tga_palette != NULL )
+ {
+ STBI_FREE( tga_palette );
+ }
+ }
+
+ // swap RGB - if the source data was RGB16, it already is in the right order
+ if (tga_comp >= 3 && !tga_rgb16)
+ {
+ unsigned char* tga_pixel = tga_data;
+ for (i=0; i < tga_width * tga_height; ++i)
+ {
+ unsigned char temp = tga_pixel[0];
+ tga_pixel[0] = tga_pixel[2];
+ tga_pixel[2] = temp;
+ tga_pixel += tga_comp;
+ }
+ }
+
+ // convert to target component count
+ if (req_comp && req_comp != tga_comp)
+ tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);
+
+ // the things I do to get rid of an error message, and yet keep
+ // Microsoft's C compilers happy... [8^(
+ tga_palette_start = tga_palette_len = tga_palette_bits =
+ tga_x_origin = tga_y_origin = 0;
+ STBI_NOTUSED(tga_palette_start);
+ // OK, done
+ return tga_data;
+}
+#endif
+
+// *************************************************************************************************
+// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_test(stbi__context *s)
+{
+ int r = (stbi__get32be(s) == 0x38425053);
+ stbi__rewind(s);
+ return r;
+}
+
+static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount)
+{
+ int count, nleft, len;
+
+ count = 0;
+ while ((nleft = pixelCount - count) > 0) {
+ len = stbi__get8(s);
+ if (len == 128) {
+ // No-op.
+ } else if (len < 128) {
+ // Copy next len+1 bytes literally.
+ len++;
+ if (len > nleft) return 0; // corrupt data
+ count += len;
+ while (len) {
+ *p = stbi__get8(s);
+ p += 4;
+ len--;
+ }
+ } else if (len > 128) {
+ stbi_uc val;
+ // Next -len+1 bytes in the dest are replicated from next source byte.
+ // (Interpret len as a negative 8-bit int.)
+ len = 257 - len;
+ if (len > nleft) return 0; // corrupt data
+ val = stbi__get8(s);
+ count += len;
+ while (len) {
+ *p = val;
+ p += 4;
+ len--;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
+{
+ int pixelCount;
+ int channelCount, compression;
+ int channel, i;
+ int bitdepth;
+ int w,h;
+ stbi_uc *out;
+ STBI_NOTUSED(ri);
+
+ // Check identifier
+ if (stbi__get32be(s) != 0x38425053) // "8BPS"
+ return stbi__errpuc("not PSD", "Corrupt PSD image");
+
+ // Check file type version.
+ if (stbi__get16be(s) != 1)
+ return stbi__errpuc("wrong version", "Unsupported version of PSD image");
+
+ // Skip 6 reserved bytes.
+ stbi__skip(s, 6 );
+
+ // Read the number of channels (R, G, B, A, etc).
+ channelCount = stbi__get16be(s);
+ if (channelCount < 0 || channelCount > 16)
+ return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image");
+
+ // Read the rows and columns of the image.
+ h = stbi__get32be(s);
+ w = stbi__get32be(s);
+
+ if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+ if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+
+ // Make sure the depth is 8 bits.
+ bitdepth = stbi__get16be(s);
+ if (bitdepth != 8 && bitdepth != 16)
+ return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit");
+
+ // Make sure the color mode is RGB.
+ // Valid options are:
+ // 0: Bitmap
+ // 1: Grayscale
+ // 2: Indexed color
+ // 3: RGB color
+ // 4: CMYK color
+ // 7: Multichannel
+ // 8: Duotone
+ // 9: Lab color
+ if (stbi__get16be(s) != 3)
+ return stbi__errpuc("wrong color format", "PSD is not in RGB color format");
+
+ // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.)
+ stbi__skip(s,stbi__get32be(s) );
+
+ // Skip the image resources. (resolution, pen tool paths, etc)
+ stbi__skip(s, stbi__get32be(s) );
+
+ // Skip the reserved data.
+ stbi__skip(s, stbi__get32be(s) );
+
+ // Find out if the data is compressed.
+ // Known values:
+ // 0: no compression
+ // 1: RLE compressed
+ compression = stbi__get16be(s);
+ if (compression > 1)
+ return stbi__errpuc("bad compression", "PSD has an unknown compression format");
+
+ // Check size
+ if (!stbi__mad3sizes_valid(4, w, h, 0))
+ return stbi__errpuc("too large", "Corrupt PSD");
+
+ // Create the destination image.
+
+ if (!compression && bitdepth == 16 && bpc == 16) {
+ out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0);
+ ri->bits_per_channel = 16;
+ } else
+ out = (stbi_uc *) stbi__malloc(4 * w*h);
+
+ if (!out) return stbi__errpuc("outofmem", "Out of memory");
+ pixelCount = w*h;
+
+ // Initialize the data to zero.
+ //memset( out, 0, pixelCount * 4 );
+
+ // Finally, the image data.
+ if (compression) {
+ // RLE as used by .PSD and .TIFF
+ // Loop until you get the number of unpacked bytes you are expecting:
+ // Read the next source byte into n.
+ // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
+ // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
+ // Else if n is 128, noop.
+ // Endloop
+
+ // The RLE-compressed data is preceded by a 2-byte data count for each row in the data,
+ // which we're going to just skip.
+ stbi__skip(s, h * channelCount * 2 );
+
+ // Read the RLE data by channel.
+ for (channel = 0; channel < 4; channel++) {
+ stbi_uc *p;
+
+ p = out+channel;
+ if (channel >= channelCount) {
+ // Fill this channel with default data.
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = (channel == 3 ? 255 : 0);
+ } else {
+ // Read the RLE data.
+ if (!stbi__psd_decode_rle(s, p, pixelCount)) {
+ STBI_FREE(out);
+ return stbi__errpuc("corrupt", "bad RLE data");
+ }
+ }
+ }
+
+ } else {
+ // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...)
+ // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image.
+
+ // Read the data by channel.
+ for (channel = 0; channel < 4; channel++) {
+ if (channel >= channelCount) {
+ // Fill this channel with default data.
+ if (bitdepth == 16 && bpc == 16) {
+ stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
+ stbi__uint16 val = channel == 3 ? 65535 : 0;
+ for (i = 0; i < pixelCount; i++, q += 4)
+ *q = val;
+ } else {
+ stbi_uc *p = out+channel;
+ stbi_uc val = channel == 3 ? 255 : 0;
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = val;
+ }
+ } else {
+ if (ri->bits_per_channel == 16) { // output bpc
+ stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
+ for (i = 0; i < pixelCount; i++, q += 4)
+ *q = (stbi__uint16) stbi__get16be(s);
+ } else {
+ stbi_uc *p = out+channel;
+ if (bitdepth == 16) { // input bpc
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = (stbi_uc) (stbi__get16be(s) >> 8);
+ } else {
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = stbi__get8(s);
+ }
+ }
+ }
+ }
+ }
+
+ // remove weird white matte from PSD
+ if (channelCount >= 4) {
+ if (ri->bits_per_channel == 16) {
+ for (i=0; i < w*h; ++i) {
+ stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i;
+ if (pixel[3] != 0 && pixel[3] != 65535) {
+ float a = pixel[3] / 65535.0f;
+ float ra = 1.0f / a;
+ float inv_a = 65535.0f * (1 - ra);
+ pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a);
+ pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a);
+ pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a);
+ }
+ }
+ } else {
+ for (i=0; i < w*h; ++i) {
+ unsigned char *pixel = out + 4*i;
+ if (pixel[3] != 0 && pixel[3] != 255) {
+ float a = pixel[3] / 255.0f;
+ float ra = 1.0f / a;
+ float inv_a = 255.0f * (1 - ra);
+ pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);
+ pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);
+ pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);
+ }
+ }
+ }
+ }
+
+ // convert to desired output format
+ if (req_comp && req_comp != 4) {
+ if (ri->bits_per_channel == 16)
+ out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h);
+ else
+ out = stbi__convert_format(out, 4, req_comp, w, h);
+ if (out == NULL) return out; // stbi__convert_format frees input on failure
+ }
+
+ if (comp) *comp = 4;
+ *y = h;
+ *x = w;
+
+ return out;
+}
+#endif
+
+// *************************************************************************************************
+// Softimage PIC loader
+// by Tom Seddon
+//
+// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format
+// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_is4(stbi__context *s,const char *str)
+{
+ int i;
+ for (i=0; i<4; ++i)
+ if (stbi__get8(s) != (stbi_uc)str[i])
+ return 0;
+
+ return 1;
+}
+
+static int stbi__pic_test_core(stbi__context *s)
+{
+ int i;
+
+ if (!stbi__pic_is4(s,"\x53\x80\xF6\x34"))
+ return 0;
+
+ for(i=0;i<84;++i)
+ stbi__get8(s);
+
+ if (!stbi__pic_is4(s,"PICT"))
+ return 0;
+
+ return 1;
+}
+
+typedef struct
+{
+ stbi_uc size,type,channel;
+} stbi__pic_packet;
+
+static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest)
+{
+ int mask=0x80, i;
+
+ for (i=0; i<4; ++i, mask>>=1) {
+ if (channel & mask) {
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short");
+ dest[i]=stbi__get8(s);
+ }
+ }
+
+ return dest;
+}
+
+static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src)
+{
+ int mask=0x80,i;
+
+ for (i=0;i<4; ++i, mask>>=1)
+ if (channel&mask)
+ dest[i]=src[i];
+}
+
+static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result)
+{
+ int act_comp=0,num_packets=0,y,chained;
+ stbi__pic_packet packets[10];
+
+ // this will (should...) cater for even some bizarre stuff like having data
+ // for the same channel in multiple packets.
+ do {
+ stbi__pic_packet *packet;
+
+ if (num_packets==sizeof(packets)/sizeof(packets[0]))
+ return stbi__errpuc("bad format","too many packets");
+
+ packet = &packets[num_packets++];
+
+ chained = stbi__get8(s);
+ packet->size = stbi__get8(s);
+ packet->type = stbi__get8(s);
+ packet->channel = stbi__get8(s);
+
+ act_comp |= packet->channel;
+
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)");
+ if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp");
+ } while (chained);
+
+ *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?
+
+ for(y=0; y<height; ++y) {
+ int packet_idx;
+
+ for(packet_idx=0; packet_idx < num_packets; ++packet_idx) {
+ stbi__pic_packet *packet = &packets[packet_idx];
+ stbi_uc *dest = result+y*width*4;
+
+ switch (packet->type) {
+ default:
+ return stbi__errpuc("bad format","packet has bad compression type");
+
+ case 0: {//uncompressed
+ int x;
+
+ for(x=0;x<width;++x, dest+=4)
+ if (!stbi__readval(s,packet->channel,dest))
+ return 0;
+ break;
+ }
+
+ case 1://Pure RLE
+ {
+ int left=width, i;
+
+ while (left>0) {
+ stbi_uc count,value[4];
+
+ count=stbi__get8(s);
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)");
+
+ if (count > left)
+ count = (stbi_uc) left;
+
+ if (!stbi__readval(s,packet->channel,value)) return 0;
+
+ for(i=0; i<count; ++i,dest+=4)
+ stbi__copyval(packet->channel,dest,value);
+ left -= count;
+ }
+ }
+ break;
+
+ case 2: {//Mixed RLE
+ int left=width;
+ while (left>0) {
+ int count = stbi__get8(s), i;
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)");
+
+ if (count >= 128) { // Repeated
+ stbi_uc value[4];
+
+ if (count==128)
+ count = stbi__get16be(s);
+ else
+ count -= 127;
+ if (count > left)
+ return stbi__errpuc("bad file","scanline overrun");
+
+ if (!stbi__readval(s,packet->channel,value))
+ return 0;
+
+ for(i=0;i<count;++i, dest += 4)
+ stbi__copyval(packet->channel,dest,value);
+ } else { // Raw
+ ++count;
+ if (count>left) return stbi__errpuc("bad file","scanline overrun");
+
+ for(i=0;i<count;++i, dest+=4)
+ if (!stbi__readval(s,packet->channel,dest))
+ return 0;
+ }
+ left-=count;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri)
+{
+ stbi_uc *result;
+ int i, x,y, internal_comp;
+ STBI_NOTUSED(ri);
+
+ if (!comp) comp = &internal_comp;
+
+ for (i=0; i<92; ++i)
+ stbi__get8(s);
+
+ x = stbi__get16be(s);
+ y = stbi__get16be(s);
+
+ if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+ if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)");
+ if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode");
+
+ stbi__get32be(s); //skip `ratio'
+ stbi__get16be(s); //skip `fields'
+ stbi__get16be(s); //skip `pad'
+
+ // intermediate buffer is RGBA
+ result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0);
+ if (!result) return stbi__errpuc("outofmem", "Out of memory");
+ memset(result, 0xff, x*y*4);
+
+ if (!stbi__pic_load_core(s,x,y,comp, result)) {
+ STBI_FREE(result);
+ result=0;
+ }
+ *px = x;
+ *py = y;
+ if (req_comp == 0) req_comp = *comp;
+ result=stbi__convert_format(result,4,req_comp,x,y);
+
+ return result;
+}
+
+static int stbi__pic_test(stbi__context *s)
+{
+ int r = stbi__pic_test_core(s);
+ stbi__rewind(s);
+ return r;
+}
+#endif
+
+// *************************************************************************************************
+// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb
+
+#ifndef STBI_NO_GIF
+typedef struct
+{
+ stbi__int16 prefix;
+ stbi_uc first;
+ stbi_uc suffix;
+} stbi__gif_lzw;
+
+typedef struct
+{
+ int w,h;
+ stbi_uc *out; // output buffer (always 4 components)
+ stbi_uc *background; // The current "background" as far as a gif is concerned
+ stbi_uc *history;
+ int flags, bgindex, ratio, transparent, eflags;
+ stbi_uc pal[256][4];
+ stbi_uc lpal[256][4];
+ stbi__gif_lzw codes[8192];
+ stbi_uc *color_table;
+ int parse, step;
+ int lflags;
+ int start_x, start_y;
+ int max_x, max_y;
+ int cur_x, cur_y;
+ int line_size;
+ int delay;
+} stbi__gif;
+
+static int stbi__gif_test_raw(stbi__context *s)
+{
+ int sz;
+ if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0;
+ sz = stbi__get8(s);
+ if (sz != '9' && sz != '7') return 0;
+ if (stbi__get8(s) != 'a') return 0;
+ return 1;
+}
+
+static int stbi__gif_test(stbi__context *s)
+{
+ int r = stbi__gif_test_raw(s);
+ stbi__rewind(s);
+ return r;
+}
+
+static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp)
+{
+ int i;
+ for (i=0; i < num_entries; ++i) {
+ pal[i][2] = stbi__get8(s);
+ pal[i][1] = stbi__get8(s);
+ pal[i][0] = stbi__get8(s);
+ pal[i][3] = transp == i ? 0 : 255;
+ }
+}
+
+static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info)
+{
+ stbi_uc version;
+ if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8')
+ return stbi__err("not GIF", "Corrupt GIF");
+
+ version = stbi__get8(s);
+ if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF");
+ if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF");
+
+ stbi__g_failure_reason = "";
+ g->w = stbi__get16le(s);
+ g->h = stbi__get16le(s);
+ g->flags = stbi__get8(s);
+ g->bgindex = stbi__get8(s);
+ g->ratio = stbi__get8(s);
+ g->transparent = -1;
+
+ if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
+ if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)");
+
+ if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments
+
+ if (is_info) return 1;
+
+ if (g->flags & 0x80)
+ stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);
+
+ return 1;
+}
+
+static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
+{
+ stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
+ if (!g) return stbi__err("outofmem", "Out of memory");
+ if (!stbi__gif_header(s, g, comp, 1)) {
+ STBI_FREE(g);
+ stbi__rewind( s );
+ return 0;
+ }
+ if (x) *x = g->w;
+ if (y) *y = g->h;
+ STBI_FREE(g);
+ return 1;
+}
+
+static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
+{
+ stbi_uc *p, *c;
+ int idx;
+
+ // recurse to decode the prefixes, since the linked-list is backwards,
+ // and working backwards through an interleaved image would be nasty
+ if (g->codes[code].prefix >= 0)
+ stbi__out_gif_code(g, g->codes[code].prefix);
+
+ if (g->cur_y >= g->max_y) return;
+
+ idx = g->cur_x + g->cur_y;
+ p = &g->out[idx];
+ g->history[idx / 4] = 1;
+
+ c = &g->color_table[g->codes[code].suffix * 4];
+ if (c[3] > 128) { // don't render transparent pixels;
+ p[0] = c[2];
+ p[1] = c[1];
+ p[2] = c[0];
+ p[3] = c[3];
+ }
+ g->cur_x += 4;
+
+ if (g->cur_x >= g->max_x) {
+ g->cur_x = g->start_x;
+ g->cur_y += g->step;
+
+ while (g->cur_y >= g->max_y && g->parse > 0) {
+ g->step = (1 << g->parse) * g->line_size;
+ g->cur_y = g->start_y + (g->step >> 1);
+ --g->parse;
+ }
+ }
+}
+
+static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
+{
+ stbi_uc lzw_cs;
+ stbi__int32 len, init_code;
+ stbi__uint32 first;
+ stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
+ stbi__gif_lzw *p;
+
+ lzw_cs = stbi__get8(s);
+ if (lzw_cs > 12) return NULL;
+ clear = 1 << lzw_cs;
+ first = 1;
+ codesize = lzw_cs + 1;
+ codemask = (1 << codesize) - 1;
+ bits = 0;
+ valid_bits = 0;
+ for (init_code = 0; init_code < clear; init_code++) {
+ g->codes[init_code].prefix = -1;
+ g->codes[init_code].first = (stbi_uc) init_code;
+ g->codes[init_code].suffix = (stbi_uc) init_code;
+ }
+
+ // support no starting clear code
+ avail = clear+2;
+ oldcode = -1;
+
+ len = 0;
+ for(;;) {
+ if (valid_bits < codesize) {
+ if (len == 0) {
+ len = stbi__get8(s); // start new block
+ if (len == 0)
+ return g->out;
+ }
+ --len;
+ bits |= (stbi__int32) stbi__get8(s) << valid_bits;
+ valid_bits += 8;
+ } else {
+ stbi__int32 code = bits & codemask;
+ bits >>= codesize;
+ valid_bits -= codesize;
+ // @OPTIMIZE: is there some way we can accelerate the non-clear path?
+ if (code == clear) { // clear code
+ codesize = lzw_cs + 1;
+ codemask = (1 << codesize) - 1;
+ avail = clear + 2;
+ oldcode = -1;
+ first = 0;
+ } else if (code == clear + 1) { // end of stream code
+ stbi__skip(s, len);
+ while ((len = stbi__get8(s)) > 0)
+ stbi__skip(s,len);
+ return g->out;
+ } else if (code <= avail) {
+ if (first) {
+ return stbi__errpuc("no clear code", "Corrupt GIF");
+ }
+
+ if (oldcode >= 0) {
+ p = &g->codes[avail++];
+ if (avail > 8192) {
+ return stbi__errpuc("too many codes", "Corrupt GIF");
+ }
+
+ p->prefix = (stbi__int16) oldcode;
+ p->first = g->codes[oldcode].first;
+ p->suffix = (code == avail) ? p->first : g->codes[code].first;
+ } else if (code == avail)
+ return stbi__errpuc("illegal code in raster", "Corrupt GIF");
+
+ stbi__out_gif_code(g, (stbi__uint16) code);
+
+ if ((avail & codemask) == 0 && avail <= 0x0FFF) {
+ codesize++;
+ codemask = (1 << codesize) - 1;
+ }
+
+ oldcode = code;
+ } else {
+ return stbi__errpuc("illegal code in raster", "Corrupt GIF");
+ }
+ }
+ }
+}
+
+// this function is designed to support animated gifs, although stb_image doesn't support it
+// two back is the image from two frames ago, used for a very specific disposal format
+static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)
+{
+ int dispose;
+ int first_frame;
+ int pi;
+ int pcount;
+ STBI_NOTUSED(req_comp);
+
+ // on first frame, any non-written pixels get the background colour (non-transparent)
+ first_frame = 0;
+ if (g->out == 0) {
+ if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header
+ if (!stbi__mad3sizes_valid(4, g->w, g->h, 0))
+ return stbi__errpuc("too large", "GIF image is too large");
+ pcount = g->w * g->h;
+ g->out = (stbi_uc *) stbi__malloc(4 * pcount);
+ g->background = (stbi_uc *) stbi__malloc(4 * pcount);
+ g->history = (stbi_uc *) stbi__malloc(pcount);
+ if (!g->out || !g->background || !g->history)
+ return stbi__errpuc("outofmem", "Out of memory");
+
+ // image is treated as "transparent" at the start - ie, nothing overwrites the current background;
+ // background colour is only used for pixels that are not rendered first frame, after that "background"
+ // color refers to the color that was there the previous frame.
+ memset(g->out, 0x00, 4 * pcount);
+ memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent)
+ memset(g->history, 0x00, pcount); // pixels that were affected previous frame
+ first_frame = 1;
+ } else {
+ // second frame - how do we dispose of the previous one?
+ dispose = (g->eflags & 0x1C) >> 2;
+ pcount = g->w * g->h;
+
+ if ((dispose == 3) && (two_back == 0)) {
+ dispose = 2; // if I don't have an image to revert back to, default to the old background
+ }
+
+ if (dispose == 3) { // use previous graphic
+ for (pi = 0; pi < pcount; ++pi) {
+ if (g->history[pi]) {
+ memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 );
+ }
+ }
+ } else if (dispose == 2) {
+ // restore what was changed last frame to background before that frame;
+ for (pi = 0; pi < pcount; ++pi) {
+ if (g->history[pi]) {
+ memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 );
+ }
+ }
+ } else {
+ // This is a non-disposal case eithe way, so just
+ // leave the pixels as is, and they will become the new background
+ // 1: do not dispose
+ // 0: not specified.
+ }
+
+ // background is what out is after the undoing of the previou frame;
+ memcpy( g->background, g->out, 4 * g->w * g->h );
+ }
+
+ // clear my history;
+ memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame
+
+ for (;;) {
+ int tag = stbi__get8(s);
+ switch (tag) {
+ case 0x2C: /* Image Descriptor */
+ {
+ stbi__int32 x, y, w, h;
+ stbi_uc *o;
+
+ x = stbi__get16le(s);
+ y = stbi__get16le(s);
+ w = stbi__get16le(s);
+ h = stbi__get16le(s);
+ if (((x + w) > (g->w)) || ((y + h) > (g->h)))
+ return stbi__errpuc("bad Image Descriptor", "Corrupt GIF");
+
+ g->line_size = g->w * 4;
+ g->start_x = x * 4;
+ g->start_y = y * g->line_size;
+ g->max_x = g->start_x + w * 4;
+ g->max_y = g->start_y + h * g->line_size;
+ g->cur_x = g->start_x;
+ g->cur_y = g->start_y;
+
+ // if the width of the specified rectangle is 0, that means
+ // we may not see *any* pixels or the image is malformed;
+ // to make sure this is caught, move the current y down to
+ // max_y (which is what out_gif_code checks).
+ if (w == 0)
+ g->cur_y = g->max_y;
+
+ g->lflags = stbi__get8(s);
+
+ if (g->lflags & 0x40) {
+ g->step = 8 * g->line_size; // first interlaced spacing
+ g->parse = 3;
+ } else {
+ g->step = g->line_size;
+ g->parse = 0;
+ }
+
+ if (g->lflags & 0x80) {
+ stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
+ g->color_table = (stbi_uc *) g->lpal;
+ } else if (g->flags & 0x80) {
+ g->color_table = (stbi_uc *) g->pal;
+ } else
+ return stbi__errpuc("missing color table", "Corrupt GIF");
+
+ o = stbi__process_gif_raster(s, g);
+ if (!o) return NULL;
+
+ // if this was the first frame,
+ pcount = g->w * g->h;
+ if (first_frame && (g->bgindex > 0)) {
+ // if first frame, any pixel not drawn to gets the background color
+ for (pi = 0; pi < pcount; ++pi) {
+ if (g->history[pi] == 0) {
+ g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be;
+ memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 );
+ }
+ }
+ }
+
+ return o;
+ }
+
+ case 0x21: // Comment Extension.
+ {
+ int len;
+ int ext = stbi__get8(s);
+ if (ext == 0xF9) { // Graphic Control Extension.
+ len = stbi__get8(s);
+ if (len == 4) {
+ g->eflags = stbi__get8(s);
+ g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths.
+
+ // unset old transparent
+ if (g->transparent >= 0) {
+ g->pal[g->transparent][3] = 255;
+ }
+ if (g->eflags & 0x01) {
+ g->transparent = stbi__get8(s);
+ if (g->transparent >= 0) {
+ g->pal[g->transparent][3] = 0;
+ }
+ } else {
+ // don't need transparent
+ stbi__skip(s, 1);
+ g->transparent = -1;
+ }
+ } else {
+ stbi__skip(s, len);
+ break;
+ }
+ }
+ while ((len = stbi__get8(s)) != 0) {
+ stbi__skip(s, len);
+ }
+ break;
+ }
+
+ case 0x3B: // gif stream termination code
+ return (stbi_uc *) s; // using '1' causes warning on some compilers
+
+ default:
+ return stbi__errpuc("unknown code", "Corrupt GIF");
+ }
+ }
+}
+
+static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays)
+{
+ STBI_FREE(g->out);
+ STBI_FREE(g->history);
+ STBI_FREE(g->background);
+
+ if (out) STBI_FREE(out);
+ if (delays && *delays) STBI_FREE(*delays);
+ return stbi__errpuc("outofmem", "Out of memory");
+}
+
+static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
+{
+ if (stbi__gif_test(s)) {
+ int layers = 0;
+ stbi_uc *u = 0;
+ stbi_uc *out = 0;
+ stbi_uc *two_back = 0;
+ stbi__gif g;
+ int stride;
+ int out_size = 0;
+ int delays_size = 0;
+
+ STBI_NOTUSED(out_size);
+ STBI_NOTUSED(delays_size);
+
+ memset(&g, 0, sizeof(g));
+ if (delays) {
+ *delays = 0;
+ }
+
+ do {
+ u = stbi__gif_load_next(s, &g, comp, req_comp, two_back);
+ if (u == (stbi_uc *) s) u = 0; // end of animated gif marker
+
+ if (u) {
+ *x = g.w;
+ *y = g.h;
+ ++layers;
+ stride = g.w * g.h * 4;
+
+ if (out) {
+ void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride );
+ if (!tmp)
+ return stbi__load_gif_main_outofmem(&g, out, delays);
+ else {
+ out = (stbi_uc*) tmp;
+ out_size = layers * stride;
+ }
+
+ if (delays) {
+ int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers );
+ if (!new_delays)
+ return stbi__load_gif_main_outofmem(&g, out, delays);
+ *delays = new_delays;
+ delays_size = layers * sizeof(int);
+ }
+ } else {
+ out = (stbi_uc*)stbi__malloc( layers * stride );
+ if (!out)
+ return stbi__load_gif_main_outofmem(&g, out, delays);
+ out_size = layers * stride;
+ if (delays) {
+ *delays = (int*) stbi__malloc( layers * sizeof(int) );
+ if (!*delays)
+ return stbi__load_gif_main_outofmem(&g, out, delays);
+ delays_size = layers * sizeof(int);
+ }
+ }
+ memcpy( out + ((layers - 1) * stride), u, stride );
+ if (layers >= 2) {
+ two_back = out - 2 * stride;
+ }
+
+ if (delays) {
+ (*delays)[layers - 1U] = g.delay;
+ }
+ }
+ } while (u != 0);
+
+ // free temp buffer;
+ STBI_FREE(g.out);
+ STBI_FREE(g.history);
+ STBI_FREE(g.background);
+
+ // do the final conversion after loading everything;
+ if (req_comp && req_comp != 4)
+ out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);
+
+ *z = layers;
+ return out;
+ } else {
+ return stbi__errpuc("not GIF", "Image was not as a gif type.");
+ }
+}
+
+static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+ stbi_uc *u = 0;
+ stbi__gif g;
+ memset(&g, 0, sizeof(g));
+ STBI_NOTUSED(ri);
+
+ u = stbi__gif_load_next(s, &g, comp, req_comp, 0);
+ if (u == (stbi_uc *) s) u = 0; // end of animated gif marker
+ if (u) {
+ *x = g.w;
+ *y = g.h;
+
+ // moved conversion to after successful load so that the same
+ // can be done for multiple frames.
+ if (req_comp && req_comp != 4)
+ u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
+ } else if (g.out) {
+ // if there was an error and we allocated an image buffer, free it!
+ STBI_FREE(g.out);
+ }
+
+ // free buffers needed for multiple frame loading;
+ STBI_FREE(g.history);
+ STBI_FREE(g.background);
+
+ return u;
+}
+
+static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ return stbi__gif_info_raw(s,x,y,comp);
+}
+#endif
+
+// *************************************************************************************************
+// Radiance RGBE HDR loader
+// originally by Nicolas Schulz
+#ifndef STBI_NO_HDR
+static int stbi__hdr_test_core(stbi__context *s, const char *signature)
+{
+ int i;
+ for (i=0; signature[i]; ++i)
+ if (stbi__get8(s) != signature[i])
+ return 0;
+ stbi__rewind(s);
+ return 1;
+}
+
+static int stbi__hdr_test(stbi__context* s)
+{
+ int r = stbi__hdr_test_core(s, "#?RADIANCE\n");
+ stbi__rewind(s);
+ if(!r) {
+ r = stbi__hdr_test_core(s, "#?RGBE\n");
+ stbi__rewind(s);
+ }
+ return r;
+}
+
+#define STBI__HDR_BUFLEN 1024
+static char *stbi__hdr_gettoken(stbi__context *z, char *buffer)
+{
+ int len=0;
+ char c = '\0';
+
+ c = (char) stbi__get8(z);
+
+ while (!stbi__at_eof(z) && c != '\n') {
+ buffer[len++] = c;
+ if (len == STBI__HDR_BUFLEN-1) {
+ // flush to end of line
+ while (!stbi__at_eof(z) && stbi__get8(z) != '\n')
+ ;
+ break;
+ }
+ c = (char) stbi__get8(z);
+ }
+
+ buffer[len] = 0;
+ return buffer;
+}
+
+static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp)
+{
+ if ( input[3] != 0 ) {
+ float f1;
+ // Exponent
+ f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));
+ if (req_comp <= 2)
+ output[0] = (input[0] + input[1] + input[2]) * f1 / 3;
+ else {
+ output[0] = input[0] * f1;
+ output[1] = input[1] * f1;
+ output[2] = input[2] * f1;
+ }
+ if (req_comp == 2) output[1] = 1;
+ if (req_comp == 4) output[3] = 1;
+ } else {
+ switch (req_comp) {
+ case 4: output[3] = 1; /* fallthrough */
+ case 3: output[0] = output[1] = output[2] = 0;
+ break;
+ case 2: output[1] = 1; /* fallthrough */
+ case 1: output[0] = 0;
+ break;
+ }
+ }
+}
+
+static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+ char buffer[STBI__HDR_BUFLEN];
+ char *token;
+ int valid = 0;
+ int width, height;
+ stbi_uc *scanline;
+ float *hdr_data;
+ int len;
+ unsigned char count, value;
+ int i, j, k, c1,c2, z;
+ const char *headerToken;
+ STBI_NOTUSED(ri);
+
+ // Check identifier
+ headerToken = stbi__hdr_gettoken(s,buffer);
+ if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0)
+ return stbi__errpf("not HDR", "Corrupt HDR image");
+
+ // Parse header
+ for(;;) {
+ token = stbi__hdr_gettoken(s,buffer);
+ if (token[0] == 0) break;
+ if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+ }
+
+ if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format");
+
+ // Parse width and height
+ // can't use sscanf() if we're not using stdio!
+ token = stbi__hdr_gettoken(s,buffer);
+ if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format");
+ token += 3;
+ height = (int) strtol(token, &token, 10);
+ while (*token == ' ') ++token;
+ if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format");
+ token += 3;
+ width = (int) strtol(token, NULL, 10);
+
+ if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)");
+ if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)");
+
+ *x = width;
+ *y = height;
+
+ if (comp) *comp = 3;
+ if (req_comp == 0) req_comp = 3;
+
+ if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0))
+ return stbi__errpf("too large", "HDR image is too large");
+
+ // Read data
+ hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0);
+ if (!hdr_data)
+ return stbi__errpf("outofmem", "Out of memory");
+
+ // Load image data
+ // image data is stored as some number of sca
+ if ( width < 8 || width >= 32768) {
+ // Read flat data
+ for (j=0; j < height; ++j) {
+ for (i=0; i < width; ++i) {
+ stbi_uc rgbe[4];
+ main_decode_loop:
+ stbi__getn(s, rgbe, 4);
+ stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
+ }
+ }
+ } else {
+ // Read RLE-encoded data
+ scanline = NULL;
+
+ for (j = 0; j < height; ++j) {
+ c1 = stbi__get8(s);
+ c2 = stbi__get8(s);
+ len = stbi__get8(s);
+ if (c1 != 2 || c2 != 2 || (len & 0x80)) {
+ // not run-length encoded, so we have to actually use THIS data as a decoded
+ // pixel (note this can't be a valid pixel--one of RGB must be >= 128)
+ stbi_uc rgbe[4];
+ rgbe[0] = (stbi_uc) c1;
+ rgbe[1] = (stbi_uc) c2;
+ rgbe[2] = (stbi_uc) len;
+ rgbe[3] = (stbi_uc) stbi__get8(s);
+ stbi__hdr_convert(hdr_data, rgbe, req_comp);
+ i = 1;
+ j = 0;
+ STBI_FREE(scanline);
+ goto main_decode_loop; // yes, this makes no sense
+ }
+ len <<= 8;
+ len |= stbi__get8(s);
+ if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); }
+ if (scanline == NULL) {
+ scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0);
+ if (!scanline) {
+ STBI_FREE(hdr_data);
+ return stbi__errpf("outofmem", "Out of memory");
+ }
+ }
+
+ for (k = 0; k < 4; ++k) {
+ int nleft;
+ i = 0;
+ while ((nleft = width - i) > 0) {
+ count = stbi__get8(s);
+ if (count > 128) {
+ // Run
+ value = stbi__get8(s);
+ count -= 128;
+ if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
+ for (z = 0; z < count; ++z)
+ scanline[i++ * 4 + k] = value;
+ } else {
+ // Dump
+ if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
+ for (z = 0; z < count; ++z)
+ scanline[i++ * 4 + k] = stbi__get8(s);
+ }
+ }
+ }
+ for (i=0; i < width; ++i)
+ stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);
+ }
+ if (scanline)
+ STBI_FREE(scanline);
+ }
+
+ return hdr_data;
+}
+
+static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ char buffer[STBI__HDR_BUFLEN];
+ char *token;
+ int valid = 0;
+ int dummy;
+
+ if (!x) x = &dummy;
+ if (!y) y = &dummy;
+ if (!comp) comp = &dummy;
+
+ if (stbi__hdr_test(s) == 0) {
+ stbi__rewind( s );
+ return 0;
+ }
+
+ for(;;) {
+ token = stbi__hdr_gettoken(s,buffer);
+ if (token[0] == 0) break;
+ if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+ }
+
+ if (!valid) {
+ stbi__rewind( s );
+ return 0;
+ }
+ token = stbi__hdr_gettoken(s,buffer);
+ if (strncmp(token, "-Y ", 3)) {
+ stbi__rewind( s );
+ return 0;
+ }
+ token += 3;
+ *y = (int) strtol(token, &token, 10);
+ while (*token == ' ') ++token;
+ if (strncmp(token, "+X ", 3)) {
+ stbi__rewind( s );
+ return 0;
+ }
+ token += 3;
+ *x = (int) strtol(token, NULL, 10);
+ *comp = 3;
+ return 1;
+}
+#endif // STBI_NO_HDR
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ void *p;
+ stbi__bmp_data info;
+
+ info.all_a = 255;
+ p = stbi__bmp_parse_header(s, &info);
+ if (p == NULL) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (x) *x = s->img_x;
+ if (y) *y = s->img_y;
+ if (comp) {
+ if (info.bpp == 24 && info.ma == 0xff000000)
+ *comp = 3;
+ else
+ *comp = info.ma ? 4 : 3;
+ }
+ return 1;
+}
+#endif
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int channelCount, dummy, depth;
+ if (!x) x = &dummy;
+ if (!y) y = &dummy;
+ if (!comp) comp = &dummy;
+ if (stbi__get32be(s) != 0x38425053) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (stbi__get16be(s) != 1) {
+ stbi__rewind( s );
+ return 0;
+ }
+ stbi__skip(s, 6);
+ channelCount = stbi__get16be(s);
+ if (channelCount < 0 || channelCount > 16) {
+ stbi__rewind( s );
+ return 0;
+ }
+ *y = stbi__get32be(s);
+ *x = stbi__get32be(s);
+ depth = stbi__get16be(s);
+ if (depth != 8 && depth != 16) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (stbi__get16be(s) != 3) {
+ stbi__rewind( s );
+ return 0;
+ }
+ *comp = 4;
+ return 1;
+}
+
+static int stbi__psd_is16(stbi__context *s)
+{
+ int channelCount, depth;
+ if (stbi__get32be(s) != 0x38425053) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (stbi__get16be(s) != 1) {
+ stbi__rewind( s );
+ return 0;
+ }
+ stbi__skip(s, 6);
+ channelCount = stbi__get16be(s);
+ if (channelCount < 0 || channelCount > 16) {
+ stbi__rewind( s );
+ return 0;
+ }
+ STBI_NOTUSED(stbi__get32be(s));
+ STBI_NOTUSED(stbi__get32be(s));
+ depth = stbi__get16be(s);
+ if (depth != 16) {
+ stbi__rewind( s );
+ return 0;
+ }
+ return 1;
+}
+#endif
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int act_comp=0,num_packets=0,chained,dummy;
+ stbi__pic_packet packets[10];
+
+ if (!x) x = &dummy;
+ if (!y) y = &dummy;
+ if (!comp) comp = &dummy;
+
+ if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) {
+ stbi__rewind(s);
+ return 0;
+ }
+
+ stbi__skip(s, 88);
+
+ *x = stbi__get16be(s);
+ *y = stbi__get16be(s);
+ if (stbi__at_eof(s)) {
+ stbi__rewind( s);
+ return 0;
+ }
+ if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
+ stbi__rewind( s );
+ return 0;
+ }
+
+ stbi__skip(s, 8);
+
+ do {
+ stbi__pic_packet *packet;
+
+ if (num_packets==sizeof(packets)/sizeof(packets[0]))
+ return 0;
+
+ packet = &packets[num_packets++];
+ chained = stbi__get8(s);
+ packet->size = stbi__get8(s);
+ packet->type = stbi__get8(s);
+ packet->channel = stbi__get8(s);
+ act_comp |= packet->channel;
+
+ if (stbi__at_eof(s)) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (packet->size != 8) {
+ stbi__rewind( s );
+ return 0;
+ }
+ } while (chained);
+
+ *comp = (act_comp & 0x10 ? 4 : 3);
+
+ return 1;
+}
+#endif
+
+// *************************************************************************************************
+// Portable Gray Map and Portable Pixel Map loader
+// by Ken Miller
+//
+// PGM: http://netpbm.sourceforge.net/doc/pgm.html
+// PPM: http://netpbm.sourceforge.net/doc/ppm.html
+//
+// Known limitations:
+// Does not support comments in the header section
+// Does not support ASCII image data (formats P2 and P3)
+
+#ifndef STBI_NO_PNM
+
+static int stbi__pnm_test(stbi__context *s)
+{
+ char p, t;
+ p = (char) stbi__get8(s);
+ t = (char) stbi__get8(s);
+ if (p != 'P' || (t != '5' && t != '6')) {
+ stbi__rewind( s );
+ return 0;
+ }
+ return 1;
+}
+
+static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+ stbi_uc *out;
+ STBI_NOTUSED(ri);
+
+ ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n);
+ if (ri->bits_per_channel == 0)
+ return 0;
+
+ if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+ if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)");
+
+ *x = s->img_x;
+ *y = s->img_y;
+ if (comp) *comp = s->img_n;
+
+ if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0))
+ return stbi__errpuc("too large", "PNM too large");
+
+ out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0);
+ if (!out) return stbi__errpuc("outofmem", "Out of memory");
+ if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) {
+ STBI_FREE(out);
+ return stbi__errpuc("bad PNM", "PNM file truncated");
+ }
+
+ if (req_comp && req_comp != s->img_n) {
+ if (ri->bits_per_channel == 16) {
+ out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y);
+ } else {
+ out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
+ }
+ if (out == NULL) return out; // stbi__convert_format frees input on failure
+ }
+ return out;
+}
+
+static int stbi__pnm_isspace(char c)
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
+}
+
+static void stbi__pnm_skip_whitespace(stbi__context *s, char *c)
+{
+ for (;;) {
+ while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))
+ *c = (char) stbi__get8(s);
+
+ if (stbi__at_eof(s) || *c != '#')
+ break;
+
+ while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' )
+ *c = (char) stbi__get8(s);
+ }
+}
+
+static int stbi__pnm_isdigit(char c)
+{
+ return c >= '0' && c <= '9';
+}
+
+static int stbi__pnm_getinteger(stbi__context *s, char *c)
+{
+ int value = 0;
+
+ while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {
+ value = value*10 + (*c - '0');
+ *c = (char) stbi__get8(s);
+ if((value > 214748364) || (value == 214748364 && *c > '7'))
+ return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int");
+ }
+
+ return value;
+}
+
+static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int maxv, dummy;
+ char c, p, t;
+
+ if (!x) x = &dummy;
+ if (!y) y = &dummy;
+ if (!comp) comp = &dummy;
+
+ stbi__rewind(s);
+
+ // Get identifier
+ p = (char) stbi__get8(s);
+ t = (char) stbi__get8(s);
+ if (p != 'P' || (t != '5' && t != '6')) {
+ stbi__rewind(s);
+ return 0;
+ }
+
+ *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm
+
+ c = (char) stbi__get8(s);
+ stbi__pnm_skip_whitespace(s, &c);
+
+ *x = stbi__pnm_getinteger(s, &c); // read width
+ if(*x == 0)
+ return stbi__err("invalid width", "PPM image header had zero or overflowing width");
+ stbi__pnm_skip_whitespace(s, &c);
+
+ *y = stbi__pnm_getinteger(s, &c); // read height
+ if (*y == 0)
+ return stbi__err("invalid width", "PPM image header had zero or overflowing width");
+ stbi__pnm_skip_whitespace(s, &c);
+
+ maxv = stbi__pnm_getinteger(s, &c); // read max value
+ if (maxv > 65535)
+ return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images");
+ else if (maxv > 255)
+ return 16;
+ else
+ return 8;
+}
+
+static int stbi__pnm_is16(stbi__context *s)
+{
+ if (stbi__pnm_info(s, NULL, NULL, NULL) == 16)
+ return 1;
+ return 0;
+}
+#endif
+
+static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)
+{
+ #ifndef STBI_NO_JPEG
+ if (stbi__jpeg_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PNG
+ if (stbi__png_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_GIF
+ if (stbi__gif_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_BMP
+ if (stbi__bmp_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PSD
+ if (stbi__psd_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PIC
+ if (stbi__pic_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PNM
+ if (stbi__pnm_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_HDR
+ if (stbi__hdr_info(s, x, y, comp)) return 1;
+ #endif
+
+ // test tga last because it's a crappy test!
+ #ifndef STBI_NO_TGA
+ if (stbi__tga_info(s, x, y, comp))
+ return 1;
+ #endif
+ return stbi__err("unknown image type", "Image not of any known type, or corrupt");
+}
+
+static int stbi__is_16_main(stbi__context *s)
+{
+ #ifndef STBI_NO_PNG
+ if (stbi__png_is16(s)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PSD
+ if (stbi__psd_is16(s)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PNM
+ if (stbi__pnm_is16(s)) return 1;
+ #endif
+ return 0;
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp)
+{
+ FILE *f = stbi__fopen(filename, "rb");
+ int result;
+ if (!f) return stbi__err("can't fopen", "Unable to open file");
+ result = stbi_info_from_file(f, x, y, comp);
+ fclose(f);
+ return result;
+}
+
+STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)
+{
+ int r;
+ stbi__context s;
+ long pos = ftell(f);
+ stbi__start_file(&s, f);
+ r = stbi__info_main(&s,x,y,comp);
+ fseek(f,pos,SEEK_SET);
+ return r;
+}
+
+STBIDEF int stbi_is_16_bit(char const *filename)
+{
+ FILE *f = stbi__fopen(filename, "rb");
+ int result;
+ if (!f) return stbi__err("can't fopen", "Unable to open file");
+ result = stbi_is_16_bit_from_file(f);
+ fclose(f);
+ return result;
+}
+
+STBIDEF int stbi_is_16_bit_from_file(FILE *f)
+{
+ int r;
+ stbi__context s;
+ long pos = ftell(f);
+ stbi__start_file(&s, f);
+ r = stbi__is_16_main(&s);
+ fseek(f,pos,SEEK_SET);
+ return r;
+}
+#endif // !STBI_NO_STDIO
+
+STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)
+{
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__info_main(&s,x,y,comp);
+}
+
+STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)
+{
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
+ return stbi__info_main(&s,x,y,comp);
+}
+
+STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len)
+{
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__is_16_main(&s);
+}
+
+STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user)
+{
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
+ return stbi__is_16_main(&s);
+}
+
+#endif // STB_IMAGE_IMPLEMENTATION
+
+/*
+ revision history:
+ 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
+ 2.19 (2018-02-11) fix warning
+ 2.18 (2018-01-30) fix warnings
+ 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug
+ 1-bit BMP
+ *_is_16_bit api
+ avoid warnings
+ 2.16 (2017-07-23) all functions have 16-bit variants;
+ STBI_NO_STDIO works again;
+ compilation fixes;
+ fix rounding in unpremultiply;
+ optimize vertical flip;
+ disable raw_len validation;
+ documentation fixes
+ 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode;
+ warning fixes; disable run-time SSE detection on gcc;
+ uniform handling of optional "return" values;
+ thread-safe initialization of zlib tables
+ 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
+ 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now
+ 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+ 2.11 (2016-04-02) allocate large structures on the stack
+ remove white matting for transparent PSD
+ fix reported channel count for PNG & BMP
+ re-enable SSE2 in non-gcc 64-bit
+ support RGB-formatted JPEG
+ read 16-bit PNGs (only as 8-bit)
+ 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED
+ 2.09 (2016-01-16) allow comments in PNM files
+ 16-bit-per-pixel TGA (not bit-per-component)
+ info() for TGA could break due to .hdr handling
+ info() for BMP to shares code instead of sloppy parse
+ can use STBI_REALLOC_SIZED if allocator doesn't support realloc
+ code cleanup
+ 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
+ 2.07 (2015-09-13) fix compiler warnings
+ partial animated GIF support
+ limited 16-bpc PSD support
+ #ifdef unused functions
+ bug with < 92 byte PIC,PNM,HDR,TGA
+ 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value
+ 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning
+ 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit
+ 2.03 (2015-04-12) extra corruption checking (mmozeiko)
+ stbi_set_flip_vertically_on_load (nguillemot)
+ fix NEON support; fix mingw support
+ 2.02 (2015-01-19) fix incorrect assert, fix warning
+ 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2
+ 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
+ 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)
+ progressive JPEG (stb)
+ PGM/PPM support (Ken Miller)
+ STBI_MALLOC,STBI_REALLOC,STBI_FREE
+ GIF bugfix -- seemingly never worked
+ STBI_NO_*, STBI_ONLY_*
+ 1.48 (2014-12-14) fix incorrectly-named assert()
+ 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)
+ optimize PNG (ryg)
+ fix bug in interlaced PNG with user-specified channel count (stb)
+ 1.46 (2014-08-26)
+ fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG
+ 1.45 (2014-08-16)
+ fix MSVC-ARM internal compiler error by wrapping malloc
+ 1.44 (2014-08-07)
+ various warning fixes from Ronny Chevalier
+ 1.43 (2014-07-15)
+ fix MSVC-only compiler problem in code changed in 1.42
+ 1.42 (2014-07-09)
+ don't define _CRT_SECURE_NO_WARNINGS (affects user code)
+ fixes to stbi__cleanup_jpeg path
+ added STBI_ASSERT to avoid requiring assert.h
+ 1.41 (2014-06-25)
+ fix search&replace from 1.36 that messed up comments/error messages
+ 1.40 (2014-06-22)
+ fix gcc struct-initialization warning
+ 1.39 (2014-06-15)
+ fix to TGA optimization when req_comp != number of components in TGA;
+ fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)
+ add support for BMP version 5 (more ignored fields)
+ 1.38 (2014-06-06)
+ suppress MSVC warnings on integer casts truncating values
+ fix accidental rename of 'skip' field of I/O
+ 1.37 (2014-06-04)
+ remove duplicate typedef
+ 1.36 (2014-06-03)
+ convert to header file single-file library
+ if de-iphone isn't set, load iphone images color-swapped instead of returning NULL
+ 1.35 (2014-05-27)
+ various warnings
+ fix broken STBI_SIMD path
+ fix bug where stbi_load_from_file no longer left file pointer in correct place
+ fix broken non-easy path for 32-bit BMP (possibly never used)
+ TGA optimization by Arseny Kapoulkine
+ 1.34 (unknown)
+ use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case
+ 1.33 (2011-07-14)
+ make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements
+ 1.32 (2011-07-13)
+ support for "info" function for all supported filetypes (SpartanJ)
+ 1.31 (2011-06-20)
+ a few more leak fixes, bug in PNG handling (SpartanJ)
+ 1.30 (2011-06-11)
+ added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)
+ removed deprecated format-specific test/load functions
+ removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway
+ error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)
+ fix inefficiency in decoding 32-bit BMP (David Woo)
+ 1.29 (2010-08-16)
+ various warning fixes from Aurelien Pocheville
+ 1.28 (2010-08-01)
+ fix bug in GIF palette transparency (SpartanJ)
+ 1.27 (2010-08-01)
+ cast-to-stbi_uc to fix warnings
+ 1.26 (2010-07-24)
+ fix bug in file buffering for PNG reported by SpartanJ
+ 1.25 (2010-07-17)
+ refix trans_data warning (Won Chun)
+ 1.24 (2010-07-12)
+ perf improvements reading from files on platforms with lock-heavy fgetc()
+ minor perf improvements for jpeg
+ deprecated type-specific functions so we'll get feedback if they're needed
+ attempt to fix trans_data warning (Won Chun)
+ 1.23 fixed bug in iPhone support
+ 1.22 (2010-07-10)
+ removed image *writing* support
+ stbi_info support from Jetro Lauha
+ GIF support from Jean-Marc Lienher
+ iPhone PNG-extensions from James Brown
+ warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)
+ 1.21 fix use of 'stbi_uc' in header (reported by jon blow)
+ 1.20 added support for Softimage PIC, by Tom Seddon
+ 1.19 bug in interlaced PNG corruption check (found by ryg)
+ 1.18 (2008-08-02)
+ fix a threading bug (local mutable static)
+ 1.17 support interlaced PNG
+ 1.16 major bugfix - stbi__convert_format converted one too many pixels
+ 1.15 initialize some fields for thread safety
+ 1.14 fix threadsafe conversion bug
+ header-file-only version (#define STBI_HEADER_FILE_ONLY before including)
+ 1.13 threadsafe
+ 1.12 const qualifiers in the API
+ 1.11 Support installable IDCT, colorspace conversion routines
+ 1.10 Fixes for 64-bit (don't use "unsigned long")
+ optimized upsampling by Fabian "ryg" Giesen
+ 1.09 Fix format-conversion for PSD code (bad global variables!)
+ 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz
+ 1.07 attempt to fix C++ warning/errors again
+ 1.06 attempt to fix C++ warning/errors again
+ 1.05 fix TGA loading to return correct *comp and use good luminance calc
+ 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free
+ 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR
+ 1.02 support for (subset of) HDR files, float interface for preferred access to them
+ 1.01 fix bug: possible bug in handling right-side up bmps... not sure
+ fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all
+ 1.00 interface to zlib that skips zlib header
+ 0.99 correct handling of alpha in palette
+ 0.98 TGA loader by lonesock; dynamically add loaders (untested)
+ 0.97 jpeg errors on too large a file; also catch another malloc failure
+ 0.96 fix detection of invalid v value - particleman@mollyrocket forum
+ 0.95 during header scan, seek to markers in case of padding
+ 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same
+ 0.93 handle jpegtran output; verbose errors
+ 0.92 read 4,8,16,24,32-bit BMP files of several formats
+ 0.91 output 24-bit Windows 3.0 BMP files
+ 0.90 fix a few more warnings; bump version number to approach 1.0
+ 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd
+ 0.60 fix compiling as c++
+ 0.59 fix warnings: merge Dave Moore's -Wall fixes
+ 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian
+ 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available
+ 0.56 fix bug: zlib uncompressed mode len vs. nlen
+ 0.55 fix bug: restart_interval not initialized to 0
+ 0.54 allow NULL for 'int *comp'
+ 0.53 fix bug in png 3->4; speedup png decoding
+ 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments
+ 0.51 obey req_comp requests, 1-component jpegs return as 1-component,
+ on 'test' only check type, not whether we support this variant
+ 0.50 (2006-11-19)
+ first released version
+*/
+
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+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 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/src/util/string.h b/src/util/string.h
new file mode 100644
index 0000000..b261e76
--- /dev/null
+++ b/src/util/string.h
@@ -0,0 +1,23 @@
+#pragma once
+#include <string.h>
+
+#include "typedef.h"
+
+template <U32 N>
+struct STR {
+ char data[N];
+ enum {
+ size = N
+ };
+ STR() {
+ memset( data, 0, N );
+ }
+ STR( const char* str ) {
+ memcpy( data, str, N );
+ }
+ STR( const STR<N>& str ) {
+ memcpy( data, str.data, N );
+ }
+
+ operator char*() { return data; }
+};
diff --git a/src/util/thread.cpp b/src/util/thread.cpp
new file mode 100644
index 0000000..74b01fc
--- /dev/null
+++ b/src/util/thread.cpp
@@ -0,0 +1,2 @@
+#define THREAD_IMPLEMENTATION
+#include "thread.h"
diff --git a/src/util/thread.h b/src/util/thread.h
new file mode 100644
index 0000000..72ef5fb
--- /dev/null
+++ b/src/util/thread.h
@@ -0,0 +1,1518 @@
+/*
+------------------------------------------------------------------------------
+ Licensing information can be found at the end of the file.
+------------------------------------------------------------------------------
+
+thread.h - v0.3 - Cross platform threading functions for C/C++.
+
+Do this:
+ #define THREAD_IMPLEMENTATION
+before you include this file in *one* C/C++ file to create the implementation.
+*/
+
+#ifndef thread_h
+#define thread_h
+
+#ifndef THREAD_U64
+ #define THREAD_U64 unsigned long long
+#endif
+
+#define THREAD_STACK_SIZE_DEFAULT ( 0 )
+#define THREAD_SIGNAL_WAIT_INFINITE ( -1 )
+#define THREAD_QUEUE_WAIT_INFINITE ( -1 )
+
+typedef void* thread_id_t;
+typedef thread_id_t THREAD_IT;
+thread_id_t thread_current_thread_id( void );
+void thread_yield( void );
+void thread_set_high_priority( void );
+void thread_exit( int return_code );
+
+typedef void* thread_ptr_t;
+typedef thread_ptr_t THREAD_PTR;
+thread_ptr_t thread_create( int (*thread_proc)( void* ), void* user_data, int stack_size );
+void thread_destroy( thread_ptr_t thread );
+int thread_join( thread_ptr_t thread );
+int thread_detach( thread_ptr_t thread );
+
+typedef union thread_mutex_t thread_mutex_t;
+typedef thread_mutex_t THREAD_MUTEX;
+void thread_mutex_init( thread_mutex_t* mutex );
+void thread_mutex_term( thread_mutex_t* mutex );
+void thread_mutex_lock( thread_mutex_t* mutex );
+void thread_mutex_unlock( thread_mutex_t* mutex );
+
+typedef union thread_signal_t thread_signal_t;
+typedef thread_signal_t THREAD_SIGNAL;
+void thread_signal_init( thread_signal_t* signal );
+void thread_signal_term( thread_signal_t* signal );
+void thread_signal_raise( thread_signal_t* signal );
+int thread_signal_wait( thread_signal_t* signal, int timeout_ms );
+
+typedef union thread_atomic_int_t thread_atomic_int_t;
+typedef thread_atomic_int_t THREAD_ATOMIC_INT;
+int thread_atomic_int_load( thread_atomic_int_t* atomic );
+void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired );
+int thread_atomic_int_inc( thread_atomic_int_t* atomic );
+int thread_atomic_int_dec( thread_atomic_int_t* atomic );
+int thread_atomic_int_add( thread_atomic_int_t* atomic, int value );
+int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value );
+int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired );
+int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired );
+
+typedef union thread_atomic_ptr_t thread_atomic_ptr_t;
+typedef thread_atomic_ptr_t THREAD_ATOMIC_PTR;
+void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic );
+void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired );
+void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired );
+void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired );
+
+typedef union thread_timer_t thread_timer_t;
+typedef thread_timer_t THREAD_TIMER;
+void thread_timer_init( thread_timer_t* timer );
+void thread_timer_term( thread_timer_t* timer );
+void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds );
+
+typedef void* thread_tls_t;
+typedef thread_tls_t THREAD_TLS;
+thread_tls_t thread_tls_create( void );
+void thread_tls_destroy( thread_tls_t tls );
+void thread_tls_set( thread_tls_t tls, void* value );
+void* thread_tls_get( thread_tls_t tls );
+
+typedef struct thread_queue_t thread_queue_t;
+typedef thread_queue_t THREAD_QUEUE;
+void thread_queue_init( thread_queue_t* queue, int size, void** values, int count );
+void thread_queue_term( thread_queue_t* queue );
+int thread_queue_produce( thread_queue_t* queue, void* value, int timeout_ms );
+void* thread_queue_consume( thread_queue_t* queue, int timeout_ms );
+int thread_queue_count( thread_queue_t* queue );
+
+#endif /* thread_h */
+
+
+/**
+
+thread.h
+========
+
+Cross platform threading functions for C/C++.
+
+Example
+-------
+
+Here's a basic sample program which starts a second thread which just waits and prints a message.
+
+ #define THREAD_IMPLEMENTATION
+ #include "thread.h"
+
+ #include <stdio.h> // for printf
+
+ int thread_proc( void* user_data) {
+ thread_timer_t timer;
+ thread_timer_init( &timer );
+
+ int count = 0;
+ thread_atomic_int_t* exit_flag = (thread_atomic_int_t*) user_data;
+ while( thread_atomic_int_load( exit_flag ) == 0 ) {
+ printf( "Thread... " );
+ thread_timer_wait( &timer, 1000000000 ); // sleep for a second
+ ++count;
+ }
+
+ thread_timer_term( &timer );
+ printf( "Done\n" );
+ return count;
+ }
+
+ int main( int argc, char** argv ) {
+ thread_atomic_int_t exit_flag;
+ thread_atomic_int_store( &exit_flag, 0 );
+
+ thread_ptr_t thread = thread_create( thread_proc, &exit_flag, "Example thread", THREAD_STACK_SIZE_DEFAULT );
+
+ thread_timer_t timer;
+ thread_timer_init( &timer );
+ for( int i = 0; i < 5; ++i ) {
+ printf( "Main... " );
+ thread_timer_wait( &timer, 2000000000 ); // sleep for two seconds
+ }
+ thread_timer_term( &timer );
+
+ thread_atomic_int_store( &exit_flag, 1 ); // signal thread to exit
+ int retval = thread_join( thread );
+
+ printf( "Count: %d\n", retval );
+
+ thread_destroy( thread );
+ return retval;
+ }
+
+
+API Documentation
+-----------------
+
+thread.h is a single-header library, and does not need any .lib files or other binaries, or any build scripts. To use it,
+you just include thread.h to get the API declarations. To get the definitions, you must include thread.h from *one*
+single C or C++ file, and #define the symbol `THREAD_IMPLEMENTATION` before you do.
+
+
+### Customization
+
+thread.h allows for specifying the exact type of 64-bit unsigned integer to be used in its API. By default, it is
+defined as `unsigned long long`, but as this is not a standard type on all compilers, you can redefine it by #defining
+THREAD_U64 before including thread.h. This is useful if you, for example, use the types from `<stdint.h>` in the rest of
+your program, and you want thread.h to use compatible types. In this case, you would include thread.h using the
+following code:
+
+ #define THREAD_U64 uint64_t
+ #include "thread.h"
+
+Note that when customizing this data type, you need to use the same definition in every place where you include
+thread.h, as it affect the declarations as well as the definitions.
+
+
+thread_current_thread_id
+------------------------
+
+ thread_id_t thread_current_thread_id( void )
+
+Returns a unique identifier for the calling thread. After the thread terminates, the id might be reused for new threads.
+
+
+thread_yield
+------------
+
+ void thread_yield( void )
+
+Makes the calling thread yield execution to another thread. The operating system controls which thread is switched to.
+
+
+thread_set_high_priority
+------------------------
+
+ void thread_set_high_priority( void )
+
+When created, threads are set to run at normal priority. In some rare cases, such as a sound buffer update loop, it can
+be necessary to have one thread of your application run on a higher priority than the rest. Calling
+`thread_set_high_priority` will raise the priority of the calling thread, giving it a chance to be run more often.
+Do not increase the priority of a thread unless you absolutely have to, as it can negatively affect performance if used
+without care.
+
+
+thread_exit
+-----------
+
+ void thread_exit( int return_code )
+
+Exits the calling thread, as if you had done `return return_code;` from the main body of the thread function.
+
+
+thread_create
+-------------
+
+ thread_ptr_t thread_create( int (*thread_proc)( void* ), void* user_data, int stack_size )
+
+Creates a new thread running the `thread_proc` function, passing the `user_data` through to it. The thread will have
+the stack size specified in the `stack_size` parameter. To get the operating system default stack size, use the
+defined constant `THREAD_STACK_SIZE_DEFAULT`. When returning from the thread_proc function, the value you return can
+be received in another thread by calling thread_join. `thread_create` returns a pointer to the thread instance, which
+can be used as a parameter to the functions `thread_destroy` and `thread_join`.
+
+
+thread_destroy
+--------------
+
+ void thread_destroy( thread_ptr_t thread )
+
+Destroys a thread that was created by calling `thread_create`. Make sure the thread has exited before you attempt to
+destroy it. This can be accomplished by calling `thread_join`. It is not possible for force termination of a thread by
+calling `thread_destroy`.
+
+
+thread_join
+-----------
+
+ int thread_join( thread_ptr_t thread )
+
+Waits for the specified thread to exit. Returns the value which the thread returned when exiting.
+
+
+thread_detach
+-------------
+
+ int thread_detach( thread_ptr_t thread )
+
+Marks the thread as detached. When a detached thread terminates, its resources are automatically released back to the
+system without the need for another thread to join with the terminated thread.
+
+
+thread_mutex_init
+-----------------
+
+ void thread_mutex_init( thread_mutex_t* mutex )
+
+Initializes the specified mutex instance, preparing it for use. A mutex can be used to lock sections of code, such that
+it can only be run by one thread at a time.
+
+
+thread_mutex_term
+-----------------
+
+ void thread_mutex_term( thread_mutex_t* mutex )
+
+Terminates the specified mutex instance, releasing any system resources held by it.
+
+
+thread_mutex_lock
+-----------------
+
+ void thread_mutex_lock( thread_mutex_t* mutex )
+
+Takes an exclusive lock on a mutex. If the lock is already taken by another thread, `thread_mutex_lock` will yield the
+calling thread and wait for the lock to become available before returning. The mutex must be initialized by calling
+`thread_mutex_init` before it can be locked.
+
+
+thread_mutex_unlock
+-------------------
+
+ void thread_mutex_unlock( thread_mutex_t* mutex )
+
+Releases a lock taken by calling `thread_mutex_lock`.
+
+
+thread_signal_init
+------------------
+
+ void thread_signal_init( thread_signal_t* signal )
+
+Initializes the specified signal instance, preparing it for use. A signal works like a flag, which can be waited on by
+one thread, until it is raised from another thread.
+
+
+thread_signal_term
+------------------
+
+ void thread_signal_term( thread_signal_t* signal )
+
+Terminates the specified signal instance, releasing any system resources held by it.
+
+
+thread_signal_raise
+-------------------
+
+ void thread_signal_raise( thread_signal_t* signal )
+
+Raise the specified signal. Other threads waiting for the signal will proceed.
+
+
+thread_signal_wait
+------------------
+
+ int thread_signal_wait( thread_signal_t* signal, int timeout_ms )
+
+Waits for a signal to be raised, or until `timeout_ms` milliseconds have passed. If the wait timed out, a value of 0 is
+returned, otherwise a non-zero value is returned. If the `timeout_ms` parameter is THREAD_SIGNAL_WAIT_INFINITE,
+`thread_signal_wait` waits indefinitely.
+
+
+thread_atomic_int_load
+----------------------
+
+ int thread_atomic_int_load( thread_atomic_int_t* atomic )
+
+Returns the value of `atomic` as an atomic operation.
+
+
+thread_atomic_int_store
+-----------------------
+
+ void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired )
+
+Sets the value of `atomic` as an atomic operation.
+
+
+thread_atomic_int_inc
+---------------------
+
+ int thread_atomic_int_inc( thread_atomic_int_t* atomic )
+
+Increments the value of `atomic` by one, as an atomic operation. Returns the value `atomic` had before the operation.
+
+
+thread_atomic_int_dec
+---------------------
+
+ int thread_atomic_int_dec( thread_atomic_int_t* atomic )
+
+Decrements the value of `atomic` by one, as an atomic operation. Returns the value `atomic` had before the operation.
+
+
+thread_atomic_int_add
+---------------------
+
+ int thread_atomic_int_add( thread_atomic_int_t* atomic, int value )
+
+Adds the specified value to `atomic`, as an atomic operation. Returns the value `atomic` had before the operation.
+
+
+thread_atomic_int_sub
+---------------------
+
+ int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value )
+
+Subtracts the specified value to `atomic`, as an atomic operation. Returns the value `atomic` had before the operation.
+
+
+thread_atomic_int_swap
+----------------------
+
+ int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired )
+
+Sets the value of `atomic` as an atomic operation. Returns the value `atomic` had before the operation.
+
+
+thread_atomic_int_compare_and_swap
+----------------------------------
+
+ int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired )
+
+Compares the value of `atomic` to the value of `expected`, and if they match, sets the vale of `atomic` to `desired`,
+all as an atomic operation. Returns the value `atomic` had before the operation.
+
+
+thread_atomic_ptr_load
+----------------------
+
+ void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic )
+
+Returns the value of `atomic` as an atomic operation.
+
+
+thread_atomic_ptr_store
+-----------------------
+
+ void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired )
+
+Sets the value of `atomic` as an atomic operation.
+
+
+thread_atomic_ptr_swap
+----------------------
+
+ void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired )
+
+Sets the value of `atomic` as an atomic operation. Returns the value `atomic` had before the operation.
+
+
+thread_atomic_ptr_compare_and_swap
+----------------------------------
+
+ void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired )
+
+Compares the value of `atomic` to the value of `expected`, and if they match, sets the vale of `atomic` to `desired`,
+all as an atomic operation. Returns the value `atomic` had before the operation.
+
+
+thread_timer_init
+-----------------
+
+ void thread_timer_init( thread_timer_t* timer )
+
+Initializes the specified timer instance, preparing it for use. A timer can be used to sleep a thread for a high
+precision duration.
+
+
+thread_timer_term
+-----------------
+
+ void thread_timer_term( thread_timer_t* timer )
+
+Terminates the specified timer instance, releasing any system resources held by it.
+
+
+thread_timer_wait
+-----------------
+
+ void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds )
+
+Waits until `nanoseconds` amount of time have passed, before returning.
+
+
+thread_tls_create
+-----------------
+
+ thread_tls_t thread_tls_create( void )
+
+Creates a thread local storage (TLS) index. Once created, each thread has its own value for that TLS index, which can
+be set or retrieved individually.
+
+
+thread_tls_destroy
+------------------
+
+ void thread_tls_destroy( thread_tls_t tls )
+
+Destroys the specified TLS index. No further calls to `thread_tls_set` or `thread_tls_get` are valid after this.
+
+
+thread_tls_set
+--------------
+
+ void thread_tls_set( thread_tls_t tls, void* value )
+
+Stores a value in the calling thread's slot for the specified TLS index. Each thread has its own value for each TLS
+index.
+
+
+thread_tls_get
+--------------
+
+ void* thread_tls_get( thread_tls_t tls )
+
+Retrieves the value from the calling thread's slot for the specified TLS index. Each thread has its own value for each
+TLS index.
+
+
+thread_queue_init
+-----------------
+
+ void thread_queue_init( thread_queue_t* queue, int size, void** values, int count )
+
+Initializes the specified queue instance, preparing it for use. The queue is a lock-free (but not wait-free)
+single-producer/single-consumer queue - it will not acquire any locks as long as there is space for adding or items to
+be consume, but will lock and wait when there is not. The `size` parameter specifies the number of elements in the
+queue. The `values` parameter is an array of queue slots (`size` elements in length), each being of type `void*`. If
+the queue is initially empty, the `count` parameter should be 0, otherwise it indicates the number of entires, from the
+start of the `values` array, that the queue is initialized with. The `values` array is not copied, and must remain valid
+until `thread_queue_term` is called.
+
+
+thread_queue_term
+-----------------
+
+ void thread_queue_term( thread_queue_t* queue )
+
+Terminates the specified queue instance, releasing any system resources held by it.
+
+
+thread_queue_produce
+--------------------
+
+ int thread_queue_produce( thread_queue_t* queue, void* value, int timeout_ms )
+
+Adds an element to a single-producer/single-consumer queue. If there is space in the queue to add another element, no
+lock will be taken. If the queue is full, calling thread will sleep until an element is consumed from another thread,
+before adding the element, or until `timeout_ms` milliseconds have passed. If the wait timed out, a value of 0 is
+returned, otherwise a non-zero value is returned. If the `timeout_ms` parameter is THREAD_QUEUE_WAIT_INFINITE,
+`thread_queue_produce` waits indefinitely.
+
+
+thread_queue_consume
+--------------------
+
+ void* thread_queue_consume( thread_queue_t* queue, int timeout_ms )
+
+Removes an element from a single-producer/single-consumer queue. If the queue contains at least one element, no lock
+will be taken. If the queue is empty, the calling thread will sleep until an element is added from another thread, or
+until `timeout_ms` milliseconds have passed. If the wait timed out, a value of NULL is returned, otherwise
+`thread_queue_consume` returns the value that was removed from the queue. If the `timeout_ms` parameter is
+THREAD_QUEUE_WAIT_INFINITE, `thread_queue_consume` waits indefinitely.
+
+
+thread_queue_count
+------------------
+
+ int thread_queue_count( thread_queue_t* queue )
+
+Returns the number of elements currently held in a single-producer/single-consumer queue. Be aware that by the time you
+get the count, it might have changed by another thread calling consume or produce, so use with care.
+
+*/
+
+
+/*
+----------------------
+ IMPLEMENTATION
+----------------------
+*/
+
+#ifndef thread_impl
+#define thread_impl
+
+union thread_mutex_t
+ {
+ void* align;
+ char data[ 64 ];
+ };
+
+union thread_signal_t
+ {
+ void* align;
+ char data[ 116 ];
+ };
+
+union thread_atomic_int_t
+ {
+ void* align;
+ long i;
+ };
+
+union thread_atomic_ptr_t
+ {
+ void* ptr;
+ };
+
+union thread_timer_t
+ {
+ void* data;
+ char d[ 8 ];
+ };
+
+struct thread_queue_t
+ {
+ thread_signal_t data_ready;
+ thread_signal_t space_open;
+ thread_atomic_int_t count;
+ thread_atomic_int_t head;
+ thread_atomic_int_t tail;
+ void** values;
+ int size;
+ #ifndef NDEBUG
+ thread_atomic_int_t id_produce_is_set;
+ thread_id_t id_produce;
+ thread_atomic_int_t id_consume_is_set;
+ thread_id_t id_consume;
+ #endif
+ };
+
+#endif /* thread_impl */
+
+
+
+#ifdef THREAD_IMPLEMENTATION
+#undef THREAD_IMPLEMENTATION
+
+#ifndef THREAD_ASSERT
+ #undef _CRT_NONSTDC_NO_DEPRECATE
+ #define _CRT_NONSTDC_NO_DEPRECATE
+ #undef _CRT_SECURE_NO_WARNINGS
+ #define _CRT_SECURE_NO_WARNINGS
+ #include <assert.h>
+ #define THREAD_ASSERT( expression, message ) assert( ( expression ) && ( message ) )
+#endif
+
+
+#if defined( _WIN32 )
+
+ #pragma comment( lib, "winmm.lib" )
+
+ #define _CRT_NONSTDC_NO_DEPRECATE
+ #define _CRT_SECURE_NO_WARNINGS
+
+ #if !defined( _WIN32_WINNT ) || _WIN32_WINNT < 0x0501
+ #undef _WIN32_WINNT
+ #define _WIN32_WINNT 0x501// requires Windows XP minimum
+ #endif
+
+ #define _WINSOCKAPI_
+ #pragma warning( push )
+ #pragma warning( disable: 4619 )
+ #pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives'
+ #pragma warning( disable: 4768 ) // __declspec attributes before linkage specification are ignored
+ #pragma warning( disable: 4255 ) // 'function' : no function prototype given: converting '()' to '(void)'
+ #include <windows.h>
+ #pragma warning( pop )
+
+
+#elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ #include <pthread.h>
+ #include <errno.h>
+ #include <string.h>
+ #include <sys/time.h>
+ #include <stdint.h>
+
+#else
+ #error Unknown platform.
+#endif
+
+
+
+thread_id_t thread_current_thread_id( void )
+ {
+ #if defined( _WIN32 )
+
+ return (void*) (uintptr_t)GetCurrentThreadId();
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return (void*) pthread_self();
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_yield( void )
+ {
+ #if defined( _WIN32 )
+
+ SwitchToThread();
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ sched_yield();
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_exit( int return_code )
+ {
+ #if defined( _WIN32 )
+
+ ExitThread( (DWORD) return_code );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_exit( (void*)(uintptr_t) return_code );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+thread_ptr_t thread_create( int (*thread_proc)( void* ), void* user_data, int stack_size )
+ {
+ #if defined( _WIN32 )
+
+ DWORD thread_id;
+ HANDLE handle = CreateThread( NULL, stack_size > 0 ? (size_t)stack_size : 0U,
+ (LPTHREAD_START_ROUTINE)(uintptr_t) thread_proc, user_data, 0, &thread_id );
+ if( !handle ) return NULL;
+
+ return (thread_ptr_t) handle;
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_t thread;
+ if( 0 != pthread_create( &thread, NULL, ( void* (*)( void * ) ) thread_proc, user_data ) )
+ return NULL;
+
+ return (thread_ptr_t) thread;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_destroy( thread_ptr_t thread )
+ {
+ #if defined( _WIN32 )
+
+ WaitForSingleObject( (HANDLE) thread, INFINITE );
+ CloseHandle( (HANDLE) thread );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_join( (pthread_t) thread, NULL );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_join( thread_ptr_t thread )
+ {
+ #if defined( _WIN32 )
+
+ WaitForSingleObject( (HANDLE) thread, INFINITE );
+ DWORD retval;
+ GetExitCodeThread( (HANDLE) thread, &retval );
+ return (int) retval;
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ void* retval;
+ pthread_join( (pthread_t) thread, &retval );
+ return (int)(uintptr_t) retval;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_detach( thread_ptr_t thread )
+ {
+ #if defined( _WIN32 )
+
+ return CloseHandle( (HANDLE) thread ) != 0;
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return pthread_detach( (pthread_t) thread ) == 0;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_set_high_priority( void )
+ {
+ #if defined( _WIN32 )
+
+ SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ struct sched_param sp;
+ memset( &sp, 0, sizeof( sp ) );
+ sp.sched_priority = sched_get_priority_min( SCHED_RR );
+ pthread_setschedparam( pthread_self(), SCHED_RR, &sp);
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_mutex_init( thread_mutex_t* mutex )
+ {
+ #if defined( _WIN32 )
+
+ // Compile-time size check
+ #pragma warning( push )
+ #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int
+ struct x { char thread_mutex_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( CRITICAL_SECTION ) ? 0 : 1 ); };
+ #pragma warning( pop )
+
+ InitializeCriticalSectionAndSpinCount( (CRITICAL_SECTION*) mutex, 32 );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ // Compile-time size check
+ struct x { char thread_mutex_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( pthread_mutex_t ) ? 0 : 1 ); };
+
+ pthread_mutex_init( (pthread_mutex_t*) mutex, NULL );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_mutex_term( thread_mutex_t* mutex )
+ {
+ #if defined( _WIN32 )
+
+ DeleteCriticalSection( (CRITICAL_SECTION*) mutex );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_mutex_destroy( (pthread_mutex_t*) mutex );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_mutex_lock( thread_mutex_t* mutex )
+ {
+ #if defined( _WIN32 )
+
+ EnterCriticalSection( (CRITICAL_SECTION*) mutex );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_mutex_lock( (pthread_mutex_t*) mutex );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_mutex_unlock( thread_mutex_t* mutex )
+ {
+ #if defined( _WIN32 )
+
+ LeaveCriticalSection( (CRITICAL_SECTION*) mutex );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_mutex_unlock( (pthread_mutex_t*) mutex );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+struct thread_internal_signal_t
+ {
+ #if defined( _WIN32 )
+
+ #if _WIN32_WINNT >= 0x0600
+ CRITICAL_SECTION mutex;
+ CONDITION_VARIABLE condition;
+ int value;
+ #else
+ #pragma message( "Warning: _WIN32_WINNT < 0x0600 - condition variables not available" )
+ HANDLE event;
+ #endif
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_mutex_t mutex;
+ pthread_cond_t condition;
+ int value;
+
+ #else
+ #error Unknown platform.
+ #endif
+ };
+
+
+void thread_signal_init( thread_signal_t* signal )
+ {
+ // Compile-time size check
+ #if defined( _MSC_VER )
+ #pragma warning( push )
+ #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int
+ #endif
+ struct x { char thread_signal_type_too_small : ( sizeof( thread_signal_t ) < sizeof( struct thread_internal_signal_t ) ? 0 : 1 ); };
+ #if defined( _MSC_VER )
+ #pragma warning( pop )
+ #endif
+
+ struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal;
+
+ #if defined( _WIN32 )
+
+ #if _WIN32_WINNT >= 0x0600
+ InitializeCriticalSectionAndSpinCount( &internal->mutex, 32 );
+ InitializeConditionVariable( &internal->condition );
+ internal->value = 0;
+ #else
+ internal->event = CreateEvent( NULL, FALSE, FALSE, NULL );
+ #endif
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_mutex_init( &internal->mutex, NULL );
+ pthread_cond_init( &internal->condition, NULL );
+ internal->value = 0;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+ void thread_signal_term( thread_signal_t* signal )
+ {
+ struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal;
+
+ #if defined( _WIN32 )
+
+ #if _WIN32_WINNT >= 0x0600
+ DeleteCriticalSection( &internal->mutex );
+ #else
+ CloseHandle( internal->event );
+ #endif
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_mutex_destroy( &internal->mutex );
+ pthread_cond_destroy( &internal->condition );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_signal_raise( thread_signal_t* signal )
+ {
+ struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal;
+
+ #if defined( _WIN32 )
+
+ #if _WIN32_WINNT >= 0x0600
+ EnterCriticalSection( &internal->mutex );
+ internal->value = 1;
+ LeaveCriticalSection( &internal->mutex );
+ WakeConditionVariable( &internal->condition );
+ #else
+ SetEvent( internal->event );
+ #endif
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_mutex_lock( &internal->mutex );
+ internal->value = 1;
+ pthread_mutex_unlock( &internal->mutex );
+ pthread_cond_signal( &internal->condition );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_signal_wait( thread_signal_t* signal, int timeout_ms )
+ {
+ struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal;
+
+ #if defined( _WIN32 )
+
+ #if _WIN32_WINNT >= 0x0600
+ int timed_out = 0;
+ EnterCriticalSection( &internal->mutex );
+ while( internal->value == 0 )
+ {
+ BOOL res = SleepConditionVariableCS( &internal->condition, &internal->mutex, timeout_ms < 0 ? INFINITE : timeout_ms );
+ if( !res && GetLastError() == ERROR_TIMEOUT ) { timed_out = 1; break; }
+ }
+ internal->value = 0;
+ LeaveCriticalSection( &internal->mutex );
+ return !timed_out;
+ #else
+ int failed = WAIT_OBJECT_0 != WaitForSingleObject( internal->event, timeout_ms < 0 ? INFINITE : timeout_ms );
+ return !failed;
+ #endif
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ struct timespec ts;
+ if( timeout_ms >= 0 )
+ {
+ struct timeval tv;
+ gettimeofday( &tv, NULL );
+ ts.tv_sec = time( NULL ) + timeout_ms / 1000;
+ ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * ( timeout_ms % 1000 );
+ ts.tv_sec += ts.tv_nsec / ( 1000 * 1000 * 1000 );
+ ts.tv_nsec %= ( 1000 * 1000 * 1000 );
+ }
+
+ int timed_out = 0;
+ pthread_mutex_lock( &internal->mutex );
+ while( internal->value == 0 )
+ {
+ if( timeout_ms < 0 )
+ pthread_cond_wait( &internal->condition, &internal->mutex );
+ else if( pthread_cond_timedwait( &internal->condition, &internal->mutex, &ts ) == ETIMEDOUT )
+ {
+ timed_out = 1;
+ break;
+ }
+
+ }
+ if( !timed_out ) internal->value = 0;
+ pthread_mutex_unlock( &internal->mutex );
+ return !timed_out;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_atomic_int_load( thread_atomic_int_t* atomic )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedCompareExchange( &atomic->i, 0, 0 );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return (int)__sync_fetch_and_add( &atomic->i, 0 );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired )
+ {
+ #if defined( _WIN32 )
+
+ InterlockedExchange( &atomic->i, desired );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ __sync_fetch_and_and( &atomic->i, 0 );
+ __sync_fetch_and_or( &atomic->i, desired );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_atomic_int_inc( thread_atomic_int_t* atomic )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedIncrement( &atomic->i ) - 1;
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return (int)__sync_fetch_and_add( &atomic->i, 1 );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_atomic_int_dec( thread_atomic_int_t* atomic )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedDecrement( &atomic->i ) + 1;
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return (int)__sync_fetch_and_sub( &atomic->i, 1 );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_atomic_int_add( thread_atomic_int_t* atomic, int value )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedExchangeAdd ( &atomic->i, value );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return (int)__sync_fetch_and_add( &atomic->i, value );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedExchangeAdd( &atomic->i, -value );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return (int)__sync_fetch_and_sub( &atomic->i, value );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedExchange( &atomic->i, desired );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ int old = (int)__sync_lock_test_and_set( &atomic->i, desired );
+ __sync_lock_release( &atomic->i );
+ return old;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedCompareExchange( &atomic->i, desired, expected );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return (int)__sync_val_compare_and_swap( &atomic->i, expected, desired );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedCompareExchangePointer( &atomic->ptr, 0, 0 );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return __sync_fetch_and_add( &atomic->ptr, 0 );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired )
+ {
+ #if defined( _WIN32 )
+
+ #pragma warning( push )
+ #pragma warning( disable: 4302 ) // 'type cast' : truncation from 'void *' to 'LONG'
+ #pragma warning( disable: 4311 ) // pointer truncation from 'void *' to 'LONG'
+ #pragma warning( disable: 4312 ) // conversion from 'LONG' to 'PVOID' of greater size
+ InterlockedExchangePointer( &atomic->ptr, desired );
+ #pragma warning( pop )
+
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ __sync_lock_test_and_set( &atomic->ptr, desired );
+ __sync_lock_release( &atomic->ptr );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired )
+ {
+ #if defined( _WIN32 )
+
+ #pragma warning( push )
+ #pragma warning( disable: 4302 ) // 'type cast' : truncation from 'void *' to 'LONG'
+ #pragma warning( disable: 4311 ) // pointer truncation from 'void *' to 'LONG'
+ #pragma warning( disable: 4312 ) // conversion from 'LONG' to 'PVOID' of greater size
+ return InterlockedExchangePointer( &atomic->ptr, desired );
+ #pragma warning( pop )
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ void* old = __sync_lock_test_and_set( &atomic->ptr, desired );
+ __sync_lock_release( &atomic->ptr );
+ return old;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired )
+ {
+ #if defined( _WIN32 )
+
+ return InterlockedCompareExchangePointer( &atomic->ptr, desired, expected );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return __sync_val_compare_and_swap( &atomic->ptr, expected, desired );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_timer_init( thread_timer_t* timer )
+ {
+ #if defined( _WIN32 )
+
+ // Compile-time size check
+ #pragma warning( push )
+ #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int
+ struct x { char thread_timer_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( HANDLE ) ? 0 : 1 ); };
+ #pragma warning( pop )
+
+ TIMECAPS tc;
+ if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR )
+ timeBeginPeriod( tc.wPeriodMin );
+
+ *(HANDLE*)timer = CreateWaitableTimer( NULL, TRUE, NULL );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ // Nothing
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_timer_term( thread_timer_t* timer )
+ {
+ #if defined( _WIN32 )
+
+ CloseHandle( *(HANDLE*)timer );
+
+ TIMECAPS tc;
+ if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR )
+ timeEndPeriod( tc.wPeriodMin );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ // Nothing
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds )
+ {
+ #if defined( _WIN32 )
+
+ LARGE_INTEGER due_time;
+ due_time.QuadPart = - (LONGLONG) ( nanoseconds / 100 );
+ BOOL b = SetWaitableTimer( *(HANDLE*)timer, &due_time, 0, 0, 0, FALSE );
+ (void) b;
+ WaitForSingleObject( *(HANDLE*)timer, INFINITE );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ struct timespec rem;
+ struct timespec req;
+ req.tv_sec = nanoseconds / 1000000000ULL;
+ req.tv_nsec = nanoseconds - req.tv_sec * 1000000000ULL;
+ while( nanosleep( &req, &rem ) )
+ req = rem;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+thread_tls_t thread_tls_create( void )
+ {
+ #if defined( _WIN32 )
+
+ DWORD tls = TlsAlloc();
+ if( tls == TLS_OUT_OF_INDEXES )
+ return NULL;
+ else
+ return (thread_tls_t) (uintptr_t) tls;
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_key_t tls;
+ if( pthread_key_create( &tls, NULL ) == 0 )
+ return (thread_tls_t) (uintptr_t) tls;
+ else
+ return NULL;
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_tls_destroy( thread_tls_t tls )
+ {
+ #if defined( _WIN32 )
+
+ TlsFree( (DWORD) (uintptr_t) tls );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_key_delete( (pthread_key_t) (uintptr_t) tls );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_tls_set( thread_tls_t tls, void* value )
+ {
+ #if defined( _WIN32 )
+
+ TlsSetValue( (DWORD) (uintptr_t) tls, value );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ pthread_setspecific( (pthread_key_t) (uintptr_t) tls, value );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void* thread_tls_get( thread_tls_t tls )
+ {
+ #if defined( _WIN32 )
+
+ return TlsGetValue( (DWORD) (uintptr_t) tls );
+
+ #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ )
+
+ return pthread_getspecific( (pthread_key_t) (uintptr_t) tls );
+
+ #else
+ #error Unknown platform.
+ #endif
+ }
+
+
+void thread_queue_init( thread_queue_t* queue, int size, void** values, int count )
+ {
+ queue->values = values;
+ thread_signal_init( &queue->data_ready );
+ thread_signal_init( &queue->space_open );
+ thread_atomic_int_store( &queue->head, 0 );
+ thread_atomic_int_store( &queue->tail, count > size ? size : count );
+ thread_atomic_int_store( &queue->count, count > size ? size : count );
+ queue->size = size;
+ #ifndef NDEBUG
+ thread_atomic_int_store( &queue->id_produce_is_set, 0 );
+ thread_atomic_int_store( &queue->id_consume_is_set, 0 );
+ #endif
+ }
+
+
+void thread_queue_term( thread_queue_t* queue )
+ {
+ thread_signal_term( &queue->space_open );
+ thread_signal_term( &queue->data_ready );
+ }
+
+
+int thread_queue_produce( thread_queue_t* queue, void* value, int timeout_ms )
+ {
+ #ifndef NDEBUG
+ if( thread_atomic_int_compare_and_swap( &queue->id_produce_is_set, 0, 1 ) == 0 )
+ queue->id_produce = thread_current_thread_id();
+ THREAD_ASSERT( thread_current_thread_id() == queue->id_produce, "thread_queue_produce called from multiple threads" );
+ #endif
+ while( thread_atomic_int_load( &queue->count ) == queue->size ) // TODO: fix signal so that this can be an "if" instead of "while"
+ {
+ if( timeout_ms == 0 ) return 0;
+ if( thread_signal_wait( &queue->space_open, timeout_ms == THREAD_QUEUE_WAIT_INFINITE ? THREAD_SIGNAL_WAIT_INFINITE : timeout_ms ) == 0 )
+ return 0;
+ }
+ int tail = thread_atomic_int_inc( &queue->tail );
+ queue->values[ tail % queue->size ] = value;
+ if( thread_atomic_int_inc( &queue->count ) == 0 )
+ thread_signal_raise( &queue->data_ready );
+ return 1;
+ }
+
+
+void* thread_queue_consume( thread_queue_t* queue, int timeout_ms )
+ {
+ #ifndef NDEBUG
+ if( thread_atomic_int_compare_and_swap( &queue->id_consume_is_set, 0, 1 ) == 0 )
+ queue->id_consume = thread_current_thread_id();
+ THREAD_ASSERT( thread_current_thread_id() == queue->id_consume, "thread_queue_consume called from multiple threads" );
+ #endif
+ while( thread_atomic_int_load( &queue->count ) == 0 ) // TODO: fix signal so that this can be an "if" instead of "while"
+ {
+ if( timeout_ms == 0 ) return NULL;
+ if( thread_signal_wait( &queue->data_ready, timeout_ms == THREAD_QUEUE_WAIT_INFINITE ? THREAD_SIGNAL_WAIT_INFINITE : timeout_ms ) == 0 )
+ return NULL;
+ }
+ int head = thread_atomic_int_inc( &queue->head );
+ void* retval = queue->values[ head % queue->size ];
+ if( thread_atomic_int_dec( &queue->count ) == queue->size )
+ thread_signal_raise( &queue->space_open );
+ return retval;
+ }
+
+
+int thread_queue_count( thread_queue_t* queue )
+ {
+ return thread_atomic_int_load( &queue->count );
+ }
+
+
+#endif /* THREAD_IMPLEMENTATION */
+
+/*
+revision history:
+ 0.3 set_high_priority API change. Fixed spurious wakeup bug in signal. Added
+ timeout param to queue produce/consume. Various cleanup and trivial fixes.
+ 0.2 first publicly released version
+*/
+
+/*
+------------------------------------------------------------------------------
+
+This software is available under 2 licenses - you may choose the one you like.
+
+------------------------------------------------------------------------------
+
+ALTERNATIVE A - MIT License
+
+Copyright (c) 2015 Mattias Gustavsson
+
+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.
+
+------------------------------------------------------------------------------
+
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+
+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 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/src/util/typedef.h b/src/util/typedef.h
new file mode 100644
index 0000000..53c4010
--- /dev/null
+++ b/src/util/typedef.h
@@ -0,0 +1,58 @@
+#pragma once
+#ifdef DEBUG
+#include <stdio.h>
+#endif
+
+enum STAT {
+ STAT_OK,
+ STAT_ERR,
+ STAT_BREAK,
+ STAT_ALREADYEXISTS,
+};
+
+#define OK( x ) ( (STAT)( x ) == STAT_OK )
+
+typedef char I8;
+typedef short I16;
+typedef int I32;
+typedef long long I64;
+
+typedef unsigned char U8;
+typedef unsigned short U16;
+typedef unsigned int U32;
+typedef unsigned long long U64;
+
+typedef unsigned long ULONG;
+
+typedef float F32;
+typedef double F64;
+
+typedef unsigned long PTR;
+
+#define fn( ... ) [&]( __VA_ARGS__ )
+#define pfn( ... ) []( __VA_ARGS__ )
+
+template <typename T>
+struct __defer_t {
+ T f;
+ __defer_t( T f ) : f( f ) {};
+ ~__defer_t() { f(); }
+};
+
+template <typename T>
+__defer_t<T> __defer_func( T f ) {
+ return __defer_t<T>( f );
+}
+
+#define DEFER_1( x, y ) x##y
+#define DEFER_2( x, y ) DEFER_1( x, y )
+#define DEFER_3( x ) DEFER_2( x, __COUNTER__ )
+#define defer( code ) auto DEFER_3( _defer_ ) = __defer_func( fn() { code; } )
+
+#ifdef DEBUG
+#define dlog( ... ) (void)printf( __VA_ARGS__ )
+#define ddef( x ) x
+#else
+#define dlog( ... ) {}
+#define ddef( x )
+#endif
diff --git a/src/util/vector.h b/src/util/vector.h
new file mode 100644
index 0000000..33f6551
--- /dev/null
+++ b/src/util/vector.h
@@ -0,0 +1,192 @@
+#pragma once
+
+#include <math.h>
+#include "typedef.h"
+
+static const F32 PI = 3.14159265359f;
+static const F32 PIRAD = 0.01745329251f;
+static const F32 RADPI = 57.2957795131f;
+
+struct VEC2 {
+ F32 x;
+ F32 y;
+
+ VEC2() { x = y = 0.0f; }
+ VEC2( F32 X, F32 Y ) { x = X; y = Y; }
+ VEC2( const F32* v ) { x = v[0]; y = v[1]; }
+ VEC2( const VEC2& v ) { x = v.x; y = v.y; }
+
+ bool operator==( const VEC2& v ) const { return ( x == v.x && y == v.y ); }
+ VEC2& operator=( const VEC2& v ) { x = v.x; y = v.y; return *this; }
+ F32& operator[]( I32 i ) { return ( (F32*)this )[i]; }
+ F32 operator[]( I32 i ) const { return ( (const F32*)this )[i]; }
+
+ VEC2& operator+=( const VEC2& v ) { x += v.x; y += v.y; return *this; }
+ VEC2& operator-=( const VEC2& v ) { x -= v.x; y -= v.y; return *this; }
+ VEC2& operator*=( const VEC2& v ) { x *= v.x; y *= v.y; return *this; }
+ VEC2& operator/=( const VEC2& v ) { x /= v.x; y /= v.y; return *this; }
+
+ VEC2 operator+( const VEC2& v ) const { return VEC2( x + v.x, y + v.y ); }
+ VEC2 operator-( const VEC2& v ) const { return VEC2( x - v.x, y - v.y ); }
+ VEC2 operator*( const VEC2& v ) const { return VEC2( x * v.x, y * v.y ); }
+ VEC2 operator/( const VEC2& v ) const { return VEC2( x / v.x, y / v.y ); }
+
+ VEC2& operator+=( const F32& v ) { x += v; y += v; return *this; }
+ VEC2& operator-=( const F32& v ) { x -= v; y -= v; return *this; }
+ VEC2& operator*=( const F32& v ) { x *= v; y *= v; return *this; }
+ VEC2& operator/=( const F32& v ) { x /= v; y /= v; return *this; }
+
+ VEC2 operator/( const F32& v ) const { return VEC2( x / v, y / v ); }
+ VEC2 operator*( const F32& v ) const { return VEC2( x * v, y * v ); }
+ VEC2 operator+( const F32& v ) const { return VEC2( x + v, y + v ); }
+ VEC2 operator-( const F32& v ) const { return VEC2( x - v, y - v ); }
+};
+
+struct VEC3 {
+ F32 x;
+ F32 y;
+ F32 z;
+
+ VEC3() { x = y = z = 0.0f; }
+ VEC3( F32 X, F32 Y, F32 Z ) { x = X; y = Y; z = Z; }
+ VEC3( const F32* v ) { x = v[0]; y = v[1]; z = v[2]; }
+ VEC3( const VEC3& v ) { x = v.x; y = v.y; z = v.z; }
+
+ VEC3( const VEC2& v ) { x = v.x; y = v.y; z = 0.f; }
+
+ VEC3& operator=( const VEC3& v ) { x = v.x; y = v.y; z = v.z; return *this; }
+ F32& operator[]( I32 i ) { return ( (F32*)this )[i]; }
+ F32 operator[]( I32 i ) const { return ( (F32*)this )[i]; }
+
+ VEC3& operator+=( const VEC3& v ) { x += v.x; y += v.y; z += v.z; return *this; }
+ VEC3& operator-=( const VEC3& v ) { x -= v.x; y -= v.y; z -= v.z; return *this; }
+ VEC3& operator*=( const VEC3& v ) { x *= v.x; y *= v.y; z *= v.z; return *this; }
+ VEC3& operator/=( const VEC3& v ) { x /= v.x; y /= v.y; z /= v.z; return *this; }
+
+ VEC3 operator+( const VEC3& v ) const { return VEC3( x + v.x, y + v.y, z + v.z ); }
+ VEC3 operator-( const VEC3& v ) const { return VEC3( x - v.x, y - v.y, z - v.z ); }
+ VEC3 operator*( const VEC3& v ) const { return VEC3( x * v.x, y * v.y, z * v.z ); }
+ VEC3 operator/( const VEC3& v ) const { return VEC3( x / v.x, y / v.y, z / v.z ); }
+
+ VEC3& operator+=( const F32& v ) { x += v; y += v; z += v; return *this; }
+ VEC3& operator-=( const F32& v ) { x -= v; y -= v; z -= v; return *this; }
+ VEC3& operator*=( const F32& v ) { x *= v; y *= v; z *= v; return *this; }
+ VEC3& operator/=( const F32& v ) { x /= v; y /= v; z /= v; return *this; }
+
+ VEC3 operator/( const F32& v ) const { return VEC3( x / v, y / v, z / v ); }
+ VEC3 operator*( const F32& v ) const { return VEC3( x * v, y * v, z * v ); }
+ VEC3 operator+( const F32& v ) const { return VEC3( x + v, y + v, z + v ); }
+ VEC3 operator-( const F32& v ) const { return VEC3( x - v, y - v, z - v ); }
+};
+
+struct VEC4 {
+ F32 x, y, z, w;
+
+ VEC4() { x = y = z = w = 0; }
+ VEC4( F32 X, F32 Y, F32 Z, F32 W ) { x = X; y = Y; z = Z; w = W; }
+ VEC4( const VEC4& v ) { x = v.x; y = v.y; z = v.z; w = v.w; }
+ VEC4( const VEC3& v ) { x = v.x; y = v.y; z = v.z; w = 1.f; }
+ VEC4( const VEC2& v ) { x = v.x; y = v.y; z = 0.f; w = 1.f; }
+
+ VEC4( const VEC2& v, F32 W ) { x = v.x; y = v.y; z = 0.f; w = W; }
+ VEC4( const VEC3& v, F32 W ) { x = v.x; y = v.y; z = v.z; w = W; }
+
+ VEC4( F32* v ) { x = v[0]; y = v[1]; z = v[2]; w = v[3]; }
+
+ VEC4 operator=( const VEC4& v ) { x = v.x; y = v.y; z = v.z; w = v.w; return *this; }
+ F32& operator[]( const int i ) { return *(&x + i); }
+ F32 operator[]( const int i ) const { return *(&x + i); }
+
+ VEC4& operator+=( const VEC4& v ) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; }
+ VEC4& operator-=( const VEC4& v ) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; }
+ VEC4& operator*=( const VEC4& v ) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; }
+ VEC4& operator/=( const VEC4& v ) { x /= v.x; y /= v.y; z /= v.z; w /= v.w; return *this; }
+
+ VEC4 operator+( const VEC4& v ) { return VEC4( x + v.x, y + v.y, z + v.z, w + v.w ); }
+ VEC4 operator-( const VEC4& v ) { return VEC4( x - v.x, y - v.y, z - v.z, w - v.w ); }
+ VEC4 operator*( const VEC4& v ) { return VEC4( x * v.x, y * v.y, z * v.z, w * v.w ); }
+ VEC4 operator/( const VEC4& v ) { return VEC4( x / v.x, y / v.y, z / v.z, w / v.w ); }
+
+ VEC4& operator+=( const F32& v ) { x += v; y += v; z += v; w += v; return *this; }
+ VEC4& operator-=( const F32& v ) { x -= v; y -= v; z -= v; w -= v; return *this; }
+ VEC4& operator*=( const F32& v ) { x *= v; y *= v; z *= v; w *= v; return *this; }
+ VEC4& operator/=( const F32& v ) { x /= v; y /= v; z /= v; w /= v; return *this; }
+
+ VEC4 operator+( const F32& v ) { return VEC4( x + v, y + v, z + v, w + v ); }
+ VEC4 operator-( const F32& v ) { return VEC4( x - v, y - v, z - v, w - v ); }
+ VEC4 operator*( const F32& v ) { return VEC4( x * v, y * v, z * v, w * v ); }
+ VEC4 operator/( const F32& v ) { return VEC4( x / v, y / v, z / v, w / v ); }
+};
+
+inline U8 is_zero( VEC2 v ) { return v.x == 0.f && v.y == 0.f; }
+inline U8 is_zero( VEC3 v ) { return v.x == 0.f && v.y == 0.f && v.z == 0.f; }
+inline U8 is_zero( VEC4 v ) { return v.x == 0.f && v.y == 0.f && v.z == 0.f && v.w == 0.f; }
+
+inline F32 vec_len( VEC2 v ) { return sqrtf( v.x * v.x + v.y * v.y ); }
+inline F32 vec_lensq( VEC2 v ) { return v.x * v.x + v.y * v.y; }
+
+inline F32 vec_len( VEC3 v ) { return sqrtf( v.x * v.x + v.y * v.y + v.z * v.z ); }
+inline F32 vec_lensq( VEC3 v ) { return v.x * v.x + v.y * v.y + v.z * v.z; }
+inline F32 vec_len2d( VEC3 v ) { return sqrtf( v.x * v.x + v.y * v.y ); }
+inline F32 vec_len2dsq( VEC3 v ) { return v.x * v.x + v.y * v.y; }
+
+inline F32 vec_len( VEC4 v ) { return sqrtf( v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w ); }
+inline F32 vec_lensq( VEC4 v ) { return v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w; }
+inline F32 vec_len3d( VEC4 v ) { return sqrtf( v.x * v.x + v.y * v.y + v.z * v.z ); }
+inline F32 vec_len3dsq( VEC4 v ) { return v.x * v.x + v.y * v.y + v.z * v.z; }
+
+inline F32 vec_distsq( VEC2 v1, VEC2 v2 ) { return vec_lensq( v1 - v2 ); }
+inline F32 vec_dist( VEC2 v1, VEC2 v2 ) { return sqrtf( vec_distsq( v1, v2 ) ); }
+inline F32 vec_dot( VEC2 v1, VEC2 v2 ) { return v1.x * v2.x + v1.y * v2.y; }
+inline F32 vec_cross( VEC2 v1, VEC2 v2 ) { return v1.x * v2.y - v1.y * v2.x; }
+
+inline F32 vec_distsq( VEC3 v1, VEC3 v2 ) { return vec_lensq( v1 - v2 ); }
+inline F32 vec_dist( VEC3 v1, VEC3 v2 ) { return sqrtf( vec_distsq( v1, v2 ) ); }
+inline F32 vec_dot( VEC3 v1, VEC3 v2 ) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; }
+inline VEC3 vec_cross( VEC3 v1, VEC3 v2 ) { return { v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x }; }
+
+inline void vec_normalize( VEC2* v ) { F32 l = vec_len( *v ); v->x /= l; v->y /= l; }
+inline void vec_normalize( VEC3* v ) { F32 l = vec_len( *v ); v->x /= l; v->y /= l; v->z /= l; }
+
+inline VEC2 vec_normalize( VEC2 v ) { vec_normalize( &v ); return v; };
+inline VEC3 vec_normalize( VEC3 v ) { vec_normalize( &v ); return v; };
+
+inline VEC3 vec_normangle( VEC3* v ) {
+ v->x = remainderf( v->x, 360.f );
+ v->y = remainderf( v->y, 360.f );
+ v->z = remainderf( v->z, 360.f );
+
+ v->x = fmaxf( fminf( v->x, 90.f ), -90.f );
+ return *v;
+}
+
+inline VEC3 vec_normangle( VEC3 v ) {
+ return vec_normangle( &v );
+}
+
+inline VEC2 vec_normangle( VEC2* v ) {
+ v->x = remainderf( v->x, 360.f );
+ v->y = remainderf( v->y, 360.f );
+
+ v->x = fmaxf( fminf( v->x, 90.f ), -90.f );
+ return *v;
+}
+
+inline VEC2 vec_normangle( VEC2 v ) {
+ return vec_normangle( &v );
+}
+
+inline VEC3 vec_angles( VEC3 v1, VEC3 v2 ) {
+ VEC3 d = v2 - v1;
+
+ F32 m = sqrtf( d.x * d.x + d.y * d.y );
+ F32 p = atan2f( -d.z, m ) * RADPI;
+ F32 y = atan2f( d.y, d.x ) * RADPI;
+
+ return vec_normangle( VEC3( p, y, 0.0f ) );
+}
+
+inline F32 vec_angle( VEC2 v1, VEC2 v2 ) {
+ VEC2 d = v2 - v1;
+ return remainderf( atan2f( d.y, d.x ) * RADPI, 360.f );
+}