diff --git a/README.md b/README.md index bd34717..b18d081 100644 --- a/README.md +++ b/README.md @@ -1,180 +1,11 @@ -![raylib-lua logo](assets/logo.png) +![rayfork-lua logo](assets/logo.png) -[![release](https://img.shields.io/github/v/release/TSnake41/raylib-lua?style=flat-square)](https://github.com/TSnake41/raylib-lua/releases/latest) -[![downloads](https://img.shields.io/github/downloads/tsnake41/raylib-lua/total?style=flat-square)](https://github.com/TSnake41/raylib-lua/releases) +[![release](https://img.shields.io/github/v/release/TSnake41/rayfork-lua?style=flat-square)](https://github.com/TSnake41/rayfork-lua/releases/latest) +[![downloads](https://img.shields.io/github/downloads/tsnake41/rayfork-lua/total?style=flat-square)](https://github.com/TSnake41/rayfork-lua/releases) -## raylib-lua +## rayfork-lua -[LuaJIT](https://luajit.org/)-based binding for [raylib](https://www.raylib.com/), a simple and easy-to-use -library to learn videogames programming. - -This binding is partially based on [raylib-wren/wray](https://github.com/TSnake41/raylib-wren). - -### Usage (raylua_s) - -raylua_s is the script-mode binary of raylib-lua. -Without any argument, you get into the REPL which gives you a minimal Lua] -shell that allows you to run Lua code from terminal.] - -You can specify a Lua file as argument to run the specified Lua file. - -### Usage (raylua_e) - -raylua_e is the embedding-mode binary of raylib-lua. - -This binary allows you to build standalone raylib applications from Lua code. - -There are 3 ways to use it : - - zip mode : - If you specify a zip file as argument, this zip will be used as payload - application, this file expects to have a `main.lua` which is the entry point - of the application. - - directory mode : - Similar to zip mode except that it automatically build the zip payload from - the specified directory. - - lua mode : - Build the executable from a single Lua file. - -Using `require` in embedded mode works as expected but `dofile` and `loadfile` -may not work as expected as these functions load from a external file rather -than from `package` loaders. - -### Building / Updating raylib / Contribution - -To build raylib-lua from source, you need to take care that submodules are -imported, if not or you are unsure : - -```shell -git submodule init -git submodule update -``` - -This make take some time depending on network bandwidth. -Then, raylib-lua should build as expected using `make` tool with a working C compiler. - -A working Lua interpreter is needed, by default the luajit interpreter built -along with `libluajit.a` is used. In case of cross-compiling, you may want to -change which Lua interpreter is used to a one your system supports. -You can specify the interpreter with the `LUA` variable. - -If you need to update raylib binding, there are few tasks to do : - - update `tools/api.h` functions signatures, keep file clean with exactly one function per line. - - update struct definitions in `src/raylib.lua` - -### Loading embedded ressources - -Currently, raylib-lua doesn't support loading ressources from payload using -raylib API. However, you can still arbitrarily load files from payload using -`raylua.loadfile` which returns a boolean indicating success and file content. - -### Making structs - -To make raylib structs, you need to use the LuaJIT FFI. -```lua -local ffi = require "ffi" -``` - -Then use ffi.new to make a struct, e.g `ffi.new("Color", r, g, b, a)` - -#### Note concerning pointers - -You can use `rl.ref` to build a pointer from a struct cdata. -This functions only work struct cdata, in case of primitive cdata, you -need to make a array and pass it directly. - -e.g : -```lua -local int_ptr = ffi.new "int [1]" -local data = tostring(rl.LoadFileData("test.txt", int_ptr)) -local count = tonumber(int_ptr[0]) -``` - -### Example - -```lua -rl.SetConfigFlags(rl.FLAG_VSYNC_HINT) - -rl.InitWindow(800, 450, "raylib [core] example - basic window") - -while not rl.WindowShouldClose() do - rl.BeginDrawing() - - rl.ClearBackground(rl.RAYWHITE) - rl.DrawText("Congrats! You created your first window!", 190, 200, 20, rl.LIGHTGRAY) - - rl.EndDrawing() -end - -rl.CloseWindow() -``` - -### Compatibility - -raylib-lua (raylua) currently uses raylib 3.1-dev API with some 3.0 API compatibility -(see [compat.lua](https://github.com/TSnake41/raylib-lua/blob/master/src/compat.lua)). -physac and rlgl modules are built-in by default. -raygui is supported, but is minimally tested, please report any issues you have -with raygui with raylib-lua (raylua) on GitHub or raylib Discord (#raylib-lua channel) - -#### Global API - -You can make raylib-lua (raylua) partially compatible with -[original raylib-lua](https://github.com/raysan5/raylib-lua) or -[raylib-lua-sol](https://github.com/RobLoach/raylib-lua-sol) with global API by -adding `setmetatable(_G, { __index = rl })` on the first line. - -This will allow direct use of the raylib binding through globals instead of `rl` table. - -You have an example of this in `lua_global_api.lua`. - -### Editor support - -Currently, there is no editor autocompletion/integration support for most editors, but there is a -[third-party autocompletion support](https://github.com/Rabios/raylua/tree/master/zerobrane) -for [ZeroBrane Studio](https://studio.zerobrane.com/) by [Rabios](https://github.com/Rabios). - -#### Debugging - -You can use [Local Lua Debugger for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=tomblind.local-lua-debugger-vscode) -to provide debugging support with Visual Studio Code. -You need to add this at the beginning of your code to use it : -```lua -do local f = getmetatable(rl).__index;rawset(rl, "__index", function (_, k) return select(2, pcall(f, _, k)) end) end -package.path = package.path .. os.getenv "LUA_PATH" -local lldebugger = require "lldebugger"; lldebugger.start() -``` -You also need to setup a launch configuration in Visual Studio Code to run raylua_s with debugger attached, e.g -```json -{ - "type": "lua-local", - "request": "launch", - "name": "(Lua) Launch", - "cwd": "${workspaceFolder}", - "program": { "command": "PATH TO raylua_s" }, - "args": [ "main.lua OR ${file} OR WHATEVER" ] -} -``` -This debugger doesn't support pausing, you need to place a breakpoint before executing -to get a actual debug, otherwise, a error needs to be thrown in the application to get the debugging. -This debugger has a significant overhead, expect a performance loss in intensive projects. - -### Other bindings - -raylib-lua (raylua) is not the only Lua binding for raylib. - -There are some other bindings, which may or may not be up to date. - -[RobLoach/raylib-lua-sol](https://github.com/RobLoach/raylib-lua-sol) - -[raysan5/raylib-lua](https://github.com/raysan5/raylib-lua/) - -[HDPLocust/raylib-luamore](https://github.com/HDPLocust/raylib-luamore) - -[alexander-matz ffi binding](https://gist.github.com/alexander-matz/f8ee4eb9fdf676203d70c1e5e329a6ec) - -[darltrash/raylib-luajit](https://github.com/darltrash/raylib-luajit) - -[Rabios/raylua](https://github.com/Rabios/raylua) +Modular [LuaJIT](https://luajit.org/)-based binding for [SasLuca/rayfork](https://github.com/SasLuca/rayfork). ### Licence diff --git a/makefile b/makefile index 4a6a85f..1050385 100644 --- a/makefile +++ b/makefile @@ -6,10 +6,10 @@ LUA ?= luajit/src/luajit WINDRES ?= windres -CFLAGS += -Iluajit/src -Iraylib/src -Iraygui/src -LDFLAGS += -Lluajit/src -Lraylib -lraylib +CFLAGS += -Iluajit/src +LDFLAGS += -Lluajit/src -MODULES := raymath rlgl easings gestures physac raygui +MODULES := rayfork ifeq ($(OS),Windows_NT) LDFLAGS += -lopengl32 -lgdi32 -lwinmm -static @@ -32,9 +32,6 @@ all: raylua_s raylua_e luajit raylib luajit: $(MAKE) -C luajit amalg CC=$(CC) BUILDMODE=static MACOSX_DEPLOYMENT_TARGET=10.13 -raylib: - $(MAKE) CC=$(CC) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" -C raylib/src - raylua_s: src/raylua_s.o $(EXTERNAL_FILES) libraylua.a $(CC) -o $@ $^ $(LDFLAGS) luajit/src/libluajit.a @@ -45,7 +42,7 @@ raylua_e: src/raylua_e.o src/raylua_self.o src/raylua_builder.o src/lib/miniz.o src/res/icon.res: src/res/icon.rc $(WINDRES) $^ -O coff $@ -libraylua.a: src/raylua.o +libraylua.a: src/lib/rayfork.o src/raylua.o $(AR) rcu $@ $^ raylua.dll: src/raylua.o @@ -72,7 +69,6 @@ clean: src/raylua.o src/raylua_self.o src/raylua_builder.o src/autogen/*.c \ src/lib/miniz.o $(MAKE) -C luajit clean - $(MAKE) -C raylib/src clean .PHONY: all src/autogen/bind.c src/autogen/boot.c raylua_s raylua_e luajit \ raylib clean diff --git a/rayfork b/rayfork new file mode 100644 index 0000000..cbd54ba --- /dev/null +++ b/rayfork @@ -0,0 +1,593 @@ +struct rf_lua_bind_entry { + const char *name; + const char *proto; + void *ptr; +}; + +struct rf_lua_bind_entry rayfork_entries[] = { +{ "rf_calloc_wrapper", "", &rf_calloc_wrapper }, +{ "rf_libc_allocator_wrapper", "", &rf_libc_allocator_wrapper }, +{ "rf_libc_get_file_size", "r", &rf_libc_get_file_size }, +{ "rf_libc_load_file_into_buffer", "", &rf_libc_load_file_into_buffer }, +{ "rf_get_last_recorded_error", "r", &rf_get_last_recorded_error }, +{ "rf_log_type_string", "", &rf_log_type_string }, +{ "rf_set_logger", "", &rf_set_logger }, +{ "rf_set_logger_filter", "", &rf_set_logger_filter }, +{ "rf_libc_printf_logger", "", &rf_libc_printf_logger }, +{ "rf_libc_rand_wrapper", "r", &rf_libc_rand_wrapper }, +{ "rf_decode_utf8_char", "r", &rf_decode_utf8_char }, +{ "rf_count_utf8_chars", "r", &rf_count_utf8_chars }, +{ "rf_decode_utf8_to_buffer", "r", &rf_decode_utf8_to_buffer }, +{ "rf_decode_utf8", "r", &rf_decode_utf8 }, +{ "rf_next_pot", "", &rf_next_pot }, +{ "rf_center_to_screen", "r", &rf_center_to_screen }, +{ "rf_center_to_object", "r", &rf_center_to_object }, +{ "rf_clamp", "", &rf_clamp }, +{ "rf_lerp", "", &rf_lerp }, +{ "rf_vec2_add", "r", &rf_vec2_add }, +{ "rf_vec2_sub", "r", &rf_vec2_sub }, +{ "rf_vec2_len", "", &rf_vec2_len }, +{ "rf_vec2_dot_product", "", &rf_vec2_dot_product }, +{ "rf_vec2_distance", "", &rf_vec2_distance }, +{ "rf_vec2_angle", "", &rf_vec2_angle }, +{ "rf_vec2_scale", "r", &rf_vec2_scale }, +{ "rf_vec2_mul_v", "r", &rf_vec2_mul_v }, +{ "rf_vec2_negate", "r", &rf_vec2_negate }, +{ "rf_vec2_div", "r", &rf_vec2_div }, +{ "rf_vec2_div_v", "r", &rf_vec2_div_v }, +{ "rf_vec2_normalize", "r", &rf_vec2_normalize }, +{ "rf_vec2_lerp", "r", &rf_vec2_lerp }, +{ "rf_vec3_add", "r", &rf_vec3_add }, +{ "rf_vec3_sub", "r", &rf_vec3_sub }, +{ "rf_vec3_mul", "r", &rf_vec3_mul }, +{ "rf_vec3_mul_v", "r", &rf_vec3_mul_v }, +{ "rf_vec3_cross_product", "r", &rf_vec3_cross_product }, +{ "rf_vec3_perpendicular", "r", &rf_vec3_perpendicular }, +{ "rf_vec3_len", "", &rf_vec3_len }, +{ "rf_vec3_dot_product", "", &rf_vec3_dot_product }, +{ "rf_vec3_distance", "", &rf_vec3_distance }, +{ "rf_vec3_scale", "r", &rf_vec3_scale }, +{ "rf_vec3_negate", "r", &rf_vec3_negate }, +{ "rf_vec3_div", "r", &rf_vec3_div }, +{ "rf_vec3_div_v", "r", &rf_vec3_div_v }, +{ "rf_vec3_normalize", "r", &rf_vec3_normalize }, +{ "rf_vec3_ortho_normalize", "", &rf_vec3_ortho_normalize }, +{ "rf_vec3_transform", "r", &rf_vec3_transform }, +{ "rf_vec3_rotate_by_quaternion", "r", &rf_vec3_rotate_by_quaternion }, +{ "rf_vec3_lerp", "r", &rf_vec3_lerp }, +{ "rf_vec3_reflect", "r", &rf_vec3_reflect }, +{ "rf_vec3_min", "r", &rf_vec3_min }, +{ "rf_vec3_max", "r", &rf_vec3_max }, +{ "rf_vec3_barycenter", "r", &rf_vec3_barycenter }, +{ "rf_mat_determinant", "", &rf_mat_determinant }, +{ "rf_mat_trace", "", &rf_mat_trace }, +{ "rf_mat_transpose", "r", &rf_mat_transpose }, +{ "rf_mat_invert", "r", &rf_mat_invert }, +{ "rf_mat_normalize", "r", &rf_mat_normalize }, +{ "rf_mat_identity", "r", &rf_mat_identity }, +{ "rf_mat_add", "r", &rf_mat_add }, +{ "rf_mat_sub", "r", &rf_mat_sub }, +{ "rf_mat_translate", "r", &rf_mat_translate }, +{ "rf_mat_rotate", "r", &rf_mat_rotate }, +{ "rf_mat_rotate_xyz", "r", &rf_mat_rotate_xyz }, +{ "rf_mat_rotate_x", "r", &rf_mat_rotate_x }, +{ "rf_mat_rotate_y", "r", &rf_mat_rotate_y }, +{ "rf_mat_rotate_z", "r", &rf_mat_rotate_z }, +{ "rf_mat_scale", "r", &rf_mat_scale }, +{ "rf_mat_mul", "r", &rf_mat_mul }, +{ "rf_mat_frustum", "r", &rf_mat_frustum }, +{ "rf_mat_perspective", "r", &rf_mat_perspective }, +{ "rf_mat_ortho", "r", &rf_mat_ortho }, +{ "rf_mat_look_at", "r", &rf_mat_look_at }, +{ "rf_mat_to_float16", "r", &rf_mat_to_float16 }, +{ "rf_quaternion_identity", "r", &rf_quaternion_identity }, +{ "rf_quaternion_len", "", &rf_quaternion_len }, +{ "rf_quaternion_normalize", "r", &rf_quaternion_normalize }, +{ "rf_quaternion_invert", "r", &rf_quaternion_invert }, +{ "rf_quaternion_mul", "r", &rf_quaternion_mul }, +{ "rf_quaternion_lerp", "r", &rf_quaternion_lerp }, +{ "rf_quaternion_nlerp", "r", &rf_quaternion_nlerp }, +{ "rf_quaternion_slerp", "r", &rf_quaternion_slerp }, +{ "rf_quaternion_from_vec3_to_vec3", "r", &rf_quaternion_from_vec3_to_vec3 }, +{ "rf_quaternion_from_mat", "r", &rf_quaternion_from_mat }, +{ "rf_quaternion_to_mat", "r", &rf_quaternion_to_mat }, +{ "rf_quaternion_from_axis_angle", "r", &rf_quaternion_from_axis_angle }, +{ "rf_quaternion_to_axis_angle", "", &rf_quaternion_to_axis_angle }, +{ "rf_quaternion_from_euler", "r", &rf_quaternion_from_euler }, +{ "rf_quaternion_to_euler", "r", &rf_quaternion_to_euler }, +{ "rf_quaternion_transform", "r", &rf_quaternion_transform }, +{ "rf_rec_match", "", &rf_rec_match }, +{ "rf_check_collision_recs", "", &rf_check_collision_recs }, +{ "rf_check_collision_circles", "", &rf_check_collision_circles }, +{ "rf_check_collision_circle_rec", "", &rf_check_collision_circle_rec }, +{ "rf_check_collision_point_rec", "", &rf_check_collision_point_rec }, +{ "rf_check_collision_point_circle", "", &rf_check_collision_point_circle }, +{ "rf_check_collision_point_triangle", "", &rf_check_collision_point_triangle }, +{ "rf_get_collision_rec", "r", &rf_get_collision_rec }, +{ "rf_check_collision_spheres", "", &rf_check_collision_spheres }, +{ "rf_check_collision_boxes", "", &rf_check_collision_boxes }, +{ "rf_check_collision_box_sphere", "", &rf_check_collision_box_sphere }, +{ "rf_check_collision_ray_sphere", "", &rf_check_collision_ray_sphere }, +{ "rf_check_collision_ray_sphere_ex", "", &rf_check_collision_ray_sphere_ex }, +{ "rf_check_collision_ray_box", "", &rf_check_collision_ray_box }, +{ "rf_collision_ray_model", "r", &rf_collision_ray_model }, +{ "rf_collision_ray_triangle", "r", &rf_collision_ray_triangle }, +{ "rf_collision_ray_ground", "r", &rf_collision_ray_ground }, +{ "rf_get_size_base64", "", &rf_get_size_base64 }, +{ "rf_decode_base64", "r", &rf_decode_base64 }, +{ "rf_pixel_format_string", "", &rf_pixel_format_string }, +{ "rf_is_uncompressed_format", "", &rf_is_uncompressed_format }, +{ "rf_is_compressed_format", "", &rf_is_compressed_format }, +{ "rf_bits_per_pixel", "", &rf_bits_per_pixel }, +{ "rf_bytes_per_pixel", "", &rf_bytes_per_pixel }, +{ "rf_pixel_buffer_size", "", &rf_pixel_buffer_size }, +{ "rf_format_pixels_to_normalized", "", &rf_format_pixels_to_normalized }, +{ "rf_format_pixels_to_rgba32", "", &rf_format_pixels_to_rgba32 }, +{ "rf_format_pixels", "", &rf_format_pixels }, +{ "rf_format_one_pixel_to_normalized", "r", &rf_format_one_pixel_to_normalized }, +{ "rf_format_one_pixel_to_rgba32", "r", &rf_format_one_pixel_to_rgba32 }, +{ "rf_format_one_pixel", "", &rf_format_one_pixel }, +{ "rf_color_match_rgb", "", &rf_color_match_rgb }, +{ "rf_color_match", "", &rf_color_match }, +{ "rf_color_to_int", "", &rf_color_to_int }, +{ "rf_color_normalize", "r", &rf_color_normalize }, +{ "rf_color_from_normalized", "r", &rf_color_from_normalized }, +{ "rf_color_to_hsv", "r", &rf_color_to_hsv }, +{ "rf_color_from_hsv", "r", &rf_color_from_hsv }, +{ "rf_color_from_int", "r", &rf_color_from_int }, +{ "rf_fade", "r", &rf_fade }, +{ "rf_unproject", "r", &rf_unproject }, +{ "rf_get_mouse_ray", "r", &rf_get_mouse_ray }, +{ "rf_get_camera_matrix", "r", &rf_get_camera_matrix }, +{ "rf_get_camera_matrix2d", "r", &rf_get_camera_matrix2d }, +{ "rf_get_world_to_screen", "r", &rf_get_world_to_screen }, +{ "rf_get_world_to_screen2d", "r", &rf_get_world_to_screen2d }, +{ "rf_get_screen_to_world2d", "r", &rf_get_screen_to_world2d }, +{ "rf_set_camera3d_mode", "", &rf_set_camera3d_mode }, +{ "rf_update_camera3d", "", &rf_update_camera3d }, +{ "rf_image_size", "", &rf_image_size }, +{ "rf_image_size_in_format", "", &rf_image_size_in_format }, +{ "rf_image_get_pixels_as_rgba32_to_buffer", "", &rf_image_get_pixels_as_rgba32_to_buffer }, +{ "rf_image_get_pixels_as_normalized_to_buffer", "", &rf_image_get_pixels_as_normalized_to_buffer }, +{ "rf_image_pixels_to_rgba32", "r", &rf_image_pixels_to_rgba32 }, +{ "rf_image_compute_pixels_to_normalized", "r", &rf_image_compute_pixels_to_normalized }, +{ "rf_image_extract_palette_to_buffer", "", &rf_image_extract_palette_to_buffer }, +{ "rf_image_extract_palette", "r", &rf_image_extract_palette }, +{ "rf_image_alpha_border", "r", &rf_image_alpha_border }, +{ "rf_supports_image_file_type", "", &rf_supports_image_file_type }, +{ "rf_load_image_from_file_data_to_buffer", "r", &rf_load_image_from_file_data_to_buffer }, +{ "rf_load_image_from_file_data", "r", &rf_load_image_from_file_data }, +{ "rf_load_image_from_hdr_file_data_to_buffer", "r", &rf_load_image_from_hdr_file_data_to_buffer }, +{ "rf_load_image_from_hdr_file_data", "r", &rf_load_image_from_hdr_file_data }, +{ "rf_load_image_from_format_to_buffer", "r", &rf_load_image_from_format_to_buffer }, +{ "rf_load_image_from_file", "r", &rf_load_image_from_file }, +{ "rf_unload_image", "", &rf_unload_image }, +{ "rf_mipmaps_image_size", "", &rf_mipmaps_image_size }, +{ "rf_compute_mipmaps_stats", "r", &rf_compute_mipmaps_stats }, +{ "rf_image_gen_mipmaps_to_buffer", "r", &rf_image_gen_mipmaps_to_buffer }, +{ "rf_image_gen_mipmaps", "r", &rf_image_gen_mipmaps }, +{ "rf_unload_mipmaps_image", "", &rf_unload_mipmaps_image }, +{ "rf_get_dds_image_size", "r", &rf_get_dds_image_size }, +{ "rf_load_dds_image_to_buffer", "r", &rf_load_dds_image_to_buffer }, +{ "rf_load_dds_image", "r", &rf_load_dds_image }, +{ "rf_load_dds_image_from_file", "r", &rf_load_dds_image_from_file }, +{ "rf_get_pkm_image_size", "r", &rf_get_pkm_image_size }, +{ "rf_load_pkm_image_to_buffer", "r", &rf_load_pkm_image_to_buffer }, +{ "rf_load_pkm_image", "r", &rf_load_pkm_image }, +{ "rf_load_pkm_image_from_file", "r", &rf_load_pkm_image_from_file }, +{ "rf_get_ktx_image_size", "r", &rf_get_ktx_image_size }, +{ "rf_load_ktx_image_to_buffer", "r", &rf_load_ktx_image_to_buffer }, +{ "rf_load_ktx_image", "r", &rf_load_ktx_image }, +{ "rf_load_ktx_image_from_file", "r", &rf_load_ktx_image_from_file }, +{ "rf_load_animated_gif", "r", &rf_load_animated_gif }, +{ "rf_load_animated_gif_file", "r", &rf_load_animated_gif_file }, +{ "rf_gif_frame_size", "r", &rf_gif_frame_size }, +{ "rf_get_frame_from_gif", "r", &rf_get_frame_from_gif }, +{ "rf_unload_gif", "", &rf_unload_gif }, +{ "rf_get_seed_for_cellular_image", "r", &rf_get_seed_for_cellular_image }, +{ "rf_gen_image_color_to_buffer", "r", &rf_gen_image_color_to_buffer }, +{ "rf_gen_image_color", "r", &rf_gen_image_color }, +{ "rf_gen_image_gradient_v_to_buffer", "r", &rf_gen_image_gradient_v_to_buffer }, +{ "rf_gen_image_gradient_v", "r", &rf_gen_image_gradient_v }, +{ "rf_gen_image_gradient_h_to_buffer", "r", &rf_gen_image_gradient_h_to_buffer }, +{ "rf_gen_image_gradient_h", "r", &rf_gen_image_gradient_h }, +{ "rf_gen_image_gradient_radial_to_buffer", "r", &rf_gen_image_gradient_radial_to_buffer }, +{ "rf_gen_image_gradient_radial", "r", &rf_gen_image_gradient_radial }, +{ "rf_gen_image_checked_to_buffer", "r", &rf_gen_image_checked_to_buffer }, +{ "rf_gen_image_checked", "r", &rf_gen_image_checked }, +{ "rf_gen_image_white_noise_to_buffer", "r", &rf_gen_image_white_noise_to_buffer }, +{ "rf_gen_image_white_noise", "r", &rf_gen_image_white_noise }, +{ "rf_gen_image_perlin_noise_to_buffer", "r", &rf_gen_image_perlin_noise_to_buffer }, +{ "rf_gen_image_perlin_noise", "r", &rf_gen_image_perlin_noise }, +{ "rf_gen_image_cellular_to_buffer", "r", &rf_gen_image_cellular_to_buffer }, +{ "rf_gen_image_cellular", "r", &rf_gen_image_cellular }, +{ "rf_image_copy_to_buffer", "r", &rf_image_copy_to_buffer }, +{ "rf_image_copy", "r", &rf_image_copy }, +{ "rf_image_crop_to_buffer", "r", &rf_image_crop_to_buffer }, +{ "rf_image_crop", "r", &rf_image_crop }, +{ "rf_image_resize_to_buffer", "r", &rf_image_resize_to_buffer }, +{ "rf_image_resize", "r", &rf_image_resize }, +{ "rf_image_resize_nn_to_buffer", "r", &rf_image_resize_nn_to_buffer }, +{ "rf_image_resize_nn", "r", &rf_image_resize_nn }, +{ "rf_image_format_to_buffer", "r", &rf_image_format_to_buffer }, +{ "rf_image_format", "r", &rf_image_format }, +{ "rf_image_alpha_mask_to_buffer", "r", &rf_image_alpha_mask_to_buffer }, +{ "rf_image_alpha_clear", "r", &rf_image_alpha_clear }, +{ "rf_image_alpha_premultiply", "r", &rf_image_alpha_premultiply }, +{ "rf_image_alpha_crop_rec", "r", &rf_image_alpha_crop_rec }, +{ "rf_image_alpha_crop", "r", &rf_image_alpha_crop }, +{ "rf_image_dither", "r", &rf_image_dither }, +{ "rf_image_flip_vertical_in_place", "", &rf_image_flip_vertical_in_place }, +{ "rf_image_flip_vertical_to_buffer", "r", &rf_image_flip_vertical_to_buffer }, +{ "rf_image_flip_vertical", "r", &rf_image_flip_vertical }, +{ "rf_image_flip_horizontal_in_place", "", &rf_image_flip_horizontal_in_place }, +{ "rf_image_flip_horizontal_to_buffer", "r", &rf_image_flip_horizontal_to_buffer }, +{ "rf_image_flip_horizontal", "r", &rf_image_flip_horizontal }, +{ "rf_image_rotate_cw_to_buffer", "r", &rf_image_rotate_cw_to_buffer }, +{ "rf_image_rotate_cw", "r", &rf_image_rotate_cw }, +{ "rf_image_rotate_ccw_to_buffer", "r", &rf_image_rotate_ccw_to_buffer }, +{ "rf_image_rotate_ccw", "r", &rf_image_rotate_ccw }, +{ "rf_image_color_tint_to_buffer", "r", &rf_image_color_tint_to_buffer }, +{ "rf_image_color_tint", "r", &rf_image_color_tint }, +{ "rf_image_color_invert_to_buffer", "r", &rf_image_color_invert_to_buffer }, +{ "rf_image_color_invert", "r", &rf_image_color_invert }, +{ "rf_image_color_grayscale_to_buffer", "r", &rf_image_color_grayscale_to_buffer }, +{ "rf_image_color_grayscale", "r", &rf_image_color_grayscale }, +{ "rf_image_color_contrast_to_buffer", "r", &rf_image_color_contrast_to_buffer }, +{ "rf_image_color_contrast", "r", &rf_image_color_contrast }, +{ "rf_image_color_brightness_to_buffer", "r", &rf_image_color_brightness_to_buffer }, +{ "rf_image_color_brightness", "r", &rf_image_color_brightness }, +{ "rf_image_color_replace_to_buffer", "r", &rf_image_color_replace_to_buffer }, +{ "rf_image_color_replace", "r", &rf_image_color_replace }, +{ "rf_image_draw", "", &rf_image_draw }, +{ "rf_image_draw_rectangle", "", &rf_image_draw_rectangle }, +{ "rf_image_draw_rectangle_lines", "", &rf_image_draw_rectangle_lines }, +{ "rf_audio_device_count", "r", &rf_audio_device_count }, +{ "rf_default_audio_device", "r", &rf_default_audio_device }, +{ "rf_start_audio_device", "", &rf_start_audio_device }, +{ "rf_close_audio_device", "", &rf_close_audio_device }, +{ "rf_is_audio_device_ready", "", &rf_is_audio_device_ready }, +{ "rf_set_master_volume", "", &rf_set_master_volume }, +{ "rf_audio_format_from_filename_extension", "r", &rf_audio_format_from_filename_extension }, +{ "rf_audio_format_from_filename_extension_string", "r", &rf_audio_format_from_filename_extension_string }, +{ "rf_fully_decode_wav", "r", &rf_fully_decode_wav }, +{ "rf_fully_decode_ogg", "r", &rf_fully_decode_ogg }, +{ "rf_fully_decode_flac", "r", &rf_fully_decode_flac }, +{ "rf_fully_decode_mp3", "r", &rf_fully_decode_mp3 }, +{ "rf_fully_decode_xm", "r", &rf_fully_decode_xm }, +{ "rf_fully_decode_mod", "r", &rf_fully_decode_mod }, +{ "rf_fully_decode_audio_from_buffer", "r", &rf_fully_decode_audio_from_buffer }, +{ "rf_fully_decode_audio_from_file", "r", &rf_fully_decode_audio_from_file }, +{ "rf_unload_audio_data", "", &rf_unload_audio_data }, +{ "rf_load_encoded_audio_from_buffer", "r", &rf_load_encoded_audio_from_buffer }, +{ "rf_load_encoded_audio_from_file", "r", &rf_load_encoded_audio_from_file }, +{ "rf_audio_play", "", &rf_audio_play }, +{ "rf_audio_stop", "", &rf_audio_stop }, +{ "rf_audio_pause", "", &rf_audio_pause }, +{ "rf_audio_resume", "", &rf_audio_resume }, +{ "rf_audio_update", "", &rf_audio_update }, +{ "rf_audio_set_volume", "", &rf_audio_set_volume }, +{ "rf_audio_set_pitch", "", &rf_audio_set_pitch }, +{ "rf_audio_time_len", "", &rf_audio_time_len }, +{ "rf_audio_time_played", "", &rf_audio_time_played }, +{ "rf_audio_is_playing", "", &rf_audio_is_playing }, +{ "rf_audio_volume", "", &rf_audio_volume }, +{ "rf_audio_pitch", "", &rf_audio_pitch }, +{ "rf_gfx_load_shader", "r", &rf_gfx_load_shader }, +{ "rf_gfx_unload_shader", "", &rf_gfx_unload_shader }, +{ "rf_gfx_get_shader_location", "", &rf_gfx_get_shader_location }, +{ "rf_gfx_set_shader_value", "", &rf_gfx_set_shader_value }, +{ "rf_gfx_set_shader_value_v", "", &rf_gfx_set_shader_value_v }, +{ "rf_gfx_set_shader_value_matrix", "", &rf_gfx_set_shader_value_matrix }, +{ "rf_gfx_set_shader_value_texture", "", &rf_gfx_set_shader_value_texture }, +{ "rf_gfx_get_matrix_projection", "r", &rf_gfx_get_matrix_projection }, +{ "rf_gfx_get_matrix_modelview", "r", &rf_gfx_get_matrix_modelview }, +{ "rf_gfx_set_matrix_projection", "", &rf_gfx_set_matrix_projection }, +{ "rf_gfx_set_matrix_modelview", "", &rf_gfx_set_matrix_modelview }, +{ "rf_gfx_blend_mode", "", &rf_gfx_blend_mode }, +{ "rf_gfx_matrix_mode", "", &rf_gfx_matrix_mode }, +{ "rf_gfx_push_matrix", "", &rf_gfx_push_matrix }, +{ "rf_gfx_pop_matrix", "", &rf_gfx_pop_matrix }, +{ "rf_gfx_load_identity", "", &rf_gfx_load_identity }, +{ "rf_gfx_translatef", "", &rf_gfx_translatef }, +{ "rf_gfx_rotatef", "", &rf_gfx_rotatef }, +{ "rf_gfx_scalef", "", &rf_gfx_scalef }, +{ "rf_gfx_mult_matrixf", "", &rf_gfx_mult_matrixf }, +{ "rf_gfx_frustum", "", &rf_gfx_frustum }, +{ "rf_gfx_ortho", "", &rf_gfx_ortho }, +{ "rf_gfx_viewport", "", &rf_gfx_viewport }, +{ "rf_gfx_begin", "", &rf_gfx_begin }, +{ "rf_gfx_end", "", &rf_gfx_end }, +{ "rf_gfx_vertex2i", "", &rf_gfx_vertex2i }, +{ "rf_gfx_vertex2f", "", &rf_gfx_vertex2f }, +{ "rf_gfx_vertex3f", "", &rf_gfx_vertex3f }, +{ "rf_gfx_tex_coord2f", "", &rf_gfx_tex_coord2f }, +{ "rf_gfx_normal3f", "", &rf_gfx_normal3f }, +{ "rf_gfx_color4ub", "", &rf_gfx_color4ub }, +{ "rf_gfx_color3f", "", &rf_gfx_color3f }, +{ "rf_gfx_color4f", "", &rf_gfx_color4f }, +{ "rf_gfx_enable_texture", "", &rf_gfx_enable_texture }, +{ "rf_gfx_disable_texture", "", &rf_gfx_disable_texture }, +{ "rf_gfx_set_texture_wrap", "", &rf_gfx_set_texture_wrap }, +{ "rf_gfx_set_texture_filter", "", &rf_gfx_set_texture_filter }, +{ "rf_gfx_enable_render_texture", "", &rf_gfx_enable_render_texture }, +{ "rf_gfx_disable_render_texture", "", &rf_gfx_disable_render_texture }, +{ "rf_gfx_enable_depth_test", "", &rf_gfx_enable_depth_test }, +{ "rf_gfx_disable_depth_test", "", &rf_gfx_disable_depth_test }, +{ "rf_gfx_enable_backface_culling", "", &rf_gfx_enable_backface_culling }, +{ "rf_gfx_disable_backface_culling", "", &rf_gfx_disable_backface_culling }, +{ "rf_gfx_enable_scissor_test", "", &rf_gfx_enable_scissor_test }, +{ "rf_gfx_disable_scissor_test", "", &rf_gfx_disable_scissor_test }, +{ "rf_gfx_scissor", "", &rf_gfx_scissor }, +{ "rf_gfx_enable_wire_mode", "", &rf_gfx_enable_wire_mode }, +{ "rf_gfx_disable_wire_mode", "", &rf_gfx_disable_wire_mode }, +{ "rf_gfx_delete_textures", "", &rf_gfx_delete_textures }, +{ "rf_gfx_delete_render_textures", "", &rf_gfx_delete_render_textures }, +{ "rf_gfx_delete_shader", "", &rf_gfx_delete_shader }, +{ "rf_gfx_delete_vertex_arrays", "", &rf_gfx_delete_vertex_arrays }, +{ "rf_gfx_delete_buffers", "", &rf_gfx_delete_buffers }, +{ "rf_gfx_clear_color", "", &rf_gfx_clear_color }, +{ "rf_gfx_clear_screen_buffers", "", &rf_gfx_clear_screen_buffers }, +{ "rf_gfx_update_buffer", "", &rf_gfx_update_buffer }, +{ "rf_gfx_load_attrib_buffer", "", &rf_gfx_load_attrib_buffer }, +{ "rf_gfx_init_vertex_buffer", "", &rf_gfx_init_vertex_buffer }, +{ "rf_gfx_close", "", &rf_gfx_close }, +{ "rf_gfx_draw", "", &rf_gfx_draw }, +{ "rf_gfx_check_buffer_limit", "", &rf_gfx_check_buffer_limit }, +{ "rf_gfx_set_debug_marker", "", &rf_gfx_set_debug_marker }, +{ "rf_gfx_load_texture", "", &rf_gfx_load_texture }, +{ "rf_gfx_load_texture_depth", "", &rf_gfx_load_texture_depth }, +{ "rf_gfx_load_texture_cubemap", "", &rf_gfx_load_texture_cubemap }, +{ "rf_gfx_update_texture", "", &rf_gfx_update_texture }, +{ "rf_gfx_get_internal_texture_formats", "r", &rf_gfx_get_internal_texture_formats }, +{ "rf_gfx_unload_texture", "", &rf_gfx_unload_texture }, +{ "rf_gfx_generate_mipmaps", "", &rf_gfx_generate_mipmaps }, +{ "rf_gfx_read_texture_pixels_to_buffer", "r", &rf_gfx_read_texture_pixels_to_buffer }, +{ "rf_gfx_read_texture_pixels", "r", &rf_gfx_read_texture_pixels }, +{ "rf_gfx_read_screen_pixels", "", &rf_gfx_read_screen_pixels }, +{ "rf_gfx_load_render_texture", "r", &rf_gfx_load_render_texture }, +{ "rf_gfx_render_texture_attach", "", &rf_gfx_render_texture_attach }, +{ "rf_gfx_render_texture_complete", "", &rf_gfx_render_texture_complete }, +{ "rf_gfx_load_mesh", "", &rf_gfx_load_mesh }, +{ "rf_gfx_update_mesh", "", &rf_gfx_update_mesh }, +{ "rf_gfx_update_mesh_at", "", &rf_gfx_update_mesh_at }, +{ "rf_gfx_draw_mesh", "", &rf_gfx_draw_mesh }, +{ "rf_gfx_unload_mesh", "", &rf_gfx_unload_mesh }, +{ "rf_create_custom_render_batch_from_buffers", "r", &rf_create_custom_render_batch_from_buffers }, +{ "rf_create_custom_render_batch", "r", &rf_create_custom_render_batch }, +{ "rf_create_default_render_batch", "r", &rf_create_default_render_batch }, +{ "rf_set_active_render_batch", "", &rf_set_active_render_batch }, +{ "rf_unload_render_batch", "", &rf_unload_render_batch }, +{ "rf_load_texture_from_file", "r", &rf_load_texture_from_file }, +{ "rf_load_texture_from_file_data", "r", &rf_load_texture_from_file_data }, +{ "rf_load_texture_from_image", "r", &rf_load_texture_from_image }, +{ "rf_load_texture_from_image_with_mipmaps", "r", &rf_load_texture_from_image_with_mipmaps }, +{ "rf_load_texture_cubemap_from_image", "r", &rf_load_texture_cubemap_from_image }, +{ "rf_load_render_texture", "r", &rf_load_render_texture }, +{ "rf_update_texture", "", &rf_update_texture }, +{ "rf_gen_texture_mipmaps", "", &rf_gen_texture_mipmaps }, +{ "rf_set_texture_filter", "", &rf_set_texture_filter }, +{ "rf_set_texture_wrap", "", &rf_set_texture_wrap }, +{ "rf_unload_texture", "", &rf_unload_texture }, +{ "rf_unload_render_texture", "", &rf_unload_render_texture }, +{ "rf_gen_texture_cubemap", "r", &rf_gen_texture_cubemap }, +{ "rf_gen_texture_irradiance", "r", &rf_gen_texture_irradiance }, +{ "rf_gen_texture_prefilter", "r", &rf_gen_texture_prefilter }, +{ "rf_gen_texture_brdf", "r", &rf_gen_texture_brdf }, +{ "rf_parse_ttf_font", "r", &rf_parse_ttf_font }, +{ "rf_compute_ttf_font_glyph_metrics", "", &rf_compute_ttf_font_glyph_metrics }, +{ "rf_compute_ttf_font_atlas_width", "", &rf_compute_ttf_font_atlas_width }, +{ "rf_generate_ttf_font_atlas", "r", &rf_generate_ttf_font_atlas }, +{ "rf_ttf_font_from_atlas", "r", &rf_ttf_font_from_atlas }, +{ "rf_load_ttf_font_from_data", "r", &rf_load_ttf_font_from_data }, +{ "rf_load_ttf_font_from_file", "r", &rf_load_ttf_font_from_file }, +{ "rf_compute_glyph_metrics_from_image", "", &rf_compute_glyph_metrics_from_image }, +{ "rf_load_image_font_from_data", "r", &rf_load_image_font_from_data }, +{ "rf_load_image_font", "r", &rf_load_image_font }, +{ "rf_load_image_font_from_file", "r", &rf_load_image_font_from_file }, +{ "rf_unload_font", "", &rf_unload_font }, +{ "rf_get_glyph_index", "r", &rf_get_glyph_index }, +{ "rf_font_height", "", &rf_font_height }, +{ "rf_measure_text", "r", &rf_measure_text }, +{ "rf_measure_text_rec", "r", &rf_measure_text_rec }, +{ "rf_measure_string", "r", &rf_measure_string }, +{ "rf_measure_string_rec", "r", &rf_measure_string_rec }, +{ "rf_clear", "", &rf_clear }, +{ "rf_begin", "", &rf_begin }, +{ "rf_end", "", &rf_end }, +{ "rf_begin_2d", "", &rf_begin_2d }, +{ "rf_end_2d", "", &rf_end_2d }, +{ "rf_begin_3d", "", &rf_begin_3d }, +{ "rf_end_3d", "", &rf_end_3d }, +{ "rf_begin_render_to_texture", "", &rf_begin_render_to_texture }, +{ "rf_end_render_to_texture", "", &rf_end_render_to_texture }, +{ "rf_begin_scissor_mode", "", &rf_begin_scissor_mode }, +{ "rf_end_scissor_mode", "", &rf_end_scissor_mode }, +{ "rf_begin_shader", "", &rf_begin_shader }, +{ "rf_end_shader", "", &rf_end_shader }, +{ "rf_begin_blend_mode", "", &rf_begin_blend_mode }, +{ "rf_end_blend_mode", "", &rf_end_blend_mode }, +{ "rf_draw_pixel", "", &rf_draw_pixel }, +{ "rf_draw_pixel_v", "", &rf_draw_pixel_v }, +{ "rf_draw_line", "", &rf_draw_line }, +{ "rf_draw_line_v", "", &rf_draw_line_v }, +{ "rf_draw_line_ex", "", &rf_draw_line_ex }, +{ "rf_draw_line_bezier", "", &rf_draw_line_bezier }, +{ "rf_draw_line_strip", "", &rf_draw_line_strip }, +{ "rf_draw_circle", "", &rf_draw_circle }, +{ "rf_draw_circle_v", "", &rf_draw_circle_v }, +{ "rf_draw_circle_sector", "", &rf_draw_circle_sector }, +{ "rf_draw_circle_sector_lines", "", &rf_draw_circle_sector_lines }, +{ "rf_draw_circle_gradient", "", &rf_draw_circle_gradient }, +{ "rf_draw_circle_lines", "", &rf_draw_circle_lines }, +{ "rf_draw_ring", "", &rf_draw_ring }, +{ "rf_draw_ring_lines", "", &rf_draw_ring_lines }, +{ "rf_draw_rectangle", "", &rf_draw_rectangle }, +{ "rf_draw_rectangle_v", "", &rf_draw_rectangle_v }, +{ "rf_draw_rectangle_rec", "", &rf_draw_rectangle_rec }, +{ "rf_draw_rectangle_pro", "", &rf_draw_rectangle_pro }, +{ "rf_draw_rectangle_gradient_v", "", &rf_draw_rectangle_gradient_v }, +{ "rf_draw_rectangle_gradient_h", "", &rf_draw_rectangle_gradient_h }, +{ "rf_draw_rectangle_gradient", "", &rf_draw_rectangle_gradient }, +{ "rf_draw_rectangle_outline", "", &rf_draw_rectangle_outline }, +{ "rf_draw_rectangle_rounded", "", &rf_draw_rectangle_rounded }, +{ "rf_draw_rectangle_rounded_lines", "", &rf_draw_rectangle_rounded_lines }, +{ "rf_draw_triangle", "", &rf_draw_triangle }, +{ "rf_draw_triangle_lines", "", &rf_draw_triangle_lines }, +{ "rf_draw_triangle_fan", "", &rf_draw_triangle_fan }, +{ "rf_draw_triangle_strip", "", &rf_draw_triangle_strip }, +{ "rf_draw_poly", "", &rf_draw_poly }, +{ "rf_draw_texture", "", &rf_draw_texture }, +{ "rf_draw_texture_ex", "", &rf_draw_texture_ex }, +{ "rf_draw_texture_region", "", &rf_draw_texture_region }, +{ "rf_draw_texture_npatch", "", &rf_draw_texture_npatch }, +{ "rf_draw_string", "", &rf_draw_string }, +{ "rf_draw_string_ex", "", &rf_draw_string_ex }, +{ "rf_draw_string_wrap", "", &rf_draw_string_wrap }, +{ "rf_draw_string_rec", "", &rf_draw_string_rec }, +{ "rf_draw_text", "", &rf_draw_text }, +{ "rf_draw_text_ex", "", &rf_draw_text_ex }, +{ "rf_draw_text_wrap", "", &rf_draw_text_wrap }, +{ "rf_draw_text_rec", "", &rf_draw_text_rec }, +{ "rf_draw_line3d", "", &rf_draw_line3d }, +{ "rf_draw_circle3d", "", &rf_draw_circle3d }, +{ "rf_draw_cube", "", &rf_draw_cube }, +{ "rf_draw_cube_wires", "", &rf_draw_cube_wires }, +{ "rf_draw_cube_texture", "", &rf_draw_cube_texture }, +{ "rf_draw_sphere", "", &rf_draw_sphere }, +{ "rf_draw_sphere_ex", "", &rf_draw_sphere_ex }, +{ "rf_draw_sphere_wires", "", &rf_draw_sphere_wires }, +{ "rf_draw_cylinder", "", &rf_draw_cylinder }, +{ "rf_draw_cylinder_wires", "", &rf_draw_cylinder_wires }, +{ "rf_draw_plane", "", &rf_draw_plane }, +{ "rf_draw_ray", "", &rf_draw_ray }, +{ "rf_draw_grid", "", &rf_draw_grid }, +{ "rf_draw_gizmo", "", &rf_draw_gizmo }, +{ "rf_draw_model", "", &rf_draw_model }, +{ "rf_draw_model_ex", "", &rf_draw_model_ex }, +{ "rf_draw_model_wires", "", &rf_draw_model_wires }, +{ "rf_draw_bounding_box", "", &rf_draw_bounding_box }, +{ "rf_draw_billboard", "", &rf_draw_billboard }, +{ "rf_draw_billboard_rec", "", &rf_draw_billboard_rec }, +{ "rf_mesh_bounding_box", "r", &rf_mesh_bounding_box }, +{ "rf_mesh_compute_tangents", "", &rf_mesh_compute_tangents }, +{ "rf_mesh_compute_binormals", "", &rf_mesh_compute_binormals }, +{ "rf_unload_mesh", "", &rf_unload_mesh }, +{ "rf_load_model", "r", &rf_load_model }, +{ "rf_load_model_from_obj", "r", &rf_load_model_from_obj }, +{ "rf_load_model_from_iqm", "r", &rf_load_model_from_iqm }, +{ "rf_load_model_from_gltf", "r", &rf_load_model_from_gltf }, +{ "rf_load_model_from_mesh", "r", &rf_load_model_from_mesh }, +{ "rf_unload_model", "", &rf_unload_model }, +{ "rf_load_materials_from_mtl", "r", &rf_load_materials_from_mtl }, +{ "rf_set_material_texture", "", &rf_set_material_texture }, +{ "rf_set_model_mesh_material", "", &rf_set_model_mesh_material }, +{ "rf_unload_material", "", &rf_unload_material }, +{ "rf_load_model_animations_from_iqm_file", "r", &rf_load_model_animations_from_iqm_file }, +{ "rf_load_model_animations_from_iqm", "r", &rf_load_model_animations_from_iqm }, +{ "rf_update_model_animation", "", &rf_update_model_animation }, +{ "rf_is_model_animation_valid", "", &rf_is_model_animation_valid }, +{ "rf_unload_model_animation", "", &rf_unload_model_animation }, +{ "rf_gen_mesh_cube", "r", &rf_gen_mesh_cube }, +{ "rf_gen_mesh_poly", "r", &rf_gen_mesh_poly }, +{ "rf_gen_mesh_plane", "r", &rf_gen_mesh_plane }, +{ "rf_gen_mesh_sphere", "r", &rf_gen_mesh_sphere }, +{ "rf_gen_mesh_hemi_sphere", "r", &rf_gen_mesh_hemi_sphere }, +{ "rf_gen_mesh_cylinder", "r", &rf_gen_mesh_cylinder }, +{ "rf_gen_mesh_torus", "r", &rf_gen_mesh_torus }, +{ "rf_gen_mesh_knot", "r", &rf_gen_mesh_knot }, +{ "rf_gen_mesh_heightmap", "r", &rf_gen_mesh_heightmap }, +{ "rf_gen_mesh_cubicmap", "r", &rf_gen_mesh_cubicmap }, +{ "rf_init_context", "", &rf_init_context }, +{ "rf_init_gfx", "", &rf_init_gfx }, +{ "rf_init_audio", "", &rf_init_audio }, +{ "rf_load_default_material", "r", &rf_load_default_material }, +{ "rf_load_default_shader", "r", &rf_load_default_shader }, +{ "rf_get_current_render_batch", "r", &rf_get_current_render_batch }, +{ "rf_get_default_font", "r", &rf_get_default_font }, +{ "rf_get_default_shader", "r", &rf_get_default_shader }, +{ "rf_get_default_texture", "r", &rf_get_default_texture }, +{ "rf_get_context", "r", &rf_get_context }, +{ "rf_get_screen_data", "r", &rf_get_screen_data }, +{ "rf_set_global_context_pointer", "", &rf_set_global_context_pointer }, +{ "rf_set_viewport", "", &rf_set_viewport }, +{ "rf_set_shapes_texture", "", &rf_set_shapes_texture }, +{ "rf_load_default_material_ez", "r", &rf_load_default_material_ez }, +{ "rf_get_screen_data_ez", "r", &rf_get_screen_data_ez }, +{ "rf_decode_base64_ez", "r", &rf_decode_base64_ez }, +{ "rf_gfx_read_texture_pixels_ez", "r", &rf_gfx_read_texture_pixels_ez }, +{ "rf_image_pixels_to_rgba32_ez", "r", &rf_image_pixels_to_rgba32_ez }, +{ "rf_image_compute_pixels_to_normalized_ez", "r", &rf_image_compute_pixels_to_normalized_ez }, +{ "rf_image_extract_palette_ez", "r", &rf_image_extract_palette_ez }, +{ "rf_load_image_from_file_data_ez", "r", &rf_load_image_from_file_data_ez }, +{ "rf_load_image_from_hdr_file_data_ez", "r", &rf_load_image_from_hdr_file_data_ez }, +{ "rf_load_image_from_file_ez", "r", &rf_load_image_from_file_ez }, +{ "rf_unload_image_ez", "", &rf_unload_image_ez }, +{ "rf_image_copy_ez", "r", &rf_image_copy_ez }, +{ "rf_image_crop_ez", "r", &rf_image_crop_ez }, +{ "rf_image_resize_ez", "r", &rf_image_resize_ez }, +{ "rf_image_resize_nn_ez", "r", &rf_image_resize_nn_ez }, +{ "rf_image_format_ez", "r", &rf_image_format_ez }, +{ "rf_image_alpha_clear_ez", "r", &rf_image_alpha_clear_ez }, +{ "rf_image_alpha_premultiply_ez", "r", &rf_image_alpha_premultiply_ez }, +{ "rf_image_alpha_crop_ez", "r", &rf_image_alpha_crop_ez }, +{ "rf_image_dither_ez", "r", &rf_image_dither_ez }, +{ "rf_image_flip_vertical_ez", "r", &rf_image_flip_vertical_ez }, +{ "rf_image_flip_horizontal_ez", "r", &rf_image_flip_horizontal_ez }, +{ "rf_get_seed_for_cellular_image_ez", "r", &rf_get_seed_for_cellular_image_ez }, +{ "rf_gen_image_color_ez", "r", &rf_gen_image_color_ez }, +{ "rf_gen_image_gradient_v_ez", "r", &rf_gen_image_gradient_v_ez }, +{ "rf_gen_image_gradient_h_ez", "r", &rf_gen_image_gradient_h_ez }, +{ "rf_gen_image_gradient_radial_ez", "r", &rf_gen_image_gradient_radial_ez }, +{ "rf_gen_image_checked_ez", "r", &rf_gen_image_checked_ez }, +{ "rf_gen_image_white_noise_ez", "r", &rf_gen_image_white_noise_ez }, +{ "rf_gen_image_perlin_noise_ez", "r", &rf_gen_image_perlin_noise_ez }, +{ "rf_gen_image_cellular_ez", "r", &rf_gen_image_cellular_ez }, +{ "rf_image_gen_mipmaps_ez", "r", &rf_image_gen_mipmaps_ez }, +{ "rf_unload_mipmaps_image_ez", "", &rf_unload_mipmaps_image_ez }, +{ "rf_load_dds_image_ez", "r", &rf_load_dds_image_ez }, +{ "rf_load_dds_image_from_file_ez", "r", &rf_load_dds_image_from_file_ez }, +{ "rf_load_pkm_image_ez", "r", &rf_load_pkm_image_ez }, +{ "rf_load_pkm_image_from_file_ez", "r", &rf_load_pkm_image_from_file_ez }, +{ "rf_load_ktx_image_ez", "r", &rf_load_ktx_image_ez }, +{ "rf_load_ktx_image_from_file_ez", "r", &rf_load_ktx_image_from_file_ez }, +{ "rf_load_animated_gif_ez", "r", &rf_load_animated_gif_ez }, +{ "rf_load_animated_gif_file_ez", "r", &rf_load_animated_gif_file_ez }, +{ "rf_unload_gif_ez", "", &rf_unload_gif_ez }, +{ "rf_load_texture_from_file_ez", "r", &rf_load_texture_from_file_ez }, +{ "rf_load_texture_from_file_data_ez", "r", &rf_load_texture_from_file_data_ez }, +{ "rf_load_texture_cubemap_from_image_ez", "r", &rf_load_texture_cubemap_from_image_ez }, +{ "rf_load_ttf_font_from_data_ez", "r", &rf_load_ttf_font_from_data_ez }, +{ "rf_load_ttf_font_from_file_ez", "r", &rf_load_ttf_font_from_file_ez }, +{ "rf_load_image_font_ez", "r", &rf_load_image_font_ez }, +{ "rf_load_image_font_from_file_ez", "r", &rf_load_image_font_from_file_ez }, +{ "rf_unload_font_ez", "", &rf_unload_font_ez }, +{ "rf_decode_utf8_ez", "r", &rf_decode_utf8_ez }, +{ "rf_image_draw_ez", "", &rf_image_draw_ez }, +{ "rf_image_draw_rectangle_ez", "", &rf_image_draw_rectangle_ez }, +{ "rf_image_draw_rectangle_lines_ez", "", &rf_image_draw_rectangle_lines_ez }, +{ "rf_mesh_compute_tangents_ez", "", &rf_mesh_compute_tangents_ez }, +{ "rf_unload_mesh_ez", "", &rf_unload_mesh_ez }, +{ "rf_load_model_ez", "r", &rf_load_model_ez }, +{ "rf_load_model_from_obj_ez", "r", &rf_load_model_from_obj_ez }, +{ "rf_load_model_from_iqm_ez", "r", &rf_load_model_from_iqm_ez }, +{ "rf_load_model_from_gltf_ez", "r", &rf_load_model_from_gltf_ez }, +{ "rf_load_model_from_mesh_ez", "r", &rf_load_model_from_mesh_ez }, +{ "rf_unload_model_ez", "", &rf_unload_model_ez }, +{ "rf_load_materials_from_mtl_ez", "r", &rf_load_materials_from_mtl_ez }, +{ "rf_unload_material_ez", "", &rf_unload_material_ez }, +{ "rf_load_model_animations_from_iqm_file_ez", "r", &rf_load_model_animations_from_iqm_file_ez }, +{ "rf_load_model_animations_from_iqm_ez", "r", &rf_load_model_animations_from_iqm_ez }, +{ "rf_unload_model_animation_ez", "", &rf_unload_model_animation_ez }, +{ "rf_gen_mesh_cube_ez", "r", &rf_gen_mesh_cube_ez }, +{ "rf_gen_mesh_poly_ez", "r", &rf_gen_mesh_poly_ez }, +{ "rf_gen_mesh_plane_ez", "r", &rf_gen_mesh_plane_ez }, +{ "rf_gen_mesh_sphere_ez", "r", &rf_gen_mesh_sphere_ez }, +{ "rf_gen_mesh_hemi_sphere_ez", "r", &rf_gen_mesh_hemi_sphere_ez }, +{ "rf_gen_mesh_cylinder_ez", "r", &rf_gen_mesh_cylinder_ez }, +{ "rf_gen_mesh_torus_ez", "r", &rf_gen_mesh_torus_ez }, +{ "rf_gen_mesh_knot_ez", "r", &rf_gen_mesh_knot_ez }, +{ "rf_gen_mesh_heightmap_ez", "r", &rf_gen_mesh_heightmap_ez }, +{ "rf_gen_mesh_cubicmap_ez", "r", &rf_gen_mesh_cubicmap_ez }, +{ NULL, NULL, NULL }, +}; diff --git a/raylib b/raylib deleted file mode 160000 index be03613..0000000 --- a/raylib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit be03613d1b6f9b0051d78c790f97df069150c65f diff --git a/src/compat.lua b/src/compat.lua index 2958a30..2c2a18e 100644 --- a/src/compat.lua +++ b/src/compat.lua @@ -17,23 +17,23 @@ -- math metamethods local new = ffi.new -ffi.metatype("Vector2", { +ffi.metatype("rf_vec2", { __add = function (a, b) - if ffi.istype("Vector2", b) then - return new("Vector2", a.x + b.x, a.y + b.y) + if ffi.istype("rf_vec2", b) then + return new("rf_vec2", a.x + b.x, a.y + b.y) else error "Invalid operation." end end, __sub = function (a, b) - if ffi.istype("Vector2", b) then - return new("Vector2", a.x - b.x, a.y - b.y) + if ffi.istype("rf_vec2", b) then + return new("rf_vec2", a.x - b.x, a.y - b.y) else error "Invalid operation." end end, __unm = function (a) - return new("Vector2", -a.x, -a.y) + return new("rf_vec2", -a.x, -a.y) end, __len = function (a) return math.sqrt(a.x * a.x + a.y * a.y) @@ -44,43 +44,43 @@ ffi.metatype("Vector2", { a, b = b, a end - if ffi.istype("Vector2", b) then -- dot product + if ffi.istype("rf_vec2", b) then -- dot product return a.x * b.x + a.y * b.y elseif type(b) == "number" then - return new("Vector2", a.x * b, a.y * b) + return new("rf_vec2", a.x * b, a.y * b) else error "Invalid operation." end end, __div = function (a, b) if type(b) == "number" then - return new("Vector2", a.x / b, a.y / b) + return new("rf_vec2", a.x / b, a.y / b) else error "Invalid operation" end end, __tostring = function (a) - return string.format("Vector2: (%g %g)", a.x, a.y) + return string.format("rf_vec2: (%g %g)", a.x, a.y) end }) -ffi.metatype("Vector3", { +ffi.metatype("rf_vec3", { __add = function (a, b) - if ffi.istype("Vector3", b) then - return new("Vector3", a.x + b.x, a.y + b.y, a.z + b.z) + if ffi.istype("rf_vec3", b) then + return new("rf_vec3", a.x + b.x, a.y + b.y, a.z + b.z) else error "Invalid operation." end end, __sub = function (a, b) - if ffi.istype("Vector3", b) then - return new("Vector3", a.x - b.x, a.y - b.y, a.z - b.z) + if ffi.istype("rf_vec3", b) then + return new("rf_vec3", a.x - b.x, a.y - b.y, a.z - b.z) else error "Invalid operation." end end, __unm = function (a) - return new("Vector3", -a.x, -a.y, -a.z) + return new("rf_vec3", -a.x, -a.y, -a.z) end, __len = function (a) return math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z) @@ -91,23 +91,23 @@ ffi.metatype("Vector3", { a, b = b, a end - if ffi.istype("Vector3", b) then -- dot product + if ffi.istype("rf_vec3", b) then -- dot product return a.x * b.x + a.y * b.y + a.z * b.z elseif type(b) == "number" then - return new("Vector3", a.x * b, a.y * b, a.z * b) + return new("rf_vec3", a.x * b, a.y * b, a.z * b) else error "Invalid operation." end end, __div = function (a, b) if type(b) == "number" then - return new("Vector3", a.x / b, a.y / b, a.z / b) + return new("rf_vec3", a.x / b, a.y / b, a.z / b) else error "Invalid operation" end end, __tostring = function (a) - return string.format("Vector3: (%g %g %g)", a.x, a.y, a.z) + return string.format("rf_vec3: (%g %g %g)", a.x, a.y, a.z) end }) diff --git a/src/lib/rayfork.c b/src/lib/rayfork.c new file mode 100644 index 0000000..2a740fc --- /dev/null +++ b/src/lib/rayfork.c @@ -0,0 +1,41616 @@ +#define RAYFORK__IMPLEMENTATION_FLAG // This is used such that RF_API can be defined correctly if RAYFORK_DLL is defined. + +#include "rayfork.h" + + +/*** Start of inlined file: rayfork-std.c ***/ +#pragma region globals + +// Useful internal macros +#define rf_ctx (*rf__ctx) +#define rf_gfx (rf_ctx.gfx_ctx) +#define rf_gl (rf_gfx.gl) +#define rf_batch (*(rf_ctx.current_batch)) + +// Global pointer to context struct +RF_INTERNAL rf_context* rf__ctx; +RF_INTERNAL RF_THREAD_LOCAL rf_recorded_error rf__last_error; + +#pragma endregion + +#pragma region assert + +#if !defined(RF_ASSERT) && defined(RAYFORK_ENABLE_ASSERTIONS) + #include "assert.h" + #define RF_ASSERT(condition) assert(condition) +#else + #define RF_ASSERT(condition) +#endif + +#pragma endregion + +#pragma region allocator + +RF_INTERNAL RF_THREAD_LOCAL rf_allocator rf__global_allocator_for_dependencies; +#define RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(allocator) rf__global_allocator_for_dependencies = (allocator) + +RF_API void* rf_calloc_wrapper(rf_allocator allocator, rf_int amount, rf_int size) +{ + void* ptr = RF_ALLOC(allocator, amount * size); + memset(ptr, 0, amount * size); + return ptr; +} + +RF_API void* rf_libc_allocator_wrapper(struct rf_allocator* this_allocator, rf_source_location source_location, rf_allocator_mode mode, rf_allocator_args args) +{ + RF_ASSERT(this_allocator); + + void* result = 0; + + switch (mode) + { + case RF_AM_ALLOC: + result = malloc(args.size_to_allocate_or_reallocate); + break; + + case RF_AM_FREE: + free(args.pointer_to_free_or_realloc); + break; + + case RF_AM_REALLOC: + result = realloc(args.pointer_to_free_or_realloc, args.size_to_allocate_or_reallocate); + break; + + default: break; + } + + return result; +} + +#pragma endregion + +#pragma region io + +RF_API rf_int rf_libc_get_file_size(void* user_data, const char* filename) +{ + ((void)user_data); + + FILE* file = fopen(filename, "rb"); + + fseek(file, 0L, SEEK_END); + int size = ftell(file); + fseek(file, 0L, SEEK_SET); + + fclose(file); + + return size; +} + +RF_API bool rf_libc_load_file_into_buffer(void* user_data, const char* filename, void* dst, rf_int dst_size) +{ + ((void)user_data); + bool result = false; + + FILE* file = fopen(filename, "rb"); + if (file != NULL) + { + fseek(file, 0L, SEEK_END); + int file_size = ftell(file); + fseek(file, 0L, SEEK_SET); + + if (dst_size >= file_size) + { + int read_size = fread(dst, 1, file_size, file); + int no_error = ferror(file) == 0; + if (no_error && read_size == file_size) + { + result = true; + } + } + // else log_error buffer is not big enough + } + // else log error could not open file + + fclose(file); + + return result; +} + +#pragma endregion + +#pragma region internal utils + +#ifndef RF_MAX_FILEPATH_LEN + #define RF_MAX_FILEPATH_LEN (1024) +#endif + +RF_INTERNAL bool rf_match_str_n(const char* a, int a_len, const char* b, int b_len) +{ + return a_len == b_len && strncmp(a, b, a_len) == 0; +} + +RF_INTERNAL bool rf_match_str_cstr(const char* a, int a_len, const char* b) +{ + return rf_match_str_n(a, a_len, b, strlen(b)); +} + +RF_INTERNAL void* rf_realloc_wrapper(rf_allocator allocator, void* source, int old_size, int new_size) +{ + void* new_alloc = RF_ALLOC(allocator, new_size); + if (new_alloc && source && old_size) { memcpy(new_alloc, source, old_size); } + if (source) { RF_FREE(allocator, source); } + return new_alloc; +} + +RF_INTERNAL bool rf_is_file_extension(const char* filename, const char* ext) +{ + int filename_len = strlen(filename); + int ext_len = strlen(ext); + + if (filename_len < ext_len) + { + return false; + } + + return rf_match_str_n(filename + filename_len - ext_len, ext_len, ext, ext_len); +} + +RF_INTERNAL const char* rf_str_find_last(const char* s, const char* charset) +{ + const char* latest_match = NULL; + for (; s = strpbrk(s, charset), s != NULL; latest_match = s++) { } + return latest_match; +} + +RF_INTERNAL const char* rf_get_directory_path_from_file_path(const char* file_path) +{ + static RF_THREAD_LOCAL char rf_global_dir_path[RF_MAX_FILEPATH_LEN]; + + const char* last_slash = NULL; + memset(rf_global_dir_path, 0, RF_MAX_FILEPATH_LEN); + + last_slash = rf_str_find_last(file_path, "\\/"); + if (!last_slash) { return NULL; } + + // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' + strncpy(rf_global_dir_path, file_path, strlen(file_path) - (strlen(last_slash) - 1)); + rf_global_dir_path[strlen(file_path) - strlen(last_slash)] = '\0'; // Add '\0' manually + + return rf_global_dir_path; +} + +RF_INTERNAL inline int rf_min_i(int a, int b) { return ((a) < (b) ? (a) : (b)); } +RF_INTERNAL inline int rf_max_i(int a, int b) { return ((a) > (b) ? (a) : (b)); } + +RF_INTERNAL inline int rf_min_f(float a, float b) { return ((a) < (b) ? (a) : (b)); } +RF_INTERNAL inline int rf_max_f(float a, float b) { return ((a) > (b) ? (a) : (b)); } + +#pragma endregion + +#pragma region logger + +#define RF_RECORDED_ERROR(error_type) (RF_LIT(rf_recorded_error) { RF_SOURCE_LOCATION, error_type }) + +/* + Note(LucaSas): MSVC, clang and gcc all deal with __VA_ARGS__ differently. + Normally you would expect that __VA_ARGS__ consume a trailing comma but it doesn't, this is why we must ##__VA_ARGS__. + ##__VA_ARGS__ is a preprocessor black magic which achieves this goal, it's not standard but every compiler supports it, if + this causes issues on some compiler just disable logs with RF_DISABLE_LOGGER. + Also bear in mind that ##__VA_ARGS__ still works differently between compilers but this code seems to work on all major compilers. +*/ +#define RF_LOG(log_type, msg, ...) rf_log_impl(RF_SOURCE_LOCATION, (log_type), (msg), ##__VA_ARGS__) +#define RF_LOG_ERROR(error_type, msg, ...) (RF_LOG(RF_LOG_TYPE_ERROR, (msg), (error_type), ##__VA_ARGS__), rf__last_error = RF_RECORDED_ERROR(error_type)) + +RF_INTERNAL void rf_log_impl(rf_source_location source_location, rf_log_type log_type, const char* msg, ...) +{ + if (!(log_type & rf_ctx.logger_filter)) return; + + va_list args; + + va_start(args, msg); + + rf_error_type error_type = RF_NO_ERROR; + + // If the log type is an error then the error type must be the first arg + if (log_type == RF_LOG_TYPE_ERROR) + { + error_type = va_arg(args, rf_error_type); + } + + if (rf_ctx.logger.log_proc) + { + rf_ctx.logger.log_proc(&rf_ctx.logger, source_location, log_type, msg, error_type, args); + } + + va_end(args); +} + +RF_API const char* rf_log_type_string(rf_log_type log_type) +{ + switch (log_type) + { + case RF_LOG_TYPE_NONE: return "NONE"; + case RF_LOG_TYPE_DEBUG: return "DEBUG"; + case RF_LOG_TYPE_INFO: return "INFO"; + case RF_LOG_TYPE_WARNING: return "WARNING"; + case RF_LOG_TYPE_ERROR: return "ERROR"; + default: return "RAYFORK_LOG_TYPE_UNKNOWN"; + } +} + +RF_API void rf_libc_printf_logger(struct rf_logger* logger, rf_source_location source_location, rf_log_type log_type, const char* msg, rf_error_type error_type, va_list args) +{ + ((void)logger); // unused + printf("[RAYFORK %s]: ", rf_log_type_string(log_type)); + vprintf(msg, args); + printf("\n"); +} + +#pragma endregion +/*** End of inlined file: rayfork-std.c ***/ + + +/*** Start of inlined file: rayfork-context.c ***/ +RF_INTERNAL void rf_gfx_backend_internal_init(rf_gfx_backend_data* gfx_data); + +RF_API void rf_init_context(rf_context* ctx) +{ + *ctx = (rf_context) {0}; + rf_set_global_context_pointer(ctx); +} + +RF_API void rf_init_gfx(int screen_width, int screen_height, rf_gfx_backend_data* gfx_data) +{ + rf_ctx.current_matrix_mode = -1; + rf_ctx.screen_scaling = rf_mat_identity(); + + rf_ctx.framebuffer_width = screen_width; + rf_ctx.framebuffer_height = screen_height; + rf_ctx.render_width = screen_width; + rf_ctx.render_height = screen_height; + rf_ctx.current_width = screen_width; + rf_ctx.current_height = screen_height; + + rf_gfx_backend_internal_init(gfx_data); + + // Initialize default shaders and default textures + { + // Init default white texture + unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) + rf_ctx.default_texture_id = rf_gfx_load_texture(pixels, 1, 1, RF_UNCOMPRESSED_R8G8B8A8, 1); + + if (rf_ctx.default_texture_id != 0) + { + RF_LOG(RF_LOG_TYPE_INFO, "Base white texture loaded successfully. [ Texture ID: %d ]", rf_ctx.default_texture_id); + } + else + { + RF_LOG(RF_LOG_TYPE_WARNING, "Base white texture could not be loaded"); + } + + // Init default shader (customized for GL 3.3 and ES2) + rf_ctx.default_shader = rf_load_default_shader(); + rf_ctx.current_shader = rf_ctx.default_shader; + + // Init transformations matrix accumulator + rf_ctx.transform = rf_mat_identity(); + + // Init internal matrix stack (emulating OpenGL 1) + for (rf_int i = 0; i < RF_MAX_MATRIX_STACK_SIZE; i++) + { + rf_ctx.stack[i] = rf_mat_identity(); + } + + // Init internal matrices + rf_ctx.projection = rf_mat_identity(); + rf_ctx.modelview = rf_mat_identity(); + rf_ctx.current_matrix = &rf_ctx.modelview; + } + + // Setup default viewport + rf_set_viewport(screen_width, screen_height); + + // Load default font + #if !defined(RF_NO_DEFAULT_FONT) + { + // NOTE: Using UTF8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement + // http://www.utf8-chartable.de/unicode-utf8-table.pl + + rf_ctx.default_font.glyphs_count = 224; // Number of chars included in our default font + + // Default font is directly defined here (data generated from a sprite font image) + // This way, we reconstruct rf_font without creating large global variables + // This data is automatically allocated to Stack and automatically deallocated at the end of this function + static unsigned int default_font_data[512] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f, + 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de, + 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f, + 0x00200000, 0x8001b000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xc0000fbe, 0xfbf7e00f, 0x5fbf7e7d, 0x0050bee8, 0x440808a2, 0x0a142fe8, 0x50810285, 0x0050a048, + 0x49e428a2, 0x0a142828, 0x40810284, 0x0048a048, 0x10020fbe, 0x09f7ebaf, 0xd89f3e84, 0x0047a04f, 0x09e48822, 0x0a142aa1, 0x50810284, 0x0048a048, + 0x04082822, 0x0a142fa0, 0x50810285, 0x0050a248, 0x00008fbe, 0xfbf42021, 0x5f817e7d, 0x07d09ce8, 0x00008000, 0x00000fe0, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000c0180, + 0xdfbf4282, 0x0bfbf7ef, 0x42850505, 0x004804bf, 0x50a142c6, 0x08401428, 0x42852505, 0x00a808a0, 0x50a146aa, 0x08401428, 0x42852505, 0x00081090, + 0x5fa14a92, 0x0843f7e8, 0x7e792505, 0x00082088, 0x40a15282, 0x08420128, 0x40852489, 0x00084084, 0x40a16282, 0x0842022a, 0x40852451, 0x00088082, + 0xc0bf4282, 0xf843f42f, 0x7e85fc21, 0x3e0900bf, 0x00000000, 0x00000004, 0x00000000, 0x000c0180, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000402, 0x41482000, 0x00000000, 0x00000800, + 0x04000404, 0x4100203c, 0x00000000, 0x00000800, 0xf7df7df0, 0x514bef85, 0xbefbefbe, 0x04513bef, 0x14414500, 0x494a2885, 0xa28a28aa, 0x04510820, + 0xf44145f0, 0x474a289d, 0xa28a28aa, 0x04510be0, 0x14414510, 0x494a2884, 0xa28a28aa, 0x02910a00, 0xf7df7df0, 0xd14a2f85, 0xbefbe8aa, 0x011f7be0, + 0x00000000, 0x00400804, 0x20080000, 0x00000000, 0x00000000, 0x00600f84, 0x20080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000f01, 0x00000000, 0x06000000, 0x24000000, 0x00000f01, 0x00000000, 0x09108000, + 0x24fa28a2, 0x00000f01, 0x00000000, 0x013e0000, 0x2242252a, 0x00000f52, 0x00000000, 0x038a8000, 0x2422222a, 0x00000f29, 0x00000000, 0x010a8000, + 0x2412252a, 0x00000f01, 0x00000000, 0x010a8000, 0x24fbe8be, 0x00000f01, 0x00000000, 0x0ebe8000, 0xac020000, 0x00000f01, 0x00000000, 0x00048000, + 0x0003e000, 0x00000f00, 0x00000000, 0x00008000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000038, 0x8443b80e, 0x00203a03, + 0x02bea080, 0xf0000020, 0xc452208a, 0x04202b02, 0xf8029122, 0x07f0003b, 0xe44b388e, 0x02203a02, 0x081e8a1c, 0x0411e92a, 0xf4420be0, 0x01248202, + 0xe8140414, 0x05d104ba, 0xe7c3b880, 0x00893a0a, 0x283c0e1c, 0x04500902, 0xc4400080, 0x00448002, 0xe8208422, 0x04500002, 0x80400000, 0x05200002, + 0x083e8e00, 0x04100002, 0x804003e0, 0x07000042, 0xf8008400, 0x07f00003, 0x80400000, 0x04000022, 0x00000000, 0x00000000, 0x80400000, 0x04000002, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00800702, 0x1848a0c2, 0x84010000, 0x02920921, 0x01042642, 0x00005121, 0x42023f7f, 0x00291002, + 0xefc01422, 0x7efdfbf7, 0xefdfa109, 0x03bbbbf7, 0x28440f12, 0x42850a14, 0x20408109, 0x01111010, 0x28440408, 0x42850a14, 0x2040817f, 0x01111010, + 0xefc78204, 0x7efdfbf7, 0xe7cf8109, 0x011111f3, 0x2850a932, 0x42850a14, 0x2040a109, 0x01111010, 0x2850b840, 0x42850a14, 0xefdfbf79, 0x03bbbbf7, + 0x001fa020, 0x00000000, 0x00001000, 0x00000000, 0x00002070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08022800, 0x00012283, 0x02430802, 0x01010001, 0x8404147c, 0x20000144, 0x80048404, 0x00823f08, 0xdfbf4284, 0x7e03f7ef, 0x142850a1, 0x0000210a, + 0x50a14684, 0x528a1428, 0x142850a1, 0x03efa17a, 0x50a14a9e, 0x52521428, 0x142850a1, 0x02081f4a, 0x50a15284, 0x4a221428, 0xf42850a1, 0x03efa14b, + 0x50a16284, 0x4a521428, 0x042850a1, 0x0228a17a, 0xdfbf427c, 0x7e8bf7ef, 0xf7efdfbf, 0x03efbd0b, 0x00000000, 0x04000000, 0x00000000, 0x00000008, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200508, 0x00840400, 0x11458122, 0x00014210, + 0x00514294, 0x51420800, 0x20a22a94, 0x0050a508, 0x00200000, 0x00000000, 0x00050000, 0x08000000, 0xfefbefbe, 0xfbefbefb, 0xfbeb9114, 0x00fbefbe, + 0x20820820, 0x8a28a20a, 0x8a289114, 0x3e8a28a2, 0xfefbefbe, 0xfbefbe0b, 0x8a289114, 0x008a28a2, 0x228a28a2, 0x08208208, 0x8a289114, 0x088a28a2, + 0xfefbefbe, 0xfbefbefb, 0xfa2f9114, 0x00fbefbe, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000020, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00210100, 0x00000004, 0x00000000, 0x00000000, 0x14508200, 0x00001402, 0x00000000, 0x00000000, + 0x00000010, 0x00000020, 0x00000000, 0x00000000, 0xa28a28be, 0x00002228, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, + 0xa28a28aa, 0x000022a8, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, 0xbefbefbe, 0x00003e2f, 0x00000000, 0x00000000, + 0x00000004, 0x00002028, 0x00000000, 0x00000000, 0x80000000, 0x00003e0f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 + }; + + int chars_height = 10; + int chars_divisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically + + int chars_width[224] = { + 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6, + 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5, + 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 5, 7, 1, 5, 3, 7, 3, 5, 4, 1, 7, 4, 3, 5, 3, 3, 2, 5, 6, 1, 2, 2, 3, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 3, 3, 3, 3, 7, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 4, 6, + 5, 5, 5, 5, 5, 5, 9, 5, 5, 5, 5, 5, 2, 2, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5 + }; + + // Re-construct image from rf_ctx->default_font_data and generate a texture + //---------------------------------------------------------------------- + { + rf_color font_pixels[128 * 128] = {0}; + + int counter = 0; // rf_font data elements counter + + // Fill with default_font_data (convert from bit to pixel!) + for (rf_int i = 0; i < 128 * 128; i += 32) + { + for (rf_int j = 31; j >= 0; j--) + { + const int bit_check = (default_font_data[counter]) & (1u << j); + if (bit_check) font_pixels[i + j] = RF_WHITE; + } + + counter++; + + if (counter > 512) counter = 0; // Security check... + } + + bool format_success = rf_format_pixels(font_pixels, 128 * 128 * sizeof(rf_color), RF_UNCOMPRESSED_R8G8B8A8, + rf_ctx.default_font_buffers.pixels, 128 * 128 * 2, RF_UNCOMPRESSED_GRAY_ALPHA); + + RF_ASSERT(format_success); + } + + rf_image font_image = { + .data = rf_ctx.default_font_buffers.pixels, + .format = RF_UNCOMPRESSED_GRAY_ALPHA, + .width = 128, + .height = 128, + .valid = true, + }; + + rf_ctx.default_font.texture = rf_load_texture_from_image(font_image); + + // Allocate space for our characters info data + rf_ctx.default_font.glyphs = rf_ctx.default_font_buffers.chars; + + int current_line = 0; + int current_pos_x = chars_divisor; + int test_pos_x = chars_divisor; + int char_pixels_iter = 0; + + for (rf_int i = 0; i < rf_ctx.default_font.glyphs_count; i++) + { + rf_ctx.default_font.glyphs[i].codepoint = 32 + i; // First char is 32 + + rf_ctx.default_font.glyphs[i].x = (float) current_pos_x; + rf_ctx.default_font.glyphs[i].y = (float) (chars_divisor + current_line * (chars_height + chars_divisor)); + rf_ctx.default_font.glyphs[i].width = (float) chars_width[i]; + rf_ctx.default_font.glyphs[i].height = (float) chars_height; + + test_pos_x += (int) (rf_ctx.default_font.glyphs[i].width + (float)chars_divisor); + + if (test_pos_x >= rf_ctx.default_font.texture.width) + { + current_line++; + current_pos_x = 2 * chars_divisor + chars_width[i]; + test_pos_x = current_pos_x; + + rf_ctx.default_font.glyphs[i].x = (float) (chars_divisor); + rf_ctx.default_font.glyphs[i].y = (float) (chars_divisor + current_line * (chars_height + chars_divisor)); + } + else current_pos_x = test_pos_x; + + // NOTE: On default font character offsets and xAdvance are not required + rf_ctx.default_font.glyphs[i].offset_x = 0; + rf_ctx.default_font.glyphs[i].offset_y = 0; + rf_ctx.default_font.glyphs[i].advance_x = 0; + } + + rf_ctx.default_font.base_size = (int)rf_ctx.default_font.glyphs[0].height; + rf_ctx.default_font.valid = true; + + RF_LOG(RF_LOG_TYPE_INFO, "[TEX ID %i] Default font loaded successfully", rf_ctx.default_font.texture.id); + } + #endif +} + +#pragma region getters + +RF_API rf_recorded_error rf_get_last_recorded_error() +{ + return rf__last_error; +} + +// Get the default font, useful to be used with extended parameters +RF_API rf_font rf_get_default_font() +{ + return rf_ctx.default_font; +} + +// Get default shader +RF_API rf_shader rf_get_default_shader() +{ + return rf_ctx.default_shader; +} + +// Get default internal texture (white texture) +RF_API rf_texture2d rf_get_default_texture() +{ + rf_texture2d texture = {0}; + texture.id = rf_ctx.default_texture_id; + texture.width = 1; + texture.height = 1; + texture.mipmaps = 1; + texture.format = RF_UNCOMPRESSED_R8G8B8A8; + + return texture; +} + +//Get the context pointer +RF_API rf_context* rf_get_context() +{ + return rf__ctx; +} + +// Get pixel data from GPU frontbuffer and return an rf_image (screenshot) +RF_API rf_image rf_get_screen_data(rf_color* dst, rf_int dst_size) +{ + rf_image image = {0}; + + if (dst && dst_size == rf_ctx.render_width * rf_ctx.render_height) + { + rf_gfx_read_screen_pixels(dst, rf_ctx.render_width, rf_ctx.render_height); + + image.data = dst; + image.width = rf_ctx.render_width; + image.height = rf_ctx.render_height; + image.format = RF_UNCOMPRESSED_R8G8B8A8; + image.valid = true; + } + + return image; +} + +RF_API rf_log_type rf_get_current_log_filter() +{ + return rf_ctx.logger_filter; +} + +#pragma endregion + +#pragma region setters + +// Define default texture used to draw shapes +RF_API void rf_set_shapes_texture(rf_texture2d texture, rf_rec source) +{ + rf_ctx.tex_shapes = texture; + rf_ctx.rec_tex_shapes = source; +} + +// Set the global context pointer +RF_API void rf_set_global_context_pointer(rf_context* ctx) +{ + rf__ctx = ctx; +} + +// Set viewport for a provided width and height +RF_API void rf_set_viewport(int width, int height) +{ + rf_ctx.render_width = width; + rf_ctx.render_height = height; + + // Set viewport width and height + rf_gfx_viewport(0, 0, rf_ctx.render_width, rf_ctx.render_height); + + rf_gfx_matrix_mode(RF_PROJECTION); // Switch to PROJECTION matrix + rf_gfx_load_identity(); // Reset current matrix (PROJECTION) + + // Set orthographic GL_PROJECTION to current framebuffer size, top-left corner is (0, 0) + rf_gfx_ortho(0, rf_ctx.render_width, rf_ctx.render_height, 0, 0.0f, 1.0f); + + rf_gfx_matrix_mode(RF_MODELVIEW); // Switch back to MODELVIEW matrix + rf_gfx_load_identity(); // Reset current matrix (MODELVIEW) +} + +RF_API void rf_set_logger(rf_logger logger) +{ + rf_ctx.logger = logger; +} + +RF_API void rf_set_logger_filter(rf_log_type filter) +{ + rf_ctx.logger_filter = filter; +} + +RF_API inline rf_int rf_libc_rand_wrapper(rf_int min, rf_int max) +{ + return rand() % (max + 1 - min) + min; +} + +#pragma endregion +/*** End of inlined file: rayfork-context.c ***/ + + +/*** Start of inlined file: rayfork-color.c ***/ +#pragma region pixel format + +RF_API const char* rf_pixel_format_string(rf_pixel_format format) +{ + switch (format) + { + case RF_UNCOMPRESSED_GRAYSCALE: return "RF_UNCOMPRESSED_GRAYSCALE"; + case RF_UNCOMPRESSED_GRAY_ALPHA: return "RF_UNCOMPRESSED_GRAY_ALPHA"; + case RF_UNCOMPRESSED_R5G6B5: return "RF_UNCOMPRESSED_R5G6B5"; + case RF_UNCOMPRESSED_R8G8B8: return "RF_UNCOMPRESSED_R8G8B8"; + case RF_UNCOMPRESSED_R5G5B5A1: return "RF_UNCOMPRESSED_R5G5B5A1"; + case RF_UNCOMPRESSED_R4G4B4A4: return "RF_UNCOMPRESSED_R4G4B4A4"; + case RF_UNCOMPRESSED_R8G8B8A8: return "RF_UNCOMPRESSED_R8G8B8A8"; + case RF_UNCOMPRESSED_R32: return "RF_UNCOMPRESSED_R32"; + case RF_UNCOMPRESSED_R32G32B32: return "RF_UNCOMPRESSED_R32G32B32"; + case RF_UNCOMPRESSED_R32G32B32A32: return "RF_UNCOMPRESSED_R32G32B32A32"; + case RF_COMPRESSED_DXT1_RGB: return "RF_COMPRESSED_DXT1_RGB"; + case RF_COMPRESSED_DXT1_RGBA: return "RF_COMPRESSED_DXT1_RGBA"; + case RF_COMPRESSED_DXT3_RGBA: return "RF_COMPRESSED_DXT3_RGBA"; + case RF_COMPRESSED_DXT5_RGBA: return "RF_COMPRESSED_DXT5_RGBA"; + case RF_COMPRESSED_ETC1_RGB: return "RF_COMPRESSED_ETC1_RGB"; + case RF_COMPRESSED_ETC2_RGB: return "RF_COMPRESSED_ETC2_RGB"; + case RF_COMPRESSED_ETC2_EAC_RGBA: return "RF_COMPRESSED_ETC2_EAC_RGBA"; + case RF_COMPRESSED_PVRT_RGB: return "RF_COMPRESSED_PVRT_RGB"; + case RF_COMPRESSED_PVRT_RGBA: return "RF_COMPRESSED_PVRT_RGBA"; + case RF_COMPRESSED_ASTC_4x4_RGBA: return "RF_COMPRESSED_ASTC_4x4_RGBA"; + case RF_COMPRESSED_ASTC_8x8_RGBA: return "RF_COMPRESSED_ASTC_8x8_RGBA"; + default: return NULL; + } +} + +RF_API bool rf_is_uncompressed_format(rf_pixel_format format) +{ + return format >= RF_UNCOMPRESSED_GRAYSCALE && format <= RF_UNCOMPRESSED_R32G32B32A32; +} + +RF_API bool rf_is_compressed_format(rf_pixel_format format) +{ + return format >= RF_COMPRESSED_DXT1_RGB && format <= RF_COMPRESSED_ASTC_8x8_RGBA; +} + +RF_API int rf_bits_per_pixel(rf_pixel_format format) +{ + switch (format) + { + case RF_UNCOMPRESSED_GRAYSCALE: return 8; // 8 bit per pixel (no alpha) + case RF_UNCOMPRESSED_GRAY_ALPHA: return 8 * 2; // 8 * 2 bpp (2 channels) + case RF_UNCOMPRESSED_R5G6B5: return 16; // 16 bpp + case RF_UNCOMPRESSED_R8G8B8: return 24; // 24 bpp + case RF_UNCOMPRESSED_R5G5B5A1: return 16; // 16 bpp (1 bit alpha) + case RF_UNCOMPRESSED_R4G4B4A4: return 16; // 16 bpp (4 bit alpha) + case RF_UNCOMPRESSED_R8G8B8A8: return 32; // 32 bpp + case RF_UNCOMPRESSED_R32: return 32; // 32 bpp (1 channel - float) + case RF_UNCOMPRESSED_R32G32B32: return 32 * 3; // 32 * 3 bpp (3 channels - float) + case RF_UNCOMPRESSED_R32G32B32A32: return 32 * 4; // 32 * 4 bpp (4 channels - float) + case RF_COMPRESSED_DXT1_RGB: return 4; // 4 bpp (no alpha) + case RF_COMPRESSED_DXT1_RGBA: return 4; // 4 bpp (1 bit alpha) + case RF_COMPRESSED_DXT3_RGBA: return 8; // 8 bpp + case RF_COMPRESSED_DXT5_RGBA: return 8; // 8 bpp + case RF_COMPRESSED_ETC1_RGB: return 4; // 4 bpp + case RF_COMPRESSED_ETC2_RGB: return 4; // 4 bpp + case RF_COMPRESSED_ETC2_EAC_RGBA: return 8; // 8 bpp + case RF_COMPRESSED_PVRT_RGB: return 4; // 4 bpp + case RF_COMPRESSED_PVRT_RGBA: return 4; // 4 bpp + case RF_COMPRESSED_ASTC_4x4_RGBA: return 8; // 8 bpp + case RF_COMPRESSED_ASTC_8x8_RGBA: return 2; // 2 bpp + default: return 0; + } +} + +RF_API int rf_bytes_per_pixel(rf_uncompressed_pixel_format format) +{ + switch (format) + { + case RF_UNCOMPRESSED_GRAYSCALE: return 1; + case RF_UNCOMPRESSED_GRAY_ALPHA: return 2; + case RF_UNCOMPRESSED_R5G5B5A1: return 2; + case RF_UNCOMPRESSED_R5G6B5: return 2; + case RF_UNCOMPRESSED_R4G4B4A4: return 2; + case RF_UNCOMPRESSED_R8G8B8A8: return 4; + case RF_UNCOMPRESSED_R8G8B8: return 3; + case RF_UNCOMPRESSED_R32: return 4; + case RF_UNCOMPRESSED_R32G32B32: return 12; + case RF_UNCOMPRESSED_R32G32B32A32: return 16; + default: return 0; + } +} + +RF_API int rf_pixel_buffer_size(int width, int height, rf_pixel_format format) +{ + return width * height * rf_bits_per_pixel(format) / 8; +} + +RF_API bool rf_format_pixels_to_normalized(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, rf_vec4* dst, rf_int dst_size) +{ + bool success = false; + + rf_int src_bpp = rf_bytes_per_pixel(src_format); + rf_int src_pixel_count = src_size / src_bpp; + rf_int dst_pixel_count = dst_size / sizeof(rf_vec4); + + if (dst_pixel_count >= src_pixel_count) + { + if (src_format == RF_UNCOMPRESSED_R32G32B32A32) + { + success = true; + memcpy(dst, src, src_size); + } + else + { + success = true; + + #define RF_FOR_EACH_PIXEL for (rf_int dst_iter = 0, src_iter = 0; src_iter < src_size && dst_iter < dst_size; dst_iter++, src_iter += src_bpp) + switch (src_format) + { + case RF_UNCOMPRESSED_GRAYSCALE: + RF_FOR_EACH_PIXEL + { + float value = ((unsigned char*)src)[src_iter] / 255.0f; + + dst[dst_iter].x = value; + dst[dst_iter].y = value; + dst[dst_iter].z = value; + dst[dst_iter].w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_GRAY_ALPHA: + RF_FOR_EACH_PIXEL + { + float value0 = (float)((unsigned char*)src)[src_iter + 0] / 255.0f; + float value1 = (float)((unsigned char*)src)[src_iter + 1] / 255.0f; + + dst[dst_iter].x = value0; + dst[dst_iter].y = value0; + dst[dst_iter].z = value0; + dst[dst_iter].w = value1; + } + break; + + case RF_UNCOMPRESSED_R5G5B5A1: + RF_FOR_EACH_PIXEL + { + unsigned short pixel = ((unsigned short*) src)[src_iter]; + + dst[dst_iter].x = (float)((pixel & 0b1111100000000000) >> 11) * (1.0f/31); + dst[dst_iter].y = (float)((pixel & 0b0000011111000000) >> 6) * (1.0f/31); + dst[dst_iter].z = (float)((pixel & 0b0000000000111110) >> 1) * (1.0f/31); + dst[dst_iter].w = ((pixel & 0b0000000000000001) == 0) ? 0.0f : 1.0f; + } + break; + + case RF_UNCOMPRESSED_R5G6B5: + RF_FOR_EACH_PIXEL + { + unsigned short pixel = ((unsigned short*)src)[src_iter]; + + dst[dst_iter].x = (float)((pixel & 0b1111100000000000) >> 11) * (1.0f / 31); + dst[dst_iter].y = (float)((pixel & 0b0000011111100000) >> 5) * (1.0f / 63); + dst[dst_iter].z = (float) (pixel & 0b0000000000011111) * (1.0f / 31); + dst[dst_iter].w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_R4G4B4A4: + RF_FOR_EACH_PIXEL + { + unsigned short pixel = ((unsigned short*)src)[src_iter]; + + dst[dst_iter].x = (float)((pixel & 0b1111000000000000) >> 12) * (1.0f / 15); + dst[dst_iter].y = (float)((pixel & 0b0000111100000000) >> 8) * (1.0f / 15); + dst[dst_iter].z = (float)((pixel & 0b0000000011110000) >> 4) * (1.0f / 15); + dst[dst_iter].w = (float) (pixel & 0b0000000000001111) * (1.0f / 15); + } + break; + + case RF_UNCOMPRESSED_R8G8B8A8: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].x = (float)((unsigned char*)src)[src_iter + 0] / 255.0f; + dst[dst_iter].y = (float)((unsigned char*)src)[src_iter + 1] / 255.0f; + dst[dst_iter].z = (float)((unsigned char*)src)[src_iter + 2] / 255.0f; + dst[dst_iter].w = (float)((unsigned char*)src)[src_iter + 3] / 255.0f; + } + break; + + case RF_UNCOMPRESSED_R8G8B8: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].x = (float)((unsigned char*)src)[src_iter + 0] / 255.0f; + dst[dst_iter].y = (float)((unsigned char*)src)[src_iter + 1] / 255.0f; + dst[dst_iter].z = (float)((unsigned char*)src)[src_iter + 2] / 255.0f; + dst[dst_iter].w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_R32: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].x = ((float*)src)[src_iter]; + dst[dst_iter].y = 0.0f; + dst[dst_iter].z = 0.0f; + dst[dst_iter].w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_R32G32B32: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].x = ((float*)src)[src_iter + 0]; + dst[dst_iter].y = ((float*)src)[src_iter + 1]; + dst[dst_iter].z = ((float*)src)[src_iter + 2]; + dst[dst_iter].w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_R32G32B32A32: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].x = ((float*)src)[src_iter + 0]; + dst[dst_iter].y = ((float*)src)[src_iter + 1]; + dst[dst_iter].z = ((float*)src)[src_iter + 2]; + dst[dst_iter].w = ((float*)src)[src_iter + 3]; + } + break; + + default: break; + } + #undef RF_FOR_EACH_PIXEL + } + } + else RF_LOG_ERROR(RF_BAD_BUFFER_SIZE, "Buffer is size %d but function expected a size of at least %d.", dst_size, src_pixel_count * sizeof(rf_vec4)); + + return success; +} + +RF_API bool rf_format_pixels_to_rgba32(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, rf_color* dst, rf_int dst_size) +{ + bool success = false; + + rf_int src_bpp = rf_bytes_per_pixel(src_format); + rf_int src_pixel_count = src_size / src_bpp; + rf_int dst_pixel_count = dst_size / sizeof(rf_color); + + if (dst_pixel_count >= src_pixel_count) + { + if (src_format == RF_UNCOMPRESSED_R8G8B8A8) + { + success = true; + memcpy(dst, src, src_size); + } + else + { + success = true; + #define RF_FOR_EACH_PIXEL for (rf_int dst_iter = 0, src_iter = 0; src_iter < src_size && dst_iter < dst_size; dst_iter++, src_iter += src_bpp) + switch (src_format) + { + case RF_UNCOMPRESSED_GRAYSCALE: + RF_FOR_EACH_PIXEL + { + unsigned char value = ((unsigned char*) src)[src_iter]; + dst[dst_iter].r = value; + dst[dst_iter].g = value; + dst[dst_iter].b = value; + dst[dst_iter].a = 255; + } + break; + + case RF_UNCOMPRESSED_GRAY_ALPHA: + RF_FOR_EACH_PIXEL + { + unsigned char value0 = ((unsigned char*) src)[src_iter + 0]; + unsigned char value1 = ((unsigned char*) src)[src_iter + 1]; + + dst[dst_iter].r = value0; + dst[dst_iter].g = value0; + dst[dst_iter].b = value0; + dst[dst_iter].a = value1; + } + break; + + case RF_UNCOMPRESSED_R5G5B5A1: + RF_FOR_EACH_PIXEL + { + unsigned short pixel = ((unsigned short*) src)[src_iter]; + + dst[dst_iter].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11) * (255 / 31)); + dst[dst_iter].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6) * (255 / 31)); + dst[dst_iter].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1) * (255 / 31)); + dst[dst_iter].a = (unsigned char) ((pixel & 0b0000000000000001) * 255); + } + break; + + case RF_UNCOMPRESSED_R5G6B5: + RF_FOR_EACH_PIXEL + { + unsigned short pixel = ((unsigned short*) src)[src_iter]; + + dst[dst_iter].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)* (255 / 31)); + dst[dst_iter].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)* (255 / 63)); + dst[dst_iter].b = (unsigned char)((float) (pixel & 0b0000000000011111) * (255 / 31)); + dst[dst_iter].a = 255; + } + break; + + case RF_UNCOMPRESSED_R4G4B4A4: + RF_FOR_EACH_PIXEL + { + unsigned short pixel = ((unsigned short*) src)[src_iter]; + + dst[dst_iter].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12) * (255 / 15)); + dst[dst_iter].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8) * (255 / 15)); + dst[dst_iter].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4) * (255 / 15)); + dst[dst_iter].a = (unsigned char)((float) (pixel & 0b0000000000001111) * (255 / 15)); + } + break; + + case RF_UNCOMPRESSED_R8G8B8A8: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].r = ((unsigned char*) src)[src_iter + 0]; + dst[dst_iter].g = ((unsigned char*) src)[src_iter + 1]; + dst[dst_iter].b = ((unsigned char*) src)[src_iter + 2]; + dst[dst_iter].a = ((unsigned char*) src)[src_iter + 3]; + } + break; + + case RF_UNCOMPRESSED_R8G8B8: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].r = (unsigned char)((unsigned char*) src)[src_iter + 0]; + dst[dst_iter].g = (unsigned char)((unsigned char*) src)[src_iter + 1]; + dst[dst_iter].b = (unsigned char)((unsigned char*) src)[src_iter + 2]; + dst[dst_iter].a = 255; + } + break; + + case RF_UNCOMPRESSED_R32: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].r = (unsigned char)(((float*) src)[src_iter + 0] * 255.0f); + dst[dst_iter].g = 0; + dst[dst_iter].b = 0; + dst[dst_iter].a = 255; + } + break; + + case RF_UNCOMPRESSED_R32G32B32: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].r = (unsigned char)(((float*) src)[src_iter + 0] * 255.0f); + dst[dst_iter].g = (unsigned char)(((float*) src)[src_iter + 1] * 255.0f); + dst[dst_iter].b = (unsigned char)(((float*) src)[src_iter + 2] * 255.0f); + dst[dst_iter].a = 255; + } + break; + + case RF_UNCOMPRESSED_R32G32B32A32: + RF_FOR_EACH_PIXEL + { + dst[dst_iter].r = (unsigned char)(((float*) src)[src_iter + 0] * 255.0f); + dst[dst_iter].g = (unsigned char)(((float*) src)[src_iter + 1] * 255.0f); + dst[dst_iter].b = (unsigned char)(((float*) src)[src_iter + 2] * 255.0f); + dst[dst_iter].a = (unsigned char)(((float*) src)[src_iter + 3] * 255.0f); + } + break; + + default: break; + } + #undef RF_FOR_EACH_PIXEL + } + } + else RF_LOG_ERROR(RF_BAD_BUFFER_SIZE, "Buffer is size %d but function expected a size of at least %d", dst_size, src_pixel_count * sizeof(rf_color)); + + return success; +} + +RF_API bool rf_format_pixels(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format) +{ + bool success = false; + + if (rf_is_uncompressed_format(src_format) && dst_format == RF_UNCOMPRESSED_R32G32B32A32) + { + success = rf_format_pixels_to_normalized(src, src_size, src_format, dst, dst_size); + } + else if (rf_is_uncompressed_format(src_format) && dst_format == RF_UNCOMPRESSED_R8G8B8A8) + { + success = rf_format_pixels_to_rgba32(src, src_size, src_format, dst, dst_size); + } + else if (rf_is_uncompressed_format(src_format) && rf_is_uncompressed_format(dst_format)) + { + rf_int src_bpp = rf_bytes_per_pixel(src_format); + rf_int dst_bpp = rf_bytes_per_pixel(dst_format); + + rf_int src_pixel_count = src_size / src_bpp; + rf_int dst_pixel_count = dst_size / dst_bpp; + + if (dst_pixel_count >= src_pixel_count) + { + success = true; + + //Loop over both src and dst + #define RF_FOR_EACH_PIXEL for (rf_int src_iter = 0, dst_iter = 0; src_iter < src_size && dst_iter < dst_size; src_iter += src_bpp, dst_iter += dst_bpp) + #define RF_COMPUTE_NORMALIZED_PIXEL() rf_format_one_pixel_to_normalized(((unsigned char*) src) + src_iter, src_format); + if (src_format == dst_format) + { + memcpy(dst, src, src_size); + } + else + { + switch (dst_format) + { + case RF_UNCOMPRESSED_GRAYSCALE: + RF_FOR_EACH_PIXEL + { + rf_vec4 normalized = RF_COMPUTE_NORMALIZED_PIXEL(); + ((unsigned char*)dst)[dst_iter] = (unsigned char)((normalized.x * 0.299f + normalized.y * 0.587f + normalized.z * 0.114f) * 255.0f); + } + break; + + case RF_UNCOMPRESSED_GRAY_ALPHA: + RF_FOR_EACH_PIXEL + { + rf_vec4 normalized = RF_COMPUTE_NORMALIZED_PIXEL(); + + ((unsigned char*)dst)[dst_iter ] = (unsigned char)((normalized.x * 0.299f + (float)normalized.y * 0.587f + (float)normalized.z * 0.114f) * 255.0f); + ((unsigned char*)dst)[dst_iter + 1] = (unsigned char) (normalized.w * 255.0f); + } + break; + + case RF_UNCOMPRESSED_R5G6B5: + RF_FOR_EACH_PIXEL + { + rf_vec4 normalized = RF_COMPUTE_NORMALIZED_PIXEL(); + + unsigned char r = (unsigned char)(round(normalized.x * 31.0f)); + unsigned char g = (unsigned char)(round(normalized.y * 63.0f)); + unsigned char b = (unsigned char)(round(normalized.z * 31.0f)); + + ((unsigned short*)dst)[dst_iter] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + } + break; + + case RF_UNCOMPRESSED_R8G8B8: + RF_FOR_EACH_PIXEL + { + rf_vec4 normalized = RF_COMPUTE_NORMALIZED_PIXEL(); + + ((unsigned char*)dst)[dst_iter ] = (unsigned char)(normalized.x * 255.0f); + ((unsigned char*)dst)[dst_iter + 1] = (unsigned char)(normalized.y * 255.0f); + ((unsigned char*)dst)[dst_iter + 2] = (unsigned char)(normalized.z * 255.0f); + } + break; + + case RF_UNCOMPRESSED_R5G5B5A1: + RF_FOR_EACH_PIXEL + { + rf_vec4 normalized = RF_COMPUTE_NORMALIZED_PIXEL(); + + int ALPHA_THRESHOLD = 50; + unsigned char r = (unsigned char)(round(normalized.x * 31.0f)); + unsigned char g = (unsigned char)(round(normalized.y * 31.0f)); + unsigned char b = (unsigned char)(round(normalized.z * 31.0f)); + unsigned char a = (normalized.w > ((float)ALPHA_THRESHOLD / 255.0f)) ? 1 : 0; + + ((unsigned short*)dst)[dst_iter] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + break; + + case RF_UNCOMPRESSED_R4G4B4A4: + RF_FOR_EACH_PIXEL + { + rf_vec4 normalized = RF_COMPUTE_NORMALIZED_PIXEL(); + + unsigned char r = (unsigned char)(round(normalized.x * 15.0f)); + unsigned char g = (unsigned char)(round(normalized.y * 15.0f)); + unsigned char b = (unsigned char)(round(normalized.z * 15.0f)); + unsigned char a = (unsigned char)(round(normalized.w * 15.0f)); + + ((unsigned short*)dst)[dst_iter] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + break; + + case RF_UNCOMPRESSED_R32: + RF_FOR_EACH_PIXEL + { + rf_vec4 normalized = RF_COMPUTE_NORMALIZED_PIXEL(); + + ((float*)dst)[dst_iter] = (float)(normalized.x * 0.299f + normalized.y * 0.587f + normalized.z * 0.114f); + } + break; + + case RF_UNCOMPRESSED_R32G32B32: + RF_FOR_EACH_PIXEL + { + rf_vec4 normalized = RF_COMPUTE_NORMALIZED_PIXEL(); + + ((float*)dst)[dst_iter ] = normalized.x; + ((float*)dst)[dst_iter + 1] = normalized.y; + ((float*)dst)[dst_iter + 2] = normalized.z; + } + break; + + default: break; + } + } + #undef RF_FOR_EACH_PIXEL + #undef RF_COMPUTE_NORMALIZED_PIXEL + } + else RF_LOG_ERROR(RF_BAD_BUFFER_SIZE, "Buffer is size %d but function expected a size of at least %d.", dst_size, src_pixel_count * dst_bpp); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Function expected uncompressed pixel formats. Source format: %d, Destination format: %d.", src_format, dst_format); + + return success; +} + +RF_API rf_vec4 rf_format_one_pixel_to_normalized(const void* src, rf_uncompressed_pixel_format src_format) +{ + rf_vec4 result = {0}; + + switch (src_format) + { + case RF_UNCOMPRESSED_GRAYSCALE: + { + float value = ((unsigned char*)src)[0] / 255.0f; + + result.x = value; + result.y = value; + result.z = value; + result.w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_GRAY_ALPHA: + { + float value0 = (float)((unsigned char*)src)[0] / 255.0f; + float value1 = (float)((unsigned char*)src)[1] / 255.0f; + + result.x = value0; + result.y = value0; + result.z = value0; + result.w = value1; + } + break; + + case RF_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short*) src)[0]; + + result.x = (float)((pixel & 0b1111100000000000) >> 11) * (1.0f/31); + result.y = (float)((pixel & 0b0000011111000000) >> 6) * (1.0f/31); + result.z = (float)((pixel & 0b0000000000111110) >> 1) * (1.0f/31); + result.w = ((pixel & 0b0000000000000001) == 0) ? 0.0f : 1.0f; + } + break; + + case RF_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short*)src)[0]; + + result.x = (float)((pixel & 0b1111100000000000) >> 11) * (1.0f / 31); + result.y = (float)((pixel & 0b0000011111100000) >> 5) * (1.0f / 63); + result.z = (float) (pixel & 0b0000000000011111) * (1.0f / 31); + result.w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short*)src)[0]; + + result.x = (float)((pixel & 0b1111000000000000) >> 12) * (1.0f / 15); + result.y = (float)((pixel & 0b0000111100000000) >> 8) * (1.0f / 15); + result.z = (float)((pixel & 0b0000000011110000) >> 4) * (1.0f / 15); + result.w = (float) (pixel & 0b0000000000001111) * (1.0f / 15); + } + break; + + case RF_UNCOMPRESSED_R8G8B8A8: + { + result.x = (float)((unsigned char*)src)[0] / 255.0f; + result.y = (float)((unsigned char*)src)[1] / 255.0f; + result.z = (float)((unsigned char*)src)[2] / 255.0f; + result.w = (float)((unsigned char*)src)[3] / 255.0f; + } + break; + + case RF_UNCOMPRESSED_R8G8B8: + { + result.x = (float)((unsigned char*)src)[0] / 255.0f; + result.y = (float)((unsigned char*)src)[1] / 255.0f; + result.z = (float)((unsigned char*)src)[2] / 255.0f; + result.w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_R32: + { + result.x = ((float*)src)[0]; + result.y = 0.0f; + result.z = 0.0f; + result.w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_R32G32B32: + { + result.x = ((float*)src)[0]; + result.y = ((float*)src)[1]; + result.z = ((float*)src)[2]; + result.w = 1.0f; + } + break; + + case RF_UNCOMPRESSED_R32G32B32A32: + { + result.x = ((float*)src)[0]; + result.y = ((float*)src)[1]; + result.z = ((float*)src)[2]; + result.w = ((float*)src)[3]; + } + break; + + default: break; + } + + return result; +} + +RF_API rf_color rf_format_one_pixel_to_rgba32(const void* src, rf_uncompressed_pixel_format src_format) +{ + rf_color result = {0}; + + switch (src_format) + { + case RF_UNCOMPRESSED_GRAYSCALE: + { + unsigned char value = ((unsigned char*) src)[0]; + result.r = value; + result.g = value; + result.b = value; + result.a = 255; + } + break; + + case RF_UNCOMPRESSED_GRAY_ALPHA: + { + unsigned char value0 = ((unsigned char*) src)[0]; + unsigned char value1 = ((unsigned char*) src)[1]; + + result.r = value0; + result.g = value0; + result.b = value0; + result.a = value1; + } + break; + + case RF_UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short*) src)[0]; + + result.r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11) * (255 / 31)); + result.g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6) * (255 / 31)); + result.b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1) * (255 / 31)); + result.a = (unsigned char) ((pixel & 0b0000000000000001) * 255); + } + break; + + case RF_UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short*) src)[0]; + + result.r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)* (255 / 31)); + result.g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)* (255 / 63)); + result.b = (unsigned char)((float) (pixel & 0b0000000000011111) * (255 / 31)); + result.a = 255; + } + break; + + case RF_UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short*) src)[0]; + + result.r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12) * (255 / 15)); + result.g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8) * (255 / 15)); + result.b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4) * (255 / 15)); + result.a = (unsigned char)((float) (pixel & 0b0000000000001111) * (255 / 15)); + } + break; + + case RF_UNCOMPRESSED_R8G8B8A8: + { + result.r = ((unsigned char*) src)[0]; + result.g = ((unsigned char*) src)[1]; + result.b = ((unsigned char*) src)[2]; + result.a = ((unsigned char*) src)[3]; + } + break; + + case RF_UNCOMPRESSED_R8G8B8: + { + result.r = (unsigned char)((unsigned char*) src)[0]; + result.g = (unsigned char)((unsigned char*) src)[1]; + result.b = (unsigned char)((unsigned char*) src)[2]; + result.a = 255; + } + break; + + case RF_UNCOMPRESSED_R32: + { + result.r = (unsigned char)(((float*) src)[0] * 255.0f); + result.g = 0; + result.b = 0; + result.a = 255; + } + break; + + case RF_UNCOMPRESSED_R32G32B32: + { + result.r = (unsigned char)(((float*) src)[0] * 255.0f); + result.g = (unsigned char)(((float*) src)[1] * 255.0f); + result.b = (unsigned char)(((float*) src)[2] * 255.0f); + result.a = 255; + } + break; + + case RF_UNCOMPRESSED_R32G32B32A32: + { + result.r = (unsigned char)(((float*) src)[0] * 255.0f); + result.g = (unsigned char)(((float*) src)[1] * 255.0f); + result.b = (unsigned char)(((float*) src)[2] * 255.0f); + result.a = (unsigned char)(((float*) src)[3] * 255.0f); + } + break; + + default: break; + } + + return result; +} + +RF_API void rf_format_one_pixel(const void* src, rf_uncompressed_pixel_format src_format, void* dst, rf_uncompressed_pixel_format dst_format) +{ + if (src_format == dst_format && rf_is_uncompressed_format(src_format) && rf_is_uncompressed_format(dst_format)) + { + memcpy(dst, src, rf_bytes_per_pixel(src_format)); + } + else if (rf_is_uncompressed_format(src_format) && dst_format == RF_UNCOMPRESSED_R32G32B32A32) + { + *((rf_vec4*)dst) = rf_format_one_pixel_to_normalized(src, src_format); + } + else if (rf_is_uncompressed_format(src_format) && dst_format == RF_UNCOMPRESSED_R8G8B8A8) + { + *((rf_color*)dst) = rf_format_one_pixel_to_rgba32(src, src_format); + } + else if (rf_is_uncompressed_format(src_format) && rf_is_uncompressed_format(dst_format)) + { + switch (dst_format) + { + case RF_UNCOMPRESSED_GRAYSCALE: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + ((unsigned char*)dst)[0] = (unsigned char)((normalized.x * 0.299f + normalized.y * 0.587f + normalized.z * 0.114f) * 255.0f); + } + break; + + case RF_UNCOMPRESSED_GRAY_ALPHA: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + ((unsigned char*)dst)[0 ] = (unsigned char)((normalized.x * 0.299f + (float)normalized.y * 0.587f + (float)normalized.z * 0.114f) * 255.0f); + ((unsigned char*)dst)[0 + 1] = (unsigned char) (normalized.w * 255.0f); + } + break; + + case RF_UNCOMPRESSED_R5G6B5: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + unsigned char r = (unsigned char)(round(normalized.x * 31.0f)); + unsigned char g = (unsigned char)(round(normalized.y * 63.0f)); + unsigned char b = (unsigned char)(round(normalized.z * 31.0f)); + + ((unsigned short*)dst)[0] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + } + break; + + case RF_UNCOMPRESSED_R8G8B8: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + ((unsigned char*)dst)[0 ] = (unsigned char)(normalized.x * 255.0f); + ((unsigned char*)dst)[0 + 1] = (unsigned char)(normalized.y * 255.0f); + ((unsigned char*)dst)[0 + 2] = (unsigned char)(normalized.z * 255.0f); + } + break; + + case RF_UNCOMPRESSED_R5G5B5A1: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + int ALPHA_THRESHOLD = 50; + unsigned char r = (unsigned char)(round(normalized.x * 31.0f)); + unsigned char g = (unsigned char)(round(normalized.y * 31.0f)); + unsigned char b = (unsigned char)(round(normalized.z * 31.0f)); + unsigned char a = (normalized.w > ((float)ALPHA_THRESHOLD / 255.0f)) ? 1 : 0; + + ((unsigned short*)dst)[0] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; + } + break; + + case RF_UNCOMPRESSED_R4G4B4A4: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + unsigned char r = (unsigned char)(round(normalized.x * 15.0f)); + unsigned char g = (unsigned char)(round(normalized.y * 15.0f)); + unsigned char b = (unsigned char)(round(normalized.z * 15.0f)); + unsigned char a = (unsigned char)(round(normalized.w * 15.0f)); + + ((unsigned short*)dst)[0] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; + } + break; + + case RF_UNCOMPRESSED_R8G8B8A8: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + ((unsigned char*)dst)[0 ] = (unsigned char)(normalized.x * 255.0f); + ((unsigned char*)dst)[0 + 1] = (unsigned char)(normalized.y * 255.0f); + ((unsigned char*)dst)[0 + 2] = (unsigned char)(normalized.z * 255.0f); + ((unsigned char*)dst)[0 + 3] = (unsigned char)(normalized.w * 255.0f); + } + break; + + case RF_UNCOMPRESSED_R32: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + ((float*)dst)[0] = (float)(normalized.x * 0.299f + normalized.y * 0.587f + normalized.z * 0.114f); + } + break; + + case RF_UNCOMPRESSED_R32G32B32: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + ((float*)dst)[0 ] = normalized.x; + ((float*)dst)[0 + 1] = normalized.y; + ((float*)dst)[0 + 2] = normalized.z; + } + break; + + case RF_UNCOMPRESSED_R32G32B32A32: + { + rf_vec4 normalized = rf_format_one_pixel_to_normalized(src, src_format); + ((float*)dst)[0 ] = normalized.x; + ((float*)dst)[0 + 1] = normalized.y; + ((float*)dst)[0 + 2] = normalized.z; + ((float*)dst)[0 + 3] = normalized.w; + } + break; + + default: break; + } + } +} + +#pragma endregion + +#pragma region color + +// Returns true if the two colors have the same values for the rgb components +RF_API bool rf_color_match_rgb(rf_color a, rf_color b) +{ + return a.r == b.r && a.g == b.g && a.b == b.b; +} + +// Returns true if the two colors have the same values +RF_API bool rf_color_match(rf_color a, rf_color b) +{ + return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; +} + +// Returns hexadecimal value for a rf_color +RF_API int rf_color_to_int(rf_color color) +{ + return (((int) color.r << 24) | ((int) color.g << 16) | ((int) color.b << 8) | (int) color.a); +} + +// Returns color normalized as float [0..1] +RF_API rf_vec4 rf_color_normalize(rf_color color) +{ + rf_vec4 result; + + result.x = (float) color.r / 255.0f; + result.y = (float) color.g / 255.0f; + result.z = (float) color.b / 255.0f; + result.w = (float) color.a / 255.0f; + + return result; +} + +// Returns color from normalized values [0..1] +RF_API rf_color rf_color_from_normalized(rf_vec4 normalized) +{ + rf_color result; + + result.r = normalized.x * 255.0f; + result.g = normalized.y * 255.0f; + result.b = normalized.z * 255.0f; + result.a = normalized.w * 255.0f; + + return result; +} + +// Returns HSV values for a rf_color. Hue is returned as degrees [0..360] +RF_API rf_vec3 rf_color_to_hsv(rf_color color) +{ + rf_vec3 rgb = {(float) color.r / 255.0f, (float) color.g / 255.0f, (float) color.b / 255.0f}; + rf_vec3 hsv = {0.0f, 0.0f, 0.0f}; + float min, max, delta; + + min = rgb.x < rgb.y ? rgb.x : rgb.y; + min = min < rgb.z ? min : rgb.z; + + max = rgb.x > rgb.y ? rgb.x : rgb.y; + max = max > rgb.z ? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { +// NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta / max); // Saturation + } else + { +// NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = NAN; // Undefined + return hsv; + } + +// NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z) / delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x) / delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y) / delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Returns a rf_color from HSV values. rf_color->HSV->rf_color conversion will not yield exactly the same color due to rounding errors. Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion +RF_API rf_color rf_color_from_hsv(rf_vec3 hsv) +{ + rf_color color = {0, 0, 0, 255}; + float h = hsv.x, s = hsv.y, v = hsv.z; + +// Red channel + float k = fmod((5.0f + h / 60.0f), 6); + float t = 4.0f - k; + k = (t < k) ? t : k; + k = (k < 1) ? k : 1; + k = (k > 0) ? k : 0; + color.r = (v - v * s * k) * 255; + +// Green channel + k = fmod((3.0f + h / 60.0f), 6); + t = 4.0f - k; + k = (t < k) ? t : k; + k = (k < 1) ? k : 1; + k = (k > 0) ? k : 0; + color.g = (v - v * s * k) * 255; + +// Blue channel + k = fmod((1.0f + h / 60.0f), 6); + t = 4.0f - k; + k = (t < k) ? t : k; + k = (k < 1) ? k : 1; + k = (k > 0) ? k : 0; + color.b = (v - v * s * k) * 255; + + return color; +} + +// Returns a rf_color struct from hexadecimal value +RF_API rf_color rf_color_from_int(int hex_value) +{ + rf_color color; + + color.r = (unsigned char) (hex_value >> 24) & 0xFF; + color.g = (unsigned char) (hex_value >> 16) & 0xFF; + color.b = (unsigned char) (hex_value >> 8) & 0xFF; + color.a = (unsigned char) hex_value & 0xFF; + + return color; +} + +// rf_color fade-in or fade-out, alpha goes from 0.0f to 1.0f +RF_API rf_color rf_fade(rf_color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + return (rf_color) {color.r, color.g, color.b, (unsigned char) (255.0f * alpha)}; +} + +#pragma endregion +/*** End of inlined file: rayfork-color.c ***/ + + +/*** Start of inlined file: rayfork-unicode.c ***/ +RF_API rf_decoded_rune rf_decode_utf8_char(const char* text, rf_int len) +{ + /* + UTF8 specs from https://www.ietf.org/rfc/rfc3629.txt + Char. number range | UTF-8 byte sequence + (hexadecimal) | (binary) + --------------------+--------------------------------------------- + 0000 0000-0000 007F | 0xxxxxxx + 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + + if (len < 1) + { + return (rf_decoded_rune) { RF_INVALID_CODEPOINT }; + } + + // The first UTF8 byte + const int byte = (unsigned char)(text[0]); + + if (byte <= 0x7f) + { + // Only one byte (ASCII range x00-7F) + const int code = text[0]; + + // Codepoints after U+10ffff are invalid + const int valid = code > 0x10ffff; + + return (rf_decoded_rune) { valid ? RF_INVALID_CODEPOINT : code, .bytes_processed = 1, .valid = valid }; + } + else if ((byte & 0xe0) == 0xc0) + { + if (len < 2) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 1, }; + } + + // Two bytes + // [0]xC2-DF [1]UTF8-tail(x80-BF) + const unsigned char byte1 = text[1]; + + // Check for unexpected sequence + if ((byte1 == '\0') || ((byte1 >> 6) != 2)) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 2 }; + } + + if ((byte >= 0xc2) && (byte <= 0xdf)) + { + const int code = ((byte & 0x1f) << 6) | (byte1 & 0x3f); + + // Codepoints after U+10ffff are invalid + const int valid = code > 0x10ffff; + + return (rf_decoded_rune) {valid ? RF_INVALID_CODEPOINT : code, .bytes_processed = 2, .valid = valid }; + } + } + else if ((byte & 0xf0) == 0xe0) + { + if (len < 2) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 1 }; + } + + // Three bytes + const unsigned char byte1 = text[1]; + + // Check for unexpected sequence + if ((byte1 == '\0') || (len < 3) || ((byte1 >> 6) != 2)) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 2 }; + } + + const unsigned char byte2 = text[2]; + + // Check for unexpected sequence + if ((byte2 == '\0') || ((byte2 >> 6) != 2)) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 3 }; + } + + /* + [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF) + [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF) + [0]xED [1]x80-9F [2]UTF8-tail(x80-BF) + [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF) + */ + if (((byte == 0xe0) && !((byte1 >= 0xa0) && (byte1 <= 0xbf))) || + ((byte == 0xed) && !((byte1 >= 0x80) && (byte1 <= 0x9f)))) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 2 }; + } + + if ((byte >= 0xe0) && (byte <= 0xef)) + { + const int code = ((byte & 0xf) << 12) | ((byte1 & 0x3f) << 6) | (byte2 & 0x3f); + + // Codepoints after U+10ffff are invalid + const int valid = code > 0x10ffff; + return (rf_decoded_rune) {valid ? RF_INVALID_CODEPOINT : code, .bytes_processed = 3, .valid = valid }; + } + } + else if ((byte & 0xf8) == 0xf0) + { + // Four bytes + if (byte > 0xf4 || len < 2) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 1 }; + } + + const unsigned char byte1 = text[1]; + + // Check for unexpected sequence + if ((byte1 == '\0') || (len < 3) || ((byte1 >> 6) != 2)) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 2 }; + } + + const unsigned char byte2 = text[2]; + + // Check for unexpected sequence + if ((byte2 == '\0') || (len < 4) || ((byte2 >> 6) != 2)) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 3 }; + } + + const unsigned char byte3 = text[3]; + + // Check for unexpected sequence + if ((byte3 == '\0') || ((byte3 >> 6) != 2)) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 4 }; + } + + /* + [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail + [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail + [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail + */ + + // Check for unexpected sequence + if (((byte == 0xf0) && !((byte1 >= 0x90) && (byte1 <= 0xbf))) || + ((byte == 0xf4) && !((byte1 >= 0x80) && (byte1 <= 0x8f)))) + { + return (rf_decoded_rune) {RF_INVALID_CODEPOINT, .bytes_processed = 2 }; + } + + if (byte >= 0xf0) + { + const int code = ((byte & 0x7) << 18) | ((byte1 & 0x3f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f); + + // Codepoints after U+10ffff are invalid + const int valid = code > 0x10ffff; + return (rf_decoded_rune) {valid ? RF_INVALID_CODEPOINT : code, .bytes_processed = 4, .valid = valid }; + } + } + + return (rf_decoded_rune) { .codepoint = RF_INVALID_CODEPOINT, .bytes_processed = 1 }; +} + +RF_API rf_decoded_utf8_stats rf_count_utf8_chars(const char* text, rf_int len) +{ + rf_decoded_utf8_stats result = {0}; + + if (text && len > 0) + { + while (len > 0) + { + rf_decoded_rune decoded_rune = rf_decode_utf8_char(text, len); + + text += decoded_rune.bytes_processed; + len -= decoded_rune.bytes_processed; + + result.bytes_processed += decoded_rune.bytes_processed; + result.invalid_bytes += decoded_rune.valid ? 0 : decoded_rune.bytes_processed; + result.valid_rune_count += decoded_rune.valid ? 1 : 0; + result.total_rune_count += 1; + } + } + + return result; +} + +RF_API rf_decoded_string rf_decode_utf8_to_buffer(const char* text, rf_int len, rf_rune* dst, rf_int dst_size) +{ + rf_decoded_string result = {0}; + + result.codepoints = dst; + + if (text && len > 0 && dst && dst_size > 0) + { + int dst_i = 0; + int invalid_bytes = 0; + + while (len > 0 && dst_i < dst_size) + { + rf_decoded_rune decoding_result = rf_decode_utf8_char(text, len); + + // Count the invalid bytes + if (!decoding_result.valid) + { + invalid_bytes += decoding_result.bytes_processed; + } + + text += decoding_result.bytes_processed; + len -= decoding_result.bytes_processed; + + dst[dst_i++] = decoding_result.codepoint; + } + + result.size = dst_i; + result.valid = true; + result.invalid_bytes_count = invalid_bytes; + } + + return result; +} + +RF_API rf_decoded_string rf_decode_utf8(const char* text, rf_int len, rf_allocator allocator) +{ + rf_decoded_string result = {0}; + + rf_rune* dst = RF_ALLOC(allocator, sizeof(rf_rune) * len); + + result = rf_decode_utf8_to_buffer(text, len, dst, len); + + return result; +} +/*** End of inlined file: rayfork-unicode.c ***/ + + +/*** Start of inlined file: rayfork-math.c ***/ +#pragma region misc + +RF_API float rf_next_pot(float it) +{ + return powf(2, ceilf(logf(it) / logf(2))); +} + +RF_API rf_vec2 rf_center_to_screen(float w, float h) +{ + rf_vec2 result = { rf_ctx.render_width / 2 - w / 2, rf_ctx.render_height / 2 - h / 2 }; + return result; +} + +RF_API rf_vec2 rf_center_to_object(rf_sizef center_this, rf_rec to_this) +{ + rf_vec2 result = { to_this.x + to_this.width / 2 - center_this.width / 2, to_this.y + to_this.height / 2 - center_this.height / 2 }; + return result; +} + +RF_API float rf_clamp(float value, float min, float max) +{ + const float res = value < min ? min : value; + return res > max ? max : res; +} + +RF_API float rf_lerp(float start, float end, float amount) +{ + return start + amount * (end - start); +} + +#pragma endregion + +#pragma region base64 + +RF_API int rf_get_size_base64(const unsigned char *input) +{ + int size = 0; + + for (rf_int i = 0; input[4 * i] != 0; i++) + { + if (input[4 * i + 3] == '=') + { + if (input[4 * i + 2] == '=') size += 1; + else size += 2; + } else size += 3; + } + + return size; +} + +RF_API rf_base64_output rf_decode_base64(const unsigned char* input, rf_allocator allocator) +{ + static const unsigned char rf_base64_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51 + }; + + rf_base64_output result; + result.size = rf_get_size_base64(input); + result.buffer = RF_ALLOC(allocator, result.size); + + for (rf_int i = 0; i < result.size / 3; i++) + { + unsigned char a = rf_base64_table[(int) input[4 * i + 0]]; + unsigned char b = rf_base64_table[(int) input[4 * i + 1]]; + unsigned char c = rf_base64_table[(int) input[4 * i + 2]]; + unsigned char d = rf_base64_table[(int) input[4 * i + 3]]; + + result.buffer[3 * i + 0] = (a << 2) | (b >> 4); + result.buffer[3 * i + 1] = (b << 4) | (c >> 2); + result.buffer[3 * i + 2] = (c << 6) | d; + } + + int n = result.size / 3; + + if (result.size % 3 == 1) + { + unsigned char a = rf_base64_table[(int) input[4 * n + 0]]; + unsigned char b = rf_base64_table[(int) input[4 * n + 1]]; + + result.buffer[result.size - 1] = (a << 2) | (b >> 4); + } else if (result.size % 3 == 2) + { + unsigned char a = rf_base64_table[(int) input[4 * n + 0]]; + unsigned char b = rf_base64_table[(int) input[4 * n + 1]]; + unsigned char c = rf_base64_table[(int) input[4 * n + 2]]; + + result.buffer[result.size - 2] = (a << 2) | (b >> 4); + result.buffer[result.size - 1] = (b << 4) | (c >> 2); + } + + return result; +} + +#pragma endregion + +#pragma region vec and matrix math + +// Add two vectors (v1 + v2) +RF_API rf_vec2 rf_vec2_add(rf_vec2 v1, rf_vec2 v2) +{ + rf_vec2 result = {v1.x + v2.x, v1.y + v2.y}; + return result; +} + +// Subtract two vectors (v1 - v2) +RF_API rf_vec2 rf_vec2_sub(rf_vec2 v1, rf_vec2 v2) +{ + rf_vec2 result = {v1.x - v2.x, v1.y - v2.y}; + return result; +} + +// Calculate vector length +RF_API float rf_vec2_len(rf_vec2 v) +{ + float result = sqrt((v.x * v.x) + (v.y * v.y)); + return result; +} + +// Calculate two vectors dot product +RF_API float rf_vec2_dot_product(rf_vec2 v1, rf_vec2 v2) +{ + float result = (v1.x * v2.x + v1.y * v2.y); + return result; +} + +// Calculate distance between two vectors +RF_API float rf_vec2_distance(rf_vec2 v1, rf_vec2 v2) +{ + float result = sqrt((v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y)); + return result; +} + +// Calculate angle from two vectors in X-axis +RF_API float rf_vec2_angle(rf_vec2 v1, rf_vec2 v2) +{ + float result = atan2f(v2.y - v1.y, v2.x - v1.x) * (180.0f / RF_PI); + if (result < 0) result += 360.0f; + return result; +} + +// Scale vector (multiply by value) +RF_API rf_vec2 rf_vec2_scale(rf_vec2 v, float scale) +{ + rf_vec2 result = {v.x * scale, v.y * scale}; + return result; +} + +// Multiply vector by vector +RF_API rf_vec2 rf_vec2_mul_v(rf_vec2 v1, rf_vec2 v2) +{ + rf_vec2 result = {v1.x * v2.x, v1.y * v2.y}; + return result; +} + +// Negate vector +RF_API rf_vec2 rf_vec2_negate(rf_vec2 v) +{ + rf_vec2 result = {-v.x, -v.y}; + return result; +} + +// Divide vector by a float value +RF_API rf_vec2 rf_vec2_div(rf_vec2 v, float div) +{ + rf_vec2 result = {v.x / div, v.y / div}; + return result; +} + +// Divide vector by vector +RF_API rf_vec2 rf_vec2_div_v(rf_vec2 v1, rf_vec2 v2) +{ + rf_vec2 result = {v1.x / v2.x, v1.y / v2.y}; + return result; +} + +// Normalize provided vector +RF_API rf_vec2 rf_vec2_normalize(rf_vec2 v) +{ + rf_vec2 result = rf_vec2_div(v, rf_vec2_len(v)); + return result; +} + +// Calculate linear interpolation between two vectors +RF_API rf_vec2 rf_vec2_lerp(rf_vec2 v1, rf_vec2 v2, float amount) +{ + rf_vec2 result = {0}; + + result.x = v1.x + amount * (v2.x - v1.x); + result.y = v1.y + amount * (v2.y - v1.y); + + return result; +} + +// Add two vectors +RF_API rf_vec3 rf_vec3_add(rf_vec3 v1, rf_vec3 v2) +{ + rf_vec3 result = {v1.x + v2.x, v1.y + v2.y, v1.z + v2.z}; + return result; +} + +// Subtract two vectors +RF_API rf_vec3 rf_vec3_sub(rf_vec3 v1, rf_vec3 v2) +{ + rf_vec3 result = {v1.x - v2.x, v1.y - v2.y, v1.z - v2.z}; + return result; +} + +// Multiply vector by scalar +RF_API rf_vec3 rf_vec3_mul(rf_vec3 v, float scalar) +{ + rf_vec3 result = {v.x * scalar, v.y * scalar, v.z * scalar}; + return result; +} + +// Multiply vector by vector +RF_API rf_vec3 rf_vec3_mul_v(rf_vec3 v1, rf_vec3 v2) +{ + rf_vec3 result = {v1.x * v2.x, v1.y * v2.y, v1.z * v2.z}; + return result; +} + +// Calculate two vectors cross product +RF_API rf_vec3 rf_vec3_cross_product(rf_vec3 v1, rf_vec3 v2) +{ + rf_vec3 result = {v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x}; + return result; +} + +// Calculate one vector perpendicular vector +RF_API rf_vec3 rf_vec3_perpendicular(rf_vec3 v) +{ + rf_vec3 result = {0}; + + float min = (float) fabs(v.x); + rf_vec3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabs(v.y) < min) + { + min = (float) fabs(v.y); + rf_vec3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabs(v.z) < min) + { + rf_vec3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + result = rf_vec3_cross_product(v, cardinalAxis); + + return result; +} + +// Calculate vector length +RF_API float rf_vec3_len(rf_vec3 v) +{ + float result = sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + return result; +} + +// Calculate two vectors dot product +RF_API float rf_vec3_dot_product(rf_vec3 v1, rf_vec3 v2) +{ + float result = (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); + return result; +} + +// Calculate distance between two vectors +RF_API float rf_vec3_distance(rf_vec3 v1, rf_vec3 v2) +{ + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + float result = sqrt(dx * dx + dy * dy + dz * dz); + return result; +} + +// Scale provided vector +RF_API rf_vec3 rf_vec3_scale(rf_vec3 v, float scale) +{ + rf_vec3 result = {v.x * scale, v.y * scale, v.z * scale}; + return result; +} + +// Negate provided vector (invert direction) +RF_API rf_vec3 rf_vec3_negate(rf_vec3 v) +{ + rf_vec3 result = {-v.x, -v.y, -v.z}; + return result; +} + +// Divide vector by a float value +RF_API rf_vec3 rf_vec3_div(rf_vec3 v, float div) +{ + rf_vec3 result = {v.x / div, v.y / div, v.z / div}; + return result; +} + +// Divide vector by vector +RF_API rf_vec3 rf_vec3_div_v(rf_vec3 v1, rf_vec3 v2) +{ + rf_vec3 result = {v1.x / v2.x, v1.y / v2.y, v1.z / v2.z}; + return result; +} + +// Normalize provided vector +RF_API rf_vec3 rf_vec3_normalize(rf_vec3 v) +{ + rf_vec3 result = v; + + float length, ilength; + length = rf_vec3_len(v); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f / length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RF_API void rf_vec3_ortho_normalize(rf_vec3 *v1, rf_vec3 *v2) +{ + *v1 = rf_vec3_normalize(*v1); + rf_vec3 vn = rf_vec3_cross_product(*v1, *v2); + vn = rf_vec3_normalize(vn); + *v2 = rf_vec3_cross_product(vn, *v1); +} + +// Transforms a rf_vec3 by a given rf_mat +RF_API rf_vec3 rf_vec3_transform(rf_vec3 v, rf_mat mat) +{ + rf_vec3 result = {0}; + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0 * x + mat.m4 * y + mat.m8 * z + mat.m12; + result.y = mat.m1 * x + mat.m5 * y + mat.m9 * z + mat.m13; + result.z = mat.m2 * x + mat.m6 * y + mat.m10 * z + mat.m14; + + return result; +} + +// rf_transform a vector by quaternion rotation +RF_API rf_vec3 rf_vec3_rotate_by_quaternion(rf_vec3 v, rf_quaternion q) +{ + rf_vec3 result = {0}; + + result.x = v.x * (q.x * q.x + q.w * q.w - q.y * q.y - q.z * q.z) + v.y * (2 * q.x * q.y - 2 * q.w * q.z) + + v.z * (2 * q.x * q.z + 2 * q.w * q.y); + result.y = v.x * (2 * q.w * q.z + 2 * q.x * q.y) + v.y * (q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z) + + v.z * (-2 * q.w * q.x + 2 * q.y * q.z); + result.z = v.x * (-2 * q.w * q.y + 2 * q.x * q.z) + v.y * (2 * q.w * q.x + 2 * q.y * q.z) + + v.z * (q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z); + + return result; +} + +// Calculate linear interpolation between two vectors +RF_API rf_vec3 rf_vec3_lerp(rf_vec3 v1, rf_vec3 v2, float amount) +{ + rf_vec3 result = {0}; + + result.x = v1.x + amount * (v2.x - v1.x); + result.y = v1.y + amount * (v2.y - v1.y); + result.z = v1.z + amount * (v2.z - v1.z); + + return result; +} + +// Calculate reflected vector to normal +RF_API rf_vec3 rf_vec3_reflect(rf_vec3 v, rf_vec3 normal) +{ +// I is the original vector +// N is the normal of the incident plane +// R = I - (2*N*( DotProduct[ I,N] )) + + rf_vec3 result = {0}; + + float dotProduct = rf_vec3_dot_product(v, normal); + + result.x = v.x - (2.0f * normal.x) * dotProduct; + result.y = v.y - (2.0f * normal.y) * dotProduct; + result.z = v.z - (2.0f * normal.z) * dotProduct; + + return result; +} + +// Return min value for each pair of components +RF_API rf_vec3 rf_vec3_min(rf_vec3 v1, rf_vec3 v2) +{ + rf_vec3 result = {0}; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Return max value for each pair of components +RF_API rf_vec3 rf_vec3_max(rf_vec3 v1, rf_vec3 v2) +{ + rf_vec3 result = {0}; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RF_API rf_vec3 rf_vec3_barycenter(rf_vec3 p, rf_vec3 a, rf_vec3 b, rf_vec3 c) +{ +//Vector v0 = b - a, v1 = c - a, v2 = p - a; + + rf_vec3 v0 = rf_vec3_sub(b, a); + rf_vec3 v1 = rf_vec3_sub(c, a); + rf_vec3 v2 = rf_vec3_sub(p, a); + float d00 = rf_vec3_dot_product(v0, v0); + float d01 = rf_vec3_dot_product(v0, v1); + float d11 = rf_vec3_dot_product(v1, v1); + float d20 = rf_vec3_dot_product(v2, v0); + float d21 = rf_vec3_dot_product(v2, v1); + + float denom = d00 * d11 - d01 * d01; + + rf_vec3 result = {0}; + + result.y = (d11 * d20 - d01 * d21) / denom; + result.z = (d00 * d21 - d01 * d20) / denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Compute matrix determinant +RF_API float rf_mat_determinant(rf_mat mat) +{ + float result = 0.0; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + result = a30 * a21 * a12 * a03 - a20 * a31 * a12 * a03 - a30 * a11 * a22 * a03 + a10 * a31 * a22 * a03 + + a20 * a11 * a32 * a03 - a10 * a21 * a32 * a03 - a30 * a21 * a02 * a13 + a20 * a31 * a02 * a13 + + a30 * a01 * a22 * a13 - a00 * a31 * a22 * a13 - a20 * a01 * a32 * a13 + a00 * a21 * a32 * a13 + + a30 * a11 * a02 * a23 - a10 * a31 * a02 * a23 - a30 * a01 * a12 * a23 + a00 * a31 * a12 * a23 + + a10 * a01 * a32 * a23 - a00 * a11 * a32 * a23 - a20 * a11 * a02 * a33 + a10 * a21 * a02 * a33 + + a20 * a01 * a12 * a33 - a00 * a21 * a12 * a33 - a10 * a01 * a22 * a33 + a00 * a11 * a22 * a33; + + return result; +} + +// Returns the trace of the matrix (sum of the values along the diagonal) +RF_API float rf_mat_trace(rf_mat mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + return result; +} + +// Transposes provided matrix +RF_API rf_mat rf_mat_transpose(rf_mat mat) +{ + rf_mat result = {0}; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RF_API rf_mat rf_mat_invert(rf_mat mat) +{ + rf_mat result = {0}; + +// Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00 * a11 - a01 * a10; + float b01 = a00 * a12 - a02 * a10; + float b02 = a00 * a13 - a03 * a10; + float b03 = a01 * a12 - a02 * a11; + float b04 = a01 * a13 - a03 * a11; + float b05 = a02 * a13 - a03 * a12; + float b06 = a20 * a31 - a21 * a30; + float b07 = a20 * a32 - a22 * a30; + float b08 = a20 * a33 - a23 * a30; + float b09 = a21 * a32 - a22 * a31; + float b10 = a21 * a33 - a23 * a31; + float b11 = a22 * a33 - a23 * a32; + +// Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f / (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06); + + result.m0 = (a11 * b11 - a12 * b10 + a13 * b09) * invDet; + result.m1 = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet; + result.m2 = (a31 * b05 - a32 * b04 + a33 * b03) * invDet; + result.m3 = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet; + result.m4 = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet; + result.m5 = (a00 * b11 - a02 * b08 + a03 * b07) * invDet; + result.m6 = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet; + result.m7 = (a20 * b05 - a22 * b02 + a23 * b01) * invDet; + result.m8 = (a10 * b10 - a11 * b08 + a13 * b06) * invDet; + result.m9 = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet; + result.m10 = (a30 * b04 - a31 * b02 + a33 * b00) * invDet; + result.m11 = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet; + result.m12 = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet; + result.m13 = (a00 * b09 - a01 * b07 + a02 * b06) * invDet; + result.m14 = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet; + result.m15 = (a20 * b03 - a21 * b01 + a22 * b00) * invDet; + + return result; +} + +// Normalize provided matrix +RF_API rf_mat rf_mat_normalize(rf_mat mat) +{ + rf_mat result = {0}; + + float det = rf_mat_determinant(mat); + + result.m0 = mat.m0 / det; + result.m1 = mat.m1 / det; + result.m2 = mat.m2 / det; + result.m3 = mat.m3 / det; + result.m4 = mat.m4 / det; + result.m5 = mat.m5 / det; + result.m6 = mat.m6 / det; + result.m7 = mat.m7 / det; + result.m8 = mat.m8 / det; + result.m9 = mat.m9 / det; + result.m10 = mat.m10 / det; + result.m11 = mat.m11 / det; + result.m12 = mat.m12 / det; + result.m13 = mat.m13 / det; + result.m14 = mat.m14 / det; + result.m15 = mat.m15 / det; + + return result; +} + +// Returns identity matrix +RF_API rf_mat rf_mat_identity(void) +{ + rf_mat result = {1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + + return result; +} + +// Add two matrices +RF_API rf_mat rf_mat_add(rf_mat left, rf_mat right) +{ + rf_mat result = rf_mat_identity(); + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RF_API rf_mat rf_mat_sub(rf_mat left, rf_mat right) +{ + rf_mat result = rf_mat_identity(); + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Returns translation matrix +RF_API rf_mat rf_mat_translate(float x, float y, float z) +{ + rf_mat result = {1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f}; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RF_API rf_mat rf_mat_rotate(rf_vec3 axis, float angle) +{ + rf_mat result = {0}; + + float x = axis.x, y = axis.y, z = axis.z; + + float length = sqrt(x * x + y * y + z * z); + + if ((length != 1.0f) && (length != 0.0f)) + { + length = 1.0f / length; + x *= length; + y *= length; + z *= length; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x * x * t + cosres; + result.m1 = y * x * t + z * sinres; + result.m2 = z * x * t - y * sinres; + result.m3 = 0.0f; + + result.m4 = x * y * t - z * sinres; + result.m5 = y * y * t + cosres; + result.m6 = z * y * t + x * sinres; + result.m7 = 0.0f; + + result.m8 = x * z * t + y * sinres; + result.m9 = y * z * t - x * sinres; + result.m10 = z * z * t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Returns xyz-rotation matrix (angles in radians) +RF_API rf_mat rf_mat_rotate_xyz(rf_vec3 ang) +{ + rf_mat result = rf_mat_identity(); + + float cosz = cosf(-ang.z); + float sinz = sinf(-ang.z); + float cosy = cosf(-ang.y); + float siny = sinf(-ang.y); + float cosx = cosf(-ang.x); + float sinx = sinf(-ang.x); + + result.m0 = cosz * cosy; + result.m4 = (cosz * siny * sinx) - (sinz * cosx); + result.m8 = (cosz * siny * cosx) + (sinz * sinx); + + result.m1 = sinz * cosy; + result.m5 = (sinz * siny * sinx) + (cosz * cosx); + result.m9 = (sinz * siny * cosx) - (cosz * sinx); + + result.m2 = -siny; + result.m6 = cosy * sinx; + result.m10 = cosy * cosx; + + return result; +} + +// Returns x-rotation matrix (angle in radians) +RF_API rf_mat rf_mat_rotate_x(float angle) +{ + rf_mat result = rf_mat_identity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = -sinres; + result.m9 = sinres; + result.m10 = cosres; + + return result; +} + +// Returns y-rotation matrix (angle in radians) +RF_API rf_mat rf_mat_rotate_y(float angle) +{ + rf_mat result = rf_mat_identity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = sinres; + result.m8 = -sinres; + result.m10 = cosres; + + return result; +} + +// Returns z-rotation matrix (angle in radians) +RF_API rf_mat rf_mat_rotate_z(float angle) +{ + rf_mat result = rf_mat_identity(); + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = -sinres; + result.m4 = sinres; + result.m5 = cosres; + + return result; +} + +// Returns scaling matrix +RF_API rf_mat rf_mat_scale(float x, float y, float z) +{ + rf_mat result = {x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + + return result; +} + +// Returns two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RF_API rf_mat rf_mat_mul(rf_mat left, rf_mat right) +{ + rf_mat result = {0}; + + result.m0 = left.m0 * right.m0 + left.m1 * right.m4 + left.m2 * right.m8 + left.m3 * right.m12; + result.m1 = left.m0 * right.m1 + left.m1 * right.m5 + left.m2 * right.m9 + left.m3 * right.m13; + result.m2 = left.m0 * right.m2 + left.m1 * right.m6 + left.m2 * right.m10 + left.m3 * right.m14; + result.m3 = left.m0 * right.m3 + left.m1 * right.m7 + left.m2 * right.m11 + left.m3 * right.m15; + result.m4 = left.m4 * right.m0 + left.m5 * right.m4 + left.m6 * right.m8 + left.m7 * right.m12; + result.m5 = left.m4 * right.m1 + left.m5 * right.m5 + left.m6 * right.m9 + left.m7 * right.m13; + result.m6 = left.m4 * right.m2 + left.m5 * right.m6 + left.m6 * right.m10 + left.m7 * right.m14; + result.m7 = left.m4 * right.m3 + left.m5 * right.m7 + left.m6 * right.m11 + left.m7 * right.m15; + result.m8 = left.m8 * right.m0 + left.m9 * right.m4 + left.m10 * right.m8 + left.m11 * right.m12; + result.m9 = left.m8 * right.m1 + left.m9 * right.m5 + left.m10 * right.m9 + left.m11 * right.m13; + result.m10 = left.m8 * right.m2 + left.m9 * right.m6 + left.m10 * right.m10 + left.m11 * right.m14; + result.m11 = left.m8 * right.m3 + left.m9 * right.m7 + left.m10 * right.m11 + left.m11 * right.m15; + result.m12 = left.m12 * right.m0 + left.m13 * right.m4 + left.m14 * right.m8 + left.m15 * right.m12; + result.m13 = left.m12 * right.m1 + left.m13 * right.m5 + left.m14 * right.m9 + left.m15 * right.m13; + result.m14 = left.m12 * right.m2 + left.m13 * right.m6 + left.m14 * right.m10 + left.m15 * right.m14; + result.m15 = left.m12 * right.m3 + left.m13 * right.m7 + left.m14 * right.m11 + left.m15 * right.m15; + + return result; +} + +// Returns perspective GL_PROJECTION matrix +RF_API rf_mat rf_mat_frustum(double left, double right, double bottom, double top, double near_val, double far_val) +{ + rf_mat result = {0}; + + float rl = (float) (right - left); + float tb = (float) (top - bottom); + float fn = (float) (far_val - near_val); + + result.m0 = ((float) near_val * 2.0f) / rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float) near_val * 2.0f) / tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float) right + (float) left) / rl; + result.m9 = ((float) top + (float) bottom) / tb; + result.m10 = -((float) far_val + (float) near_val) / fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float) far_val * (float) near_val * 2.0f) / fn; + result.m15 = 0.0f; + + return result; +} + +// Returns perspective GL_PROJECTION matrix +// NOTE: Angle should be provided in radians +RF_API rf_mat rf_mat_perspective(double fovy, double aspect, double near_val, double far_val) +{ + double top = near_val * tan(fovy * 0.5); + double right = top * aspect; + rf_mat result = rf_mat_frustum(-right, right, -top, top, near_val, far_val); + + return result; +} + +// Returns orthographic GL_PROJECTION matrix +RF_API rf_mat rf_mat_ortho(double left, double right, double bottom, double top, double near_val, double far_val) +{ + rf_mat result = {0}; + + float rl = (float) (right - left); + float tb = (float) (top - bottom); + float fn = (float) (far_val - near_val); + + result.m0 = 2.0f / rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f / tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f / fn; + result.m11 = 0.0f; + result.m12 = -((float) left + (float) right) / rl; + result.m13 = -((float) top + (float) bottom) / tb; + result.m14 = -((float) far_val + (float) near_val) / fn; + result.m15 = 1.0f; + + return result; +} + +// Returns camera look-at matrix (view matrix) +RF_API rf_mat rf_mat_look_at(rf_vec3 eye, rf_vec3 target, rf_vec3 up) +{ + rf_mat result = {0}; + + rf_vec3 z = rf_vec3_sub(eye, target); + z = rf_vec3_normalize(z); + rf_vec3 x = rf_vec3_cross_product(up, z); + x = rf_vec3_normalize(x); + rf_vec3 y = rf_vec3_cross_product(z, x); + y = rf_vec3_normalize(y); + + result.m0 = x.x; + result.m1 = x.y; + result.m2 = x.z; + result.m3 = 0.0f; + result.m4 = y.x; + result.m5 = y.y; + result.m6 = y.z; + result.m7 = 0.0f; + result.m8 = z.x; + result.m9 = z.y; + result.m10 = z.z; + result.m11 = 0.0f; + result.m12 = eye.x; + result.m13 = eye.y; + result.m14 = eye.z; + result.m15 = 1.0f; + + result = rf_mat_invert(result); + + return result; +} + +RF_API rf_float16 rf_mat_to_float16(rf_mat mat) +{ + rf_float16 buffer = {0}; + + buffer.v[0] = mat.m0; + buffer.v[1] = mat.m1; + buffer.v[2] = mat.m2; + buffer.v[3] = mat.m3; + buffer.v[4] = mat.m4; + buffer.v[5] = mat.m5; + buffer.v[6] = mat.m6; + buffer.v[7] = mat.m7; + buffer.v[8] = mat.m8; + buffer.v[9] = mat.m9; + buffer.v[10] = mat.m10; + buffer.v[11] = mat.m11; + buffer.v[12] = mat.m12; + buffer.v[13] = mat.m13; + buffer.v[14] = mat.m14; + buffer.v[15] = mat.m15; + + return buffer; +} + +// Returns identity quaternion +RF_API rf_quaternion rf_quaternion_identity(void) +{ + rf_quaternion result = {0.0f, 0.0f, 0.0f, 1.0f}; + return result; +} + +// Computes the length of a quaternion +RF_API float rf_quaternion_len(rf_quaternion q) +{ + float result = (float) sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + return result; +} + +// Normalize provided quaternion +RF_API rf_quaternion rf_quaternion_normalize(rf_quaternion q) +{ + rf_quaternion result = {0}; + + float length, ilength; + length = rf_quaternion_len(q); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f / length; + + result.x = q.x * ilength; + result.y = q.y * ilength; + result.z = q.z * ilength; + result.w = q.w * ilength; + + return result; +} + +// Invert provided quaternion +RF_API rf_quaternion rf_quaternion_invert(rf_quaternion q) +{ + rf_quaternion result = q; + float length = rf_quaternion_len(q); + float lengthSq = length * length; + + if (lengthSq != 0.0) + { + float i = 1.0f / lengthSq; + + result.x *= -i; + result.y *= -i; + result.z *= -i; + result.w *= i; + } + + return result; +} + +// Calculate two quaternion multiplication +RF_API rf_quaternion rf_quaternion_mul(rf_quaternion q1, rf_quaternion q2) +{ + rf_quaternion result = {0}; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + result.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + result.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + result.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; + + return result; +} + +// Calculate linear interpolation between two quaternions +RF_API rf_quaternion rf_quaternion_lerp(rf_quaternion q1, rf_quaternion q2, float amount) +{ + rf_quaternion result = {0}; + + result.x = q1.x + amount * (q2.x - q1.x); + result.y = q1.y + amount * (q2.y - q1.y); + result.z = q1.z + amount * (q2.z - q1.z); + result.w = q1.w + amount * (q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RF_API rf_quaternion rf_quaternion_nlerp(rf_quaternion q1, rf_quaternion q2, float amount) +{ + rf_quaternion result = rf_quaternion_lerp(q1, q2, amount); + result = rf_quaternion_normalize(result); + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RF_API rf_quaternion rf_quaternion_slerp(rf_quaternion q1, rf_quaternion q2, float amount) +{ + rf_quaternion result = {0}; + + float cosHalfTheta = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; + + if (fabs(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = rf_quaternion_nlerp(q1, q2, amount); + else + { + float halfTheta = (float) acos(cosHalfTheta); + float sinHalfTheta = (float) sqrt(1.0f - cosHalfTheta * cosHalfTheta); + + if (fabs(sinHalfTheta) < 0.001f) + { + result.x = (q1.x * 0.5f + q2.x * 0.5f); + result.y = (q1.y * 0.5f + q2.y * 0.5f); + result.z = (q1.z * 0.5f + q2.z * 0.5f); + result.w = (q1.w * 0.5f + q2.w * 0.5f); + } else + { + float ratioA = sinf((1 - amount) * halfTheta) / sinHalfTheta; + float ratioB = sinf(amount * halfTheta) / sinHalfTheta; + + result.x = (q1.x * ratioA + q2.x * ratioB); + result.y = (q1.y * ratioA + q2.y * ratioB); + result.z = (q1.z * ratioA + q2.z * ratioB); + result.w = (q1.w * ratioA + q2.w * ratioB); + } + } + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RF_API rf_quaternion rf_quaternion_from_vector3_to_vector3(rf_vec3 from, rf_vec3 to) +{ + rf_quaternion result = {0}; + + float cos2Theta = rf_vec3_dot_product(from, to); + rf_vec3 cross = rf_vec3_cross_product(from, to); + + result.x = cross.x; + result.y = cross.y; + result.z = cross.y; + result.w = 1.0f + cos2Theta; // NOTE: Added QuaternioIdentity() + +// Normalize to essentially nlerp the original and identity to 0.5 + result = rf_quaternion_normalize(result); + +// Above lines are equivalent to: +//rf_quaternion result = rf_quaternion_nlerp(q, rf_quaternion_identity(), 0.5f); + + return result; +} + +// Returns a quaternion for a given rotation matrix +RF_API rf_quaternion rf_quaternion_from_matrix(rf_mat mat) +{ + rf_quaternion result = {0}; + + float trace = rf_mat_trace(mat); + + if (trace > 0.0f) + { + float s = (float) sqrt(trace + 1) * 2.0f; + float invS = 1.0f / s; + + result.w = s * 0.25f; + result.x = (mat.m6 - mat.m9) * invS; + result.y = (mat.m8 - mat.m2) * invS; + result.z = (mat.m1 - mat.m4) * invS; + } else + { + float m00 = mat.m0, m11 = mat.m5, m22 = mat.m10; + + if (m00 > m11 && m00 > m22) + { + float s = (float) sqrt(1.0f + m00 - m11 - m22) * 2.0f; + float invS = 1.0f / s; + + result.w = (mat.m6 - mat.m9) * invS; + result.x = s * 0.25f; + result.y = (mat.m4 + mat.m1) * invS; + result.z = (mat.m8 + mat.m2) * invS; + } else if (m11 > m22) + { + float s = (float) sqrt(1.0f + m11 - m00 - m22) * 2.0f; + float invS = 1.0f / s; + + result.w = (mat.m8 - mat.m2) * invS; + result.x = (mat.m4 + mat.m1) * invS; + result.y = s * 0.25f; + result.z = (mat.m9 + mat.m6) * invS; + } else + { + float s = (float) sqrt(1.0f + m22 - m00 - m11) * 2.0f; + float invS = 1.0f / s; + + result.w = (mat.m1 - mat.m4) * invS; + result.x = (mat.m8 + mat.m2) * invS; + result.y = (mat.m9 + mat.m6) * invS; + result.z = s * 0.25f; + } + } + + return result; +} + +// Returns a matrix for a given quaternion +RF_API rf_mat rf_quaternion_to_matrix(rf_quaternion q) +{ + rf_mat result = {0}; + + float x = q.x, y = q.y, z = q.z, w = q.w; + + float x2 = x + x; + float y2 = y + y; + float z2 = z + z; + + float length = rf_quaternion_len(q); + float lengthSquared = length * length; + + float xx = x * x2 / lengthSquared; + float xy = x * y2 / lengthSquared; + float xz = x * z2 / lengthSquared; + + float yy = y * y2 / lengthSquared; + float yz = y * z2 / lengthSquared; + float zz = z * z2 / lengthSquared; + + float wx = w * x2 / lengthSquared; + float wy = w * y2 / lengthSquared; + float wz = w * z2 / lengthSquared; + + result.m0 = 1.0f - (yy + zz); + result.m1 = xy - wz; + result.m2 = xz + wy; + result.m3 = 0.0f; + result.m4 = xy + wz; + result.m5 = 1.0f - (xx + zz); + result.m6 = yz - wx; + result.m7 = 0.0f; + result.m8 = xz - wy; + result.m9 = yz + wx; + result.m10 = 1.0f - (xx + yy); + result.m11 = 0.0f; + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Returns rotation quaternion for an angle and axis +// NOTE: angle must be provided in radians +RF_API rf_quaternion rf_quaternion_from_axis_angle(rf_vec3 axis, float angle) +{ + rf_quaternion result = {0.0f, 0.0f, 0.0f, 1.0f}; + + if (rf_vec3_len(axis) != 0.0f) + + angle *= 0.5f; + + axis = rf_vec3_normalize(axis); + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x * sinres; + result.y = axis.y * sinres; + result.z = axis.z * sinres; + result.w = cosres; + + result = rf_quaternion_normalize(result); + + return result; +} + +// Returns the rotation angle and axis for a given quaternion +RF_API void rf_quaternion_to_axis_angle(rf_quaternion q, rf_vec3 *outAxis, float *outAngle) +{ + if (fabs(q.w) > 1.0f) q = rf_quaternion_normalize(q); + + rf_vec3 resAxis = {0.0f, 0.0f, 0.0f}; + float resAngle = 0.0f; + + resAngle = 2.0f * (float) acos(q.w); + float den = (float) sqrt(1.0f - q.w * q.w); + + if (den > 0.0001f) + { + resAxis.x = q.x / den; + resAxis.y = q.y / den; + resAxis.z = q.z / den; + } else + { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Returns he quaternion equivalent to Euler angles +RF_API rf_quaternion rf_quaternion_from_euler(float roll, float pitch, float yaw) +{ + rf_quaternion q = {0}; + + float x0 = cosf(roll * 0.5f); + float x1 = sinf(roll * 0.5f); + float y0 = cosf(pitch * 0.5f); + float y1 = sinf(pitch * 0.5f); + float z0 = cosf(yaw * 0.5f); + float z1 = sinf(yaw * 0.5f); + + q.x = x1 * y0 * z0 - x0 * y1 * z1; + q.y = x0 * y1 * z0 + x1 * y0 * z1; + q.z = x0 * y0 * z1 - x1 * y1 * z0; + q.w = x0 * y0 * z0 + x1 * y1 * z1; + + return q; +} + +// Return the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a rf_vec3 struct in degrees +RF_API rf_vec3 rf_quaternion_to_euler(rf_quaternion q) +{ + rf_vec3 result = {0}; + +// roll (x-axis rotation) + float x0 = 2.0f * (q.w * q.x + q.y * q.z); + float x1 = 1.0f - 2.0f * (q.x * q.x + q.y * q.y); + result.x = atan2f(x0, x1) * RF_RAD2DEG; + +// pitch (y-axis rotation) + float y0 = 2.0f * (q.w * q.y - q.z * q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0) * RF_RAD2DEG; + +// yaw (z-axis rotation) + float z0 = 2.0f * (q.w * q.z + q.x * q.y); + float z1 = 1.0f - 2.0f * (q.y * q.y + q.z * q.z); + result.z = atan2f(z0, z1) * RF_RAD2DEG; + + return result; +} + +// rf_transform a quaternion given a transformation matrix +RF_API rf_quaternion rf_quaternion_transform(rf_quaternion q, rf_mat mat) +{ + rf_quaternion result = {0}; + + result.x = mat.m0 * q.x + mat.m4 * q.y + mat.m8 * q.z + mat.m12 * q.w; + result.y = mat.m1 * q.x + mat.m5 * q.y + mat.m9 * q.z + mat.m13 * q.w; + result.z = mat.m2 * q.x + mat.m6 * q.y + mat.m10 * q.z + mat.m14 * q.w; + result.w = mat.m3 * q.x + mat.m7 * q.y + mat.m11 * q.z + mat.m15 * q.w; + + return result; +} + +#pragma endregion + +#pragma region collision detection + +// Check if point is inside rectangle +bool rf_check_collision_point_rec(rf_vec2 point, rf_rec rec) +{ + bool collision = 0; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && (point.y >= rec.y) && + (point.y <= (rec.y + rec.height))) + collision = 1; + + return collision; +} + +// Check if point is inside circle +bool rf_check_collision_point_circle(rf_vec2 point, rf_vec2 center, float radius) +{ + return rf_check_collision_circles(point, 0, center, radius); +} + +// Check if point is inside a triangle defined by three points (p1, p2, p3) +bool rf_check_collision_point_triangle(rf_vec2 point, rf_vec2 p1, rf_vec2 p2, rf_vec2 p3) +{ + bool collision = 0; + + float alpha = ((p2.y - p3.y) * (point.x - p3.x) + (p3.x - p2.x) * (point.y - p3.y)) / + ((p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y)); + + float beta = ((p3.y - p1.y) * (point.x - p3.x) + (p1.x - p3.x) * (point.y - p3.y)) / + ((p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y)); + + float gamma = 1.0f - alpha - beta; + + if ((alpha > 0) && (beta > 0) & (gamma > 0)) collision = 1; + + return collision; +} + +// Check collision between two rectangles +bool rf_check_collision_recs(rf_rec rec1, rf_rec rec2) +{ + bool collision = false; + + if ((rec1.x < (rec2.x + rec2.width) && (rec1.x + rec1.width) > rec2.x) && + (rec1.y < (rec2.y + rec2.height) && (rec1.y + rec1.height) > rec2.y)) + collision = true; + + return collision; +} + +// Check collision between two circles +bool rf_check_collision_circles(rf_vec2 center1, float radius1, rf_vec2 center2, float radius2) +{ + bool collision = false; + + float dx = center2.x - center1.x; // X distance between centers + float dy = center2.y - center1.y; // Y distance between centers + + float distance = sqrt(dx * dx + dy * dy); // Distance between centers + + if (distance <= (radius1 + radius2)) collision = true; + + return collision; +} + +// Check collision between circle and rectangle +// NOTE: Reviewed version to take into account corner limit case +bool rf_check_collision_circle_rec(rf_vec2 center, float radius, rf_rec rec) +{ + int recCenterX = (int) (rec.x + rec.width / 2.0f); + int recCenterY = (int) (rec.y + rec.height / 2.0f); + + float dx = (float) fabs(center.x - recCenterX); + float dy = (float) fabs(center.y - recCenterY); + + if (dx > (rec.width / 2.0f + radius)) + { return 0; } + if (dy > (rec.height / 2.0f + radius)) + { return 0; } + + if (dx <= (rec.width / 2.0f)) + { return 1; } + if (dy <= (rec.height / 2.0f)) + { return 1; } + + float cornerDistanceSq = (dx - rec.width / 2.0f) * (dx - rec.width / 2.0f) + + (dy - rec.height / 2.0f) * (dy - rec.height / 2.0f); + + return (cornerDistanceSq <= (radius * radius)); +} + +// Get collision rectangle for two rectangles collision +rf_rec rf_get_collision_rec(rf_rec rec1, rf_rec rec2) +{ + rf_rec retRec = {0, 0, 0, 0}; + + if (rf_check_collision_recs(rec1, rec2)) + { + float dxx = (float) fabs(rec1.x - rec2.x); + float dyy = (float) fabs(rec1.y - rec2.y); + + if (rec1.x <= rec2.x) + { + if (rec1.y <= rec2.y) + { + retRec.x = rec2.x; + retRec.y = rec2.y; + retRec.width = rec1.width - dxx; + retRec.height = rec1.height - dyy; + } else + { + retRec.x = rec2.x; + retRec.y = rec1.y; + retRec.width = rec1.width - dxx; + retRec.height = rec2.height - dyy; + } + } else + { + if (rec1.y <= rec2.y) + { + retRec.x = rec1.x; + retRec.y = rec2.y; + retRec.width = rec2.width - dxx; + retRec.height = rec1.height - dyy; + } else + { + retRec.x = rec1.x; + retRec.y = rec1.y; + retRec.width = rec2.width - dxx; + retRec.height = rec2.height - dyy; + } + } + + if (rec1.width > rec2.width) + { + if (retRec.width >= rec2.width) retRec.width = rec2.width; + } else + { + if (retRec.width >= rec1.width) retRec.width = rec1.width; + } + + if (rec1.height > rec2.height) + { + if (retRec.height >= rec2.height) retRec.height = rec2.height; + } else + { + if (retRec.height >= rec1.height) retRec.height = rec1.height; + } + } + + return retRec; +} + +// Detect collision between two spheres +RF_API bool rf_check_collision_spheres(rf_vec3 center_a, float radius_a, rf_vec3 center_b, float radius_b) +{ + bool collision = false; + +// Simple way to check for collision, just checking distance between two points +// Unfortunately, sqrtf() is a costly operation, so we avoid it with following solution +/* +float dx = centerA.x - centerB.x; // X distance between centers +float dy = centerA.y - centerB.y; // Y distance between centers +float dz = centerA.z - centerB.z; // Z distance between centers + +float distance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance between centers + +if (distance <= (radiusA + radiusB)) collision = true; +*/ +// Check for distances squared to avoid sqrtf() + if (rf_vec3_dot_product(rf_vec3_sub(center_b, center_a), rf_vec3_sub(center_b, center_a)) <= + (radius_a + radius_b) * (radius_a + radius_b)) + collision = true; + + return collision; +} + +// Detect collision between two boxes. Note: Boxes are defined by two points minimum and maximum +RF_API bool rf_check_collision_boxes(rf_bounding_box box1, rf_bounding_box box2) +{ + bool collision = true; + + if ((box1.max.x >= box2.min.x) && (box1.min.x <= box2.max.x)) + { + if ((box1.max.y < box2.min.y) || (box1.min.y > box2.max.y)) collision = false; + if ((box1.max.z < box2.min.z) || (box1.min.z > box2.max.z)) collision = false; + } else collision = false; + + return collision; +} + +// Detect collision between box and sphere +RF_API bool rf_check_collision_box_sphere(rf_bounding_box box, rf_vec3 center, float radius) +{ + bool collision = false; + + float dmin = 0; + + if (center.x < box.min.x) dmin += powf(center.x - box.min.x, 2); + else if (center.x > box.max.x) dmin += powf(center.x - box.max.x, 2); + + if (center.y < box.min.y) dmin += powf(center.y - box.min.y, 2); + else if (center.y > box.max.y) dmin += powf(center.y - box.max.y, 2); + + if (center.z < box.min.z) dmin += powf(center.z - box.min.z, 2); + else if (center.z > box.max.z) dmin += powf(center.z - box.max.z, 2); + + if (dmin <= (radius * radius)) collision = true; + + return collision; +} + +// Detect collision between ray and sphere +RF_API bool rf_check_collision_ray_sphere(rf_ray ray, rf_vec3 center, float radius) +{ + bool collision = false; + + rf_vec3 ray_sphere_pos = rf_vec3_sub(center, ray.position); + float distance = rf_vec3_len(ray_sphere_pos); + float vector = rf_vec3_dot_product(ray_sphere_pos, ray.direction); + float d = radius * radius - (distance * distance - vector * vector); + + if (d >= 0.0f) collision = true; + + return collision; +} + +// Detect collision between ray and sphere with extended parameters and collision point detection +RF_API bool rf_check_collision_ray_sphere_ex(rf_ray ray, rf_vec3 center, float radius, rf_vec3 *collision_point) +{ + bool collision = false; + + rf_vec3 ray_sphere_pos = rf_vec3_sub(center, ray.position); + float distance = rf_vec3_len(ray_sphere_pos); + float vector = rf_vec3_dot_product(ray_sphere_pos, ray.direction); + float d = radius * radius - (distance * distance - vector * vector); + + if (d >= 0.0f) collision = true; + +// Check if ray origin is inside the sphere to calculate the correct collision point + float collision_distance = 0; + + if (distance < radius) collision_distance = vector + sqrtf(d); + else collision_distance = vector - sqrtf(d); + +// Calculate collision point + rf_vec3 c_point = rf_vec3_add(ray.position, rf_vec3_scale(ray.direction, collision_distance)); + + collision_point->x = c_point.x; + collision_point->y = c_point.y; + collision_point->z = c_point.z; + + return collision; +} + +// Detect collision between ray and bounding box +RF_API bool rf_check_collision_ray_box(rf_ray ray, rf_bounding_box box) +{ + bool collision = false; + + float t[8]; + t[0] = (box.min.x - ray.position.x) / ray.direction.x; + t[1] = (box.max.x - ray.position.x) / ray.direction.x; + t[2] = (box.min.y - ray.position.y) / ray.direction.y; + t[3] = (box.max.y - ray.position.y) / ray.direction.y; + t[4] = (box.min.z - ray.position.z) / ray.direction.z; + t[5] = (box.max.z - ray.position.z) / ray.direction.z; + t[6] = (float) fmax(fmax(fmin(t[0], t[1]), fmin(t[2], t[3])), fmin(t[4], t[5])); + t[7] = (float) fmin(fmin(fmax(t[0], t[1]), fmax(t[2], t[3])), fmax(t[4], t[5])); + + collision = !(t[7] < 0 || t[6] > t[7]); + + return collision; +} + +// Get collision info between ray and model +RF_API rf_ray_hit_info rf_collision_ray_model(rf_ray ray, rf_model model) +{ + rf_ray_hit_info result = {0}; + + for (rf_int m = 0; m < model.mesh_count; m++) + { +// Check if meshhas vertex data on CPU for testing + if (model.meshes[m].vertices != NULL) + { +// model->mesh.triangle_count may not be set, vertex_count is more reliable + int triangle_count = model.meshes[m].vertex_count / 3; + +// Test against all triangles in mesh + for (rf_int i = 0; i < triangle_count; i++) + { + rf_vec3 a, b, c; + rf_vec3 *vertdata = (rf_vec3 *) model.meshes[m].vertices; + + if (model.meshes[m].indices) + { + a = vertdata[model.meshes[m].indices[i * 3 + 0]]; + b = vertdata[model.meshes[m].indices[i * 3 + 1]]; + c = vertdata[model.meshes[m].indices[i * 3 + 2]]; + } else + { + a = vertdata[i * 3 + 0]; + b = vertdata[i * 3 + 1]; + c = vertdata[i * 3 + 2]; + } + + a = rf_vec3_transform(a, model.transform); + b = rf_vec3_transform(b, model.transform); + c = rf_vec3_transform(c, model.transform); + + rf_ray_hit_info tri_hit_info = rf_collision_ray_triangle(ray, a, b, c); + + if (tri_hit_info.hit) + { +// Save the closest hit triangle + if ((!result.hit) || (result.distance > tri_hit_info.distance)) result = tri_hit_info; + } + } + } + } + + return result; +} + +// Get collision info between ray and triangle. Note: Based on https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm +RF_API rf_ray_hit_info rf_collision_ray_triangle(rf_ray ray, rf_vec3 p1, rf_vec3 p2, rf_vec3 p3) +{ +#define rf_epsilon 0.000001 // Just a small number + + rf_vec3 edge1, edge2; + rf_vec3 p, q, tv; + float det, inv_det, u, v, t; + rf_ray_hit_info result = {0}; + +// Find vectors for two edges sharing V1 + edge1 = rf_vec3_sub(p2, p1); + edge2 = rf_vec3_sub(p3, p1); + +// Begin calculating determinant - also used to calculate u parameter + p = rf_vec3_cross_product(ray.direction, edge2); + +// If determinant is near zero, ray lies in plane of triangle or ray is parallel to plane of triangle + det = rf_vec3_dot_product(edge1, p); + +// Avoid culling! + if ((det > -rf_epsilon) && (det < rf_epsilon)) return result; + + inv_det = 1.0f / det; + +// Calculate distance from V1 to ray origin + tv = rf_vec3_sub(ray.position, p1); + +// Calculate u parameter and test bound + u = rf_vec3_dot_product(tv, p) * inv_det; + +// The intersection lies outside of the triangle + if ((u < 0.0f) || (u > 1.0f)) return result; + +// Prepare to test v parameter + q = rf_vec3_cross_product(tv, edge1); + +// Calculate V parameter and test bound + v = rf_vec3_dot_product(ray.direction, q) * inv_det; + +// The intersection lies outside of the triangle + if ((v < 0.0f) || ((u + v) > 1.0f)) return result; + + t = rf_vec3_dot_product(edge2, q) * inv_det; + + if (t > rf_epsilon) + { +// rf_ray hit, get hit point and normal + result.hit = true; + result.distance = t; + result.hit = true; + result.normal = rf_vec3_normalize(rf_vec3_cross_product(edge1, edge2)); + result.position = rf_vec3_add(ray.position, rf_vec3_scale(ray.direction, t)); + } + + return result; +} + +// Get collision info between ray and ground plane (Y-normal plane) +RF_API rf_ray_hit_info rf_collision_ray_ground(rf_ray ray, float ground_height) +{ +#define rf_epsilon 0.000001 // Just a small number + + rf_ray_hit_info result = {0}; + + if (fabs(ray.direction.y) > rf_epsilon) + { + float distance = (ray.position.y - ground_height) / -ray.direction.y; + + if (distance >= 0.0) + { + result.hit = true; + result.distance = distance; + result.normal = (rf_vec3) {0.0, 1.0, 0.0}; + result.position = rf_vec3_add(ray.position, rf_vec3_scale(ray.direction, distance)); + } + } + + return result; +} + +#pragma endregion +/*** End of inlined file: rayfork-math.c ***/ + + +/*** Start of inlined file: rayfork-camera.c ***/ +// Get world coordinates from screen coordinates +RF_API rf_vec3 rf_unproject(rf_vec3 source, rf_mat proj, rf_mat view) +{ + rf_vec3 result = {0.0f, 0.0f, 0.0f}; + + // Calculate unproject matrix (multiply view patrix by rf_ctx->gl_ctx.projection matrix) and invert it + rf_mat mat_viewProj = rf_mat_mul(view, proj); + mat_viewProj = rf_mat_invert(mat_viewProj); + + // Create quaternion from source point + rf_quaternion quat = {source.x, source.y, source.z, 1.0f}; + + // Multiply quat point by unproject matrix + quat = rf_quaternion_transform(quat, mat_viewProj); + + // Normalized world points in vectors + result.x = quat.x / quat.w; + result.y = quat.y / quat.w; + result.z = quat.z / quat.w; + + return result; +} + +// Returns a ray trace from mouse position +RF_API rf_ray rf_get_mouse_ray(rf_sizei screen_size, rf_vec2 mouse_position, rf_camera3d camera) +{ + rf_ray ray = {0}; + + // Calculate normalized device coordinates + // NOTE: y value is negative + float x = (2.0f * mouse_position.x) / (float) screen_size.width - 1.0f; + float y = 1.0f - (2.0f * mouse_position.y) / (float) screen_size.height; + float z = 1.0f; + + // Store values in a vector + rf_vec3 device_coords = { x, y, z }; + + // Calculate view matrix from camera look at + rf_mat mat_view = rf_mat_look_at(camera.position, camera.target, camera.up); + + rf_mat mat_proj = rf_mat_identity(); + + if (camera.type == RF_CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + mat_proj = rf_mat_perspective(camera.fovy * RF_DEG2RAD, ((double) screen_size.width / (double) screen_size.height), 0.01, 1000.0); + } + else if (camera.type == RF_CAMERA_ORTHOGRAPHIC) + { + float aspect = (float) screen_size.width / (float) screen_size.height; + double top = camera.fovy / 2.0; + double right = top * aspect; + + // Calculate projection matrix from orthographic + mat_proj = rf_mat_ortho(-right, right, -top, top, 0.01, 1000.0); + } + + // Unproject far/near points + rf_vec3 near_point = rf_unproject((rf_vec3) {device_coords.x, device_coords.y, 0.0f}, mat_proj, mat_view); + rf_vec3 far_point = rf_unproject((rf_vec3) {device_coords.x, device_coords.y, 1.0f}, mat_proj, mat_view); + + // Unproject the mouse cursor in the near plane. + // We need this as the source position because orthographic projects, compared to perspect doesn't have a + // convergence point, meaning that the "eye" of the camera is more like a plane than a point. + rf_vec3 camera_plane_pointer_pos = rf_unproject((rf_vec3) {device_coords.x, device_coords.y, -1.0f}, mat_proj, + mat_view); + + // Calculate normalized direction vector + rf_vec3 direction = rf_vec3_normalize(rf_vec3_sub(far_point, near_point)); + + if (camera.type == RF_CAMERA_PERSPECTIVE) + { + ray.position = camera.position; + } + else if (camera.type == RF_CAMERA_ORTHOGRAPHIC) + { + ray.position = camera_plane_pointer_pos; + } + + // Apply calculated vectors to ray + ray.direction = direction; + + return ray; +} + +// Get transform matrix for camera +RF_API rf_mat rf_get_camera_matrix(rf_camera3d camera) +{ + return rf_mat_look_at(camera.position, camera.target, camera.up); +} + +// Returns camera 2d transform matrix +RF_API rf_mat rf_get_camera_matrix2d(rf_camera2d camera) +{ + rf_mat mat_transform = {0}; +// The camera in world-space is set by +// 1. Move it to target +// 2. Rotate by -rotation and scale by (1/zoom) +// When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), +// not for the camera getting bigger, hence the invert. Same deal with rotation. +// 3. Move it by (-offset); +// Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) +// we need to do it into opposite direction (inverse transform) + +// Having camera transform in world-space, inverse of it gives the rf_gfxobal_model_view transform. +// Since (A*B*C)' = C'*B'*A', the rf_gfxobal_model_view is +// 1. Move to offset +// 2. Rotate and Scale +// 3. Move by -target + rf_mat mat_origin = rf_mat_translate(-camera.target.x, -camera.target.y, 0.0f); + rf_mat mat_rotation = rf_mat_rotate((rf_vec3) {0.0f, 0.0f, 1.0f}, camera.rotation * RF_DEG2RAD); + rf_mat mat_scale = rf_mat_scale(camera.zoom, camera.zoom, 1.0f); + rf_mat mat_translation = rf_mat_translate(camera.offset.x, camera.offset.y, 0.0f); + + mat_transform = rf_mat_mul(rf_mat_mul(mat_origin, rf_mat_mul(mat_scale, mat_rotation)), mat_translation); + + return mat_transform; +} + +// Returns the screen space position from a 3d world space position +RF_API rf_vec2 rf_get_world_to_screen(rf_sizei screen_size, rf_vec3 position, rf_camera3d camera) +{ + // Calculate projection matrix from perspective instead of frustum + rf_mat mat_proj = rf_mat_identity(); + + if (camera.type == RF_CAMERA_PERSPECTIVE) + { + // Calculate projection matrix from perspective + mat_proj = rf_mat_perspective(camera.fovy * RF_DEG2RAD, ((double) screen_size.width / (double) screen_size.height), 0.01, 1000.0); + } + else if (camera.type == RF_CAMERA_ORTHOGRAPHIC) + { + float aspect = (float) screen_size.width / (float) screen_size.height; + double top = camera.fovy / 2.0; + double right = top * aspect; + + // Calculate projection matrix from orthographic + mat_proj = rf_mat_ortho(-right, right, -top, top, 0.01, 1000.0); + } + + // Calculate view matrix from camera look at (and transpose it) + rf_mat mat_view = rf_mat_look_at(camera.position, camera.target, camera.up); + + // Convert world position vector to quaternion + rf_quaternion world_pos = { position.x, position.y, position.z, 1.0f }; + + // Transform world position to view + world_pos = rf_quaternion_transform(world_pos, mat_view); + + // Transform result to projection (clip space position) + world_pos = rf_quaternion_transform(world_pos, mat_proj); + + // Calculate normalized device coordinates (inverted y) + rf_vec3 ndc_pos = {world_pos.x / world_pos.w, -world_pos.y / world_pos.w, world_pos.z / world_pos.w}; + + // Calculate 2d screen position vector + rf_vec2 screen_position = {(ndc_pos.x + 1.0f) / 2.0f * (float) screen_size.width, + (ndc_pos.y + 1.0f) / 2.0f * (float) screen_size.height}; + + return screen_position; +} + +// Returns the screen space position for a 2d camera world space position +RF_API rf_vec2 rf_get_world_to_screen2d(rf_vec2 position, rf_camera2d camera) +{ + rf_mat mat_camera = rf_get_camera_matrix2d(camera); + rf_vec3 transform = rf_vec3_transform((rf_vec3) {position.x, position.y, 0}, mat_camera); + + return (rf_vec2) {transform.x, transform.y}; +} + +// Returns the world space position for a 2d camera screen space position +RF_API rf_vec2 rf_get_screen_to_world2d(rf_vec2 position, rf_camera2d camera) +{ + rf_mat inv_mat_camera = rf_mat_invert(rf_get_camera_matrix2d(camera)); + rf_vec3 transform = rf_vec3_transform((rf_vec3) {position.x, position.y, 0}, inv_mat_camera); + + return (rf_vec2) {transform.x, transform.y}; +} + +// Select camera mode (multiple camera modes available) +RF_API void rf_set_camera3d_mode(rf_camera3d_state* state, rf_camera3d camera, rf_builtin_camera3d_mode mode) +{ + rf_vec3 v1 = camera.position; + rf_vec3 v2 = camera.target; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + + state->camera_target_distance = sqrtf(dx*dx + dy*dy + dz*dz); + + rf_vec2 distance = { 0.0f, 0.0f }; + distance.x = sqrtf(dx*dx + dz*dz); + distance.y = sqrtf(dx*dx + dy*dy); + + // rf_camera3d angle calculation + state->camera_angle.x = asinf((float)fabs(dx)/distance.x); // rf_camera3d angle in plane XZ (0 aligned with Z, move positive CCW) + state->camera_angle.y = -asinf((float)fabs(dy)/distance.y); // rf_camera3d angle in plane XY (0 aligned with X, move positive CW) + + state->player_eyes_position = camera.position.y; + + // Lock cursor for first person and third person cameras + // if ((mode == rf_camera_first_person) || (mode == rf_camera_third_person)) DisableCursor(); + // else EnableCursor(); + + state->camera_mode = mode; +} + +// Update camera depending on selected mode +// NOTE: rf_camera3d controls depend on some raylib functions: +// System: EnableCursor(), DisableCursor() +// Mouse: IsMouseButtonDown(), GetMousePosition(), GetMouseWheelMove() +// Keys: IsKeyDown() +// TODO: Port to quaternion-based camera +RF_API void rf_update_camera3d(rf_camera3d* camera, rf_camera3d_state* state, rf_input_state_for_update_camera input_state) +{ + // rf_camera3d mouse movement sensitivity + #define RF_CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f + #define RF_CAMERA_MOUSE_SCROLL_SENSITIVITY 1.5f + + // FREE_CAMERA + #define RF_CAMERA_FREE_MOUSE_SENSITIVITY 0.01f + #define RF_CAMERA_FREE_DISTANCE_MIN_CLAMP 0.3f + #define RF_CAMERA_FREE_DISTANCE_MAX_CLAMP 120.0f + #define RF_CAMERA_FREE_MIN_CLAMP 85.0f + #define RF_CAMERA_FREE_MAX_CLAMP -85.0f + #define RF_CAMERA_FREE_SMOOTH_ZOOM_SENSITIVITY 0.05f + #define RF_CAMERA_FREE_PANNING_DIVIDER 5.1f + + // ORBITAL_CAMERA + #define RF_CAMERA_ORBITAL_SPEED 0.01f // Radians per frame + + // FIRST_PERSON + //#define CAMERA_FIRST_PERSON_MOUSE_SENSITIVITY 0.003f + #define RF_CAMERA_FIRST_PERSON_FOCUS_DISTANCE 25.0f + #define RF_CAMERA_FIRST_PERSON_MIN_CLAMP 85.0f + #define RF_CAMERA_FIRST_PERSON_MAX_CLAMP -85.0f + + #define RF_CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER 5.0f + #define RF_CAMERA_FIRST_PERSON_STEP_DIVIDER 30.0f + #define RF_CAMERA_FIRST_PERSON_WAVING_DIVIDER 200.0f + + // THIRD_PERSON + //#define CAMERA_THIRD_PERSON_MOUSE_SENSITIVITY 0.003f + #define RF_CAMERA_THIRD_PERSON_DISTANCE_CLAMP 1.2f + #define RF_CAMERA_THIRD_PERSON_MIN_CLAMP 5.0f + #define RF_CAMERA_THIRD_PERSON_MAX_CLAMP -85.0f + #define RF_CAMERA_THIRD_PERSON_OFFSET (rf_vec3) { 0.4f, 0.0f, 0.0f } + + // PLAYER (used by camera) + #define RF_PLAYER_MOVEMENT_SENSITIVITY 20.0f + + // rf_camera3d move modes (first person and third person cameras) + typedef enum rf_camera_move + { + rf_move_front = 0, + rf_move_back, + rf_move_right, + rf_move_left, + rf_move_up, + rf_move_down + } rf_camera_move; + + // RF_INTERNAL float player_eyes_position = 1.85f; + + // TODO: CRF_INTERNAL rf_ctx->gl_ctx.camera_target_distance and rf_ctx->gl_ctx.camera_angle here + + // Mouse movement detection + rf_vec2 mouse_position_delta = { 0.0f, 0.0f }; + rf_vec2 mouse_position = input_state.mouse_position; + int mouse_wheel_move = input_state.mouse_wheel_move; + + // Keys input detection + bool pan_key = input_state.is_camera_pan_control_key_down; + bool alt_key = input_state.is_camera_alt_control_key_down; + bool szoom_key = input_state.is_camera_smooth_zoom_control_key; + + bool direction[6]; + direction[0] = input_state.direction_keys[0]; + direction[1] = input_state.direction_keys[1]; + direction[2] = input_state.direction_keys[2]; + direction[3] = input_state.direction_keys[3]; + direction[4] = input_state.direction_keys[4]; + direction[5] = input_state.direction_keys[5]; + + // TODO: Consider touch inputs for camera + + if (state->camera_mode != RF_CAMERA_CUSTOM) + { + mouse_position_delta.x = mouse_position.x - state->previous_mouse_position.x; + mouse_position_delta.y = mouse_position.y - state->previous_mouse_position.y; + + state->previous_mouse_position = mouse_position; + } + + // Support for multiple automatic camera modes + switch (state->camera_mode) + { + case RF_CAMERA_FREE: + { + // Camera zoom + if ((state->camera_target_distance < RF_CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouse_wheel_move < 0)) + { + state->camera_target_distance -= (mouse_wheel_move * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY); + + if (state->camera_target_distance > RF_CAMERA_FREE_DISTANCE_MAX_CLAMP) { + state->camera_target_distance = RF_CAMERA_FREE_DISTANCE_MAX_CLAMP; + } + } + // Camera looking down + // TODO: Review, weird comparisson of rf_ctx->gl_ctx.camera_target_distance == 120.0f? + else if ((camera->position.y > camera->target.y) && (state->camera_target_distance == RF_CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouse_wheel_move < 0)) + { + camera->target.x += mouse_wheel_move * (camera->target.x - camera->position.x) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + camera->target.y += mouse_wheel_move * (camera->target.y - camera->position.y) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + camera->target.z += mouse_wheel_move * (camera->target.z - camera->position.z) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y >= 0)) + { + camera->target.x += mouse_wheel_move * (camera->target.x - camera->position.x) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + camera->target.y += mouse_wheel_move * (camera->target.y - camera->position.y) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + camera->target.z += mouse_wheel_move * (camera->target.z - camera->position.z) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + + // if (camera->target.y < 0) camera->target.y = -0.001; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y < 0) && (mouse_wheel_move > 0)) + { + state->camera_target_distance -= (mouse_wheel_move * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY); + if (state->camera_target_distance < RF_CAMERA_FREE_DISTANCE_MIN_CLAMP) { + state->camera_target_distance = RF_CAMERA_FREE_DISTANCE_MIN_CLAMP; + } + } + // Camera looking up + // TODO: Review, weird comparisson of rf_ctx->gl_ctx.camera_target_distance == 120.0f? + else if ((camera->position.y < camera->target.y) && (state->camera_target_distance == RF_CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouse_wheel_move < 0)) + { + camera->target.x += mouse_wheel_move * (camera->target.x - camera->position.x) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + camera->target.y += mouse_wheel_move * (camera->target.y - camera->position.y) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + camera->target.z += mouse_wheel_move * (camera->target.z - camera->position.z) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y <= 0)) + { + camera->target.x += mouse_wheel_move * (camera->target.x - camera->position.x) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + camera->target.y += mouse_wheel_move * (camera->target.y - camera->position.y) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + camera->target.z += mouse_wheel_move * (camera->target.z - camera->position.z) * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY / state->camera_target_distance; + + // if (camera->target.y > 0) camera->target.y = 0.001; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y > 0) && (mouse_wheel_move > 0)) + { + state->camera_target_distance -= (mouse_wheel_move * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY); + if (state->camera_target_distance < RF_CAMERA_FREE_DISTANCE_MIN_CLAMP) { + state->camera_target_distance = RF_CAMERA_FREE_DISTANCE_MIN_CLAMP; + } + } + + // Input keys checks + if (pan_key) + { + if (alt_key) // Alternative key behaviour + { + if (szoom_key) + { + // Camera smooth zoom + state->camera_target_distance += (mouse_position_delta.y * RF_CAMERA_FREE_SMOOTH_ZOOM_SENSITIVITY); + } + else + { + // Camera rotation + state->camera_angle.x += mouse_position_delta.x*-RF_CAMERA_FREE_MOUSE_SENSITIVITY; + state->camera_angle.y += mouse_position_delta.y*-RF_CAMERA_FREE_MOUSE_SENSITIVITY; + + // Angle clamp + if (state->camera_angle.y > RF_CAMERA_FREE_MIN_CLAMP * RF_DEG2RAD) { + state->camera_angle.y = RF_CAMERA_FREE_MIN_CLAMP * RF_DEG2RAD; + } + else if (state->camera_angle.y < RF_CAMERA_FREE_MAX_CLAMP * RF_DEG2RAD) { + state->camera_angle.y = RF_CAMERA_FREE_MAX_CLAMP * RF_DEG2RAD; + } + } + } + else + { + // Camera panning + camera->target.x += ((mouse_position_delta.x*-RF_CAMERA_FREE_MOUSE_SENSITIVITY) * cosf(state->camera_angle.x) + (mouse_position_delta.y * RF_CAMERA_FREE_MOUSE_SENSITIVITY) * sinf(state->camera_angle.x) * sinf(state->camera_angle.y)) * (state->camera_target_distance / RF_CAMERA_FREE_PANNING_DIVIDER); + camera->target.y += ((mouse_position_delta.y * RF_CAMERA_FREE_MOUSE_SENSITIVITY) * cosf(state->camera_angle.y)) * (state->camera_target_distance / RF_CAMERA_FREE_PANNING_DIVIDER); + camera->target.z += ((mouse_position_delta.x * RF_CAMERA_FREE_MOUSE_SENSITIVITY) * sinf(state->camera_angle.x) + (mouse_position_delta.y * RF_CAMERA_FREE_MOUSE_SENSITIVITY) * cosf(state->camera_angle.x) * sinf(state->camera_angle.y)) * (state->camera_target_distance / RF_CAMERA_FREE_PANNING_DIVIDER); + } + } + + // Update camera position with changes + camera->position.x = sinf(state->camera_angle.x)*state->camera_target_distance*cosf(state->camera_angle.y) + camera->target.x; + camera->position.y = ((state->camera_angle.y <= 0.0f)? 1 : -1)*sinf(state->camera_angle.y)*state->camera_target_distance*sinf(state->camera_angle.y) + camera->target.y; + camera->position.z = cosf(state->camera_angle.x)*state->camera_target_distance*cosf(state->camera_angle.y) + camera->target.z; + + } break; + + case RF_CAMERA_ORBITAL: + { + state->camera_angle.x += RF_CAMERA_ORBITAL_SPEED; // Camera orbit angle + state->camera_target_distance -= (mouse_wheel_move * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY); // Camera zoom + + // Camera distance clamp + if (state->camera_target_distance < RF_CAMERA_THIRD_PERSON_DISTANCE_CLAMP) { + state->camera_target_distance = RF_CAMERA_THIRD_PERSON_DISTANCE_CLAMP; + } + + // Update camera position with changes + camera->position.x = sinf(state->camera_angle.x)*state->camera_target_distance*cosf(state->camera_angle.y) + camera->target.x; + camera->position.y = ((state->camera_angle.y <= 0.0f)? 1 : -1)*sinf(state->camera_angle.y)*state->camera_target_distance*sinf(state->camera_angle.y) + camera->target.y; + camera->position.z = cosf(state->camera_angle.x)*state->camera_target_distance*cosf(state->camera_angle.y) + camera->target.z; + + } break; + + case RF_CAMERA_FIRST_PERSON: + { + camera->position.x += (sinf(state->camera_angle.x)*direction[rf_move_back] - + sinf(state->camera_angle.x)*direction[rf_move_front] - + cosf(state->camera_angle.x)*direction[rf_move_left] + + cosf(state->camera_angle.x)*direction[rf_move_right]) / RF_PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.y += (sinf(state->camera_angle.y)*direction[rf_move_front] - + sinf(state->camera_angle.y)*direction[rf_move_back] + + 1.0f*direction[rf_move_up] - 1.0f*direction[rf_move_down]) / RF_PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.z += (cosf(state->camera_angle.x)*direction[rf_move_back] - + cosf(state->camera_angle.x)*direction[rf_move_front] + + sinf(state->camera_angle.x)*direction[rf_move_left] - + sinf(state->camera_angle.x)*direction[rf_move_right]) / RF_PLAYER_MOVEMENT_SENSITIVITY; + + bool is_moving = false; // Required for swinging + + for (rf_int i = 0; i < 6; i++) if (direction[i]) { is_moving = true; break; } + + // Camera orientation calculation + state->camera_angle.x += (mouse_position_delta.x*-RF_CAMERA_MOUSE_MOVE_SENSITIVITY); + state->camera_angle.y += (mouse_position_delta.y*-RF_CAMERA_MOUSE_MOVE_SENSITIVITY); + + // Angle clamp + if (state->camera_angle.y > RF_CAMERA_FIRST_PERSON_MIN_CLAMP * RF_DEG2RAD) { + state->camera_angle.y = RF_CAMERA_FIRST_PERSON_MIN_CLAMP * RF_DEG2RAD; + } + else if (state->camera_angle.y < RF_CAMERA_FIRST_PERSON_MAX_CLAMP * RF_DEG2RAD) { + state->camera_angle.y = RF_CAMERA_FIRST_PERSON_MAX_CLAMP * RF_DEG2RAD; + } + + // Camera is always looking at player + camera->target.x = camera->position.x - sinf(state->camera_angle.x) * RF_CAMERA_FIRST_PERSON_FOCUS_DISTANCE; + camera->target.y = camera->position.y + sinf(state->camera_angle.y) * RF_CAMERA_FIRST_PERSON_FOCUS_DISTANCE; + camera->target.z = camera->position.z - cosf(state->camera_angle.x) * RF_CAMERA_FIRST_PERSON_FOCUS_DISTANCE; + + if (is_moving) { + state->swing_counter++; + } + + // Camera position update + // NOTE: On RF_CAMERA_FIRST_PERSON player Y-movement is limited to player 'eyes position' + camera->position.y = state->player_eyes_position - sinf(state->swing_counter / RF_CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER) / RF_CAMERA_FIRST_PERSON_STEP_DIVIDER; + + camera->up.x = sinf(state->swing_counter/(RF_CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER * 2)) / RF_CAMERA_FIRST_PERSON_WAVING_DIVIDER; + camera->up.z = -sinf(state->swing_counter/(RF_CAMERA_FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER * 2)) / RF_CAMERA_FIRST_PERSON_WAVING_DIVIDER; + + } break; + + case RF_CAMERA_THIRD_PERSON: + { + camera->position.x += (sinf(state->camera_angle.x)*direction[rf_move_back] - + sinf(state->camera_angle.x)*direction[rf_move_front] - + cosf(state->camera_angle.x)*direction[rf_move_left] + + cosf(state->camera_angle.x)*direction[rf_move_right]) / RF_PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.y += (sinf(state->camera_angle.y)*direction[rf_move_front] - + sinf(state->camera_angle.y)*direction[rf_move_back] + + 1.0f*direction[rf_move_up] - 1.0f*direction[rf_move_down]) / RF_PLAYER_MOVEMENT_SENSITIVITY; + + camera->position.z += (cosf(state->camera_angle.x)*direction[rf_move_back] - + cosf(state->camera_angle.x)*direction[rf_move_front] + + sinf(state->camera_angle.x)*direction[rf_move_left] - + sinf(state->camera_angle.x)*direction[rf_move_right]) / RF_PLAYER_MOVEMENT_SENSITIVITY; + + // Camera orientation calculation + state->camera_angle.x += (mouse_position_delta.x*-RF_CAMERA_MOUSE_MOVE_SENSITIVITY); + state->camera_angle.y += (mouse_position_delta.y*-RF_CAMERA_MOUSE_MOVE_SENSITIVITY); + + // Angle clamp + if (state->camera_angle.y > RF_CAMERA_THIRD_PERSON_MIN_CLAMP * RF_DEG2RAD) + { + state->camera_angle.y = RF_CAMERA_THIRD_PERSON_MIN_CLAMP * RF_DEG2RAD; + } + else if (state->camera_angle.y < RF_CAMERA_THIRD_PERSON_MAX_CLAMP * RF_DEG2RAD) + { + state->camera_angle.y = RF_CAMERA_THIRD_PERSON_MAX_CLAMP * RF_DEG2RAD; + } + + // Camera zoom + state->camera_target_distance -= (mouse_wheel_move * RF_CAMERA_MOUSE_SCROLL_SENSITIVITY); + + // Camera distance clamp + if (state->camera_target_distance < RF_CAMERA_THIRD_PERSON_DISTANCE_CLAMP) + { + state->camera_target_distance = RF_CAMERA_THIRD_PERSON_DISTANCE_CLAMP; + } + + // TODO: It seems camera->position is not correctly updated or some rounding issue makes the camera move straight to camera->target... + camera->position.x = sinf(state->camera_angle.x)*state->camera_target_distance*cosf(state->camera_angle.y) + camera->target.x; + + if (state->camera_angle.y <= 0.0f) + { + camera->position.y = sinf(state->camera_angle.y)*state->camera_target_distance*sinf(state->camera_angle.y) + camera->target.y; + } + else + { + camera->position.y = -sinf(state->camera_angle.y)*state->camera_target_distance*sinf(state->camera_angle.y) + camera->target.y; + } + + camera->position.z = cosf(state->camera_angle.x)*state->camera_target_distance*cosf(state->camera_angle.y) + camera->target.z; + + } break; + + default: break; + } +} +/*** End of inlined file: rayfork-camera.c ***/ + + +/*** Start of inlined file: rayfork-image.c ***/ +#pragma region dependencies + +#pragma region stb_image +#define STB_IMAGE_IMPLEMENTATION +#define STBI_MALLOC(sz) RF_ALLOC(rf__global_allocator_for_dependencies, sz) +#define STBI_FREE(p) RF_FREE(rf__global_allocator_for_dependencies, p) +#define STBI_REALLOC_SIZED(p, oldsz, newsz) rf_realloc_wrapper(rf__global_allocator_for_dependencies, p, oldsz, newsz) +#define STBI_ASSERT(it) RF_ASSERT(it) +#define STBIDEF RF_INTERNAL + +/*** Start of inlined file: stb_image.h ***/ +#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. +// +// =========================================================================== +// +// 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: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// 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 +#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 +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_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 +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#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 + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +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) >> (32 - (y)))) +#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 + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __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 +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#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); +#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) +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) +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 + +// 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; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #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 + #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(_MSC_VER) && 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(_MSC_VER) && 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(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _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); + return z + (stbi__get16le(s) << 16); +} +#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); + 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]; + 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<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + if (n < 0 || n >= (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))) return 0; + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// 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); + 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); + 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) 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; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + 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 + 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 == -1) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << 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 + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << 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) << 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]; + } + 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; + } + + // 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; +} + +// 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 ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } 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"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + 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; + + // 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)); + 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)); + 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))); + 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) + +// 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[288]; + stbi__uint16 value[288]; +} 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 >= sizeof (z->size)) 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; + } + 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) return stbi__err("bad huffman code","Corrupt PNG"); + 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[288] = +{ + 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 , 288)) 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); + 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 = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +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"); + if (scan == STBI__SCAN_header) return 1; + } 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"); + // if 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; + 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) { s->img_n = pal_img_n; return 1; } + 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 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"); + 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) { + 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 { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } 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 { + 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); + 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) { + STBI_ASSERT(info.offset == s->callback_already_read + (int) (s->img_buffer - s->img_buffer_original)); + if (info.offset != s->callback_already_read + (s->img_buffer - s->buffer_start)) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } + } + + 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; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,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; ichannel,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;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,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); + 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 (!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(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; + 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 (NULL == tmp) { + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + return stbi__errpuc("outofmem", "Out of memory"); + } + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + *delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + 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 > 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 > 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); + stbi__rewind( s ); + if (p == NULL) + 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; + } + (void) stbi__get32be(s); + (void) 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) +// Does not support 16-bit-per-channel + +#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); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + 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__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + 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); + } + + 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 + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#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 + + 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. +------------------------------------------------------------------------------ +*/ +/*** End of inlined file: stb_image.h ***/ + + +#pragma endregion + +#pragma region stb_image_resize +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#define STBIR_MALLOC(sz,c) ((void)(c), RF_ALLOC(rf__global_allocator_for_dependencies, sz)) +#define STBIR_FREE(p,c) ((void)(c), RF_FREE(rf__global_allocator_for_dependencies, p)) +#define STBIR_ASSERT(it) RF_ASSERT(it) +#define STBIRDEF RF_INTERNAL + +/*** Start of inlined file: stb_image_resize.h ***/ +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H + +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4) +// * input_w is input image width (x-axis), input_h is input image height (y-axis) +// * stride is the offset between successive rows of image data in memory, in bytes. you can +// specify 0 to mean packed continuously in memory +// * alpha channel is treated identically to other channels. +// * colorspace is linear or sRGB as specified by function name +// * returned result is 1 for success or 0 in case of an error. +// #define STBIR_ASSERT() to trigger an assert on parameter validation errors. +// * Memory required grows approximately linearly with input and output size, but with +// discontinuities at input_w == output_w and input_h == output_h. +// * These functions use a "default" resampling filter defined at compile time. To change the filter, +// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE +// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API. + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + +// The following functions interpret image data as gamma-corrected sRGB. +// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel, +// or otherwise provide the index of the alpha channel. Flags value +// of 0 will probably do the right thing if you're not sure what +// the flags mean. + +#define STBIR_ALPHA_CHANNEL_NONE -1 + +// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will +// use alpha-weighted resampling (effectively premultiplying, resampling, +// then unpremultiplying). +#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0) +// The specified alpha channel should be handled as gamma-corrected value even +// when doing sRGB operations. +#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1) + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags); + +typedef enum +{ + STBIR_EDGE_CLAMP = 1, + STBIR_EDGE_REFLECT = 2, + STBIR_EDGE_WRAP = 3, + STBIR_EDGE_ZERO = 4, +} stbir_edge; + +// This function adds the ability to specify how requests to sample off the edge of the image are handled. +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode); + +////////////////////////////////////////////////////////////////////////////// +// +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Alpha-channel can be processed separately +// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE +// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT) +// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED) +// * Filter can be selected explicitly +// * uint16 image type +// * sRGB colorspace available for all types +// * context parameter for passing to STBIR_MALLOC + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 +} stbir_filter; + +typedef enum +{ + STBIR_COLORSPACE_LINEAR, + STBIR_COLORSPACE_SRGB, + + STBIR_MAX_COLORSPACES, +} stbir_colorspace; + +// The following functions are all identical except for the type of the image data + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +////////////////////////////////////////////////////////////////////////////// +// +// Full-complexity API +// +// This extends the medium API as follows: +// +// * uint32 image type +// * not typesafe +// * separate filter types for each axis +// * separate edge modes for each axis +// * can specify scale explicitly for subpixel correctness +// * can specify image source tile using texture coordinates + +typedef enum +{ + STBIR_TYPE_UINT8 , + STBIR_TYPE_UINT16, + STBIR_TYPE_UINT32, + STBIR_TYPE_FLOAT , + + STBIR_MAX_TYPES +} stbir_datatype; + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context); + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset); + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1); +// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use. + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H + +#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +// For memset +#include + +#include + +#ifndef STBIR_MALLOC +#include +// use comma operator to evaluate c, to avoid "unused parameter" warnings +#define STBIR_MALLOC(size,c) ((void)(c), malloc(size)) +#define STBIR_FREE(ptr,c) ((void)(c), free(ptr)) +#endif + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbir__inline inline +#else +#define stbir__inline +#endif +#else +#define stbir__inline __forceinline +#endif + +// should produce compiler error if size is wrong +typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBIR__NOTUSED(v) (void)(v) +#else +#define STBIR__NOTUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + +#ifndef STBIR_PROGRESS_REPORT +#define STBIR_PROGRESS_REPORT(float_0_to_1) +#endif + +#ifndef STBIR_MAX_CHANNELS +#define STBIR_MAX_CHANNELS 64 +#endif + +#if STBIR_MAX_CHANNELS > 65536 +#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536." +// because we store the indices in 16-bit variables +#endif + +// This value is added to alpha just before premultiplication to avoid +// zeroing out color values. It is equivalent to 2^-80. If you don't want +// that behavior (it may interfere if you have floating point images with +// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to +// disable it. +#ifndef STBIR_ALPHA_EPSILON +#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) +#endif + +#ifdef _MSC_VER +#define STBIR__UNUSED_PARAM(v) (void)(v) +#else +#define STBIR__UNUSED_PARAM(v) (void)sizeof(v) +#endif + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1, // STBIR_TYPE_UINT8 + 2, // STBIR_TYPE_UINT16 + 4, // STBIR_TYPE_UINT32 + 4, // STBIR_TYPE_FLOAT +}; + +// Kernel function centered at 0 +typedef float (stbir__kernel_fn)(float x, float scale); +typedef float (stbir__support_fn)(float scale); + +typedef struct +{ + stbir__kernel_fn* kernel; + stbir__support_fn* support; +} stbir__filter_info; + +// When upsampling, the contributors are which source pixels contribute. +// When downsampling, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + const void* input_data; + int input_w; + int input_h; + int input_stride_bytes; + + void* output_data; + int output_w; + int output_h; + int output_stride_bytes; + + float s0, t0, s1, t1; + + float horizontal_shift; // Units: output pixels + float vertical_shift; // Units: output pixels + float horizontal_scale; + float vertical_scale; + + int channels; + int alpha_channel; + stbir_uint32 flags; + stbir_datatype type; + stbir_filter horizontal_filter; + stbir_filter vertical_filter; + stbir_edge edge_horizontal; + stbir_edge edge_vertical; + stbir_colorspace colorspace; + + stbir__contributors* horizontal_contributors; + float* horizontal_coefficients; + + stbir__contributors* vertical_contributors; + float* vertical_coefficients; + + int decode_buffer_pixels; + float* decode_buffer; + + float* horizontal_buffer; + + // cache these because ceil/floor are inexplicably showing up in profile + int horizontal_coefficient_width; + int vertical_coefficient_width; + int horizontal_filter_pixel_width; + int vertical_filter_pixel_width; + int horizontal_filter_pixel_margin; + int vertical_filter_pixel_margin; + int horizontal_num_contributors; + int vertical_num_contributors; + + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + float* ring_buffer; + + float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds. + + int horizontal_contributors_size; + int horizontal_coefficients_size; + int vertical_contributors_size; + int vertical_coefficients_size; + int decode_buffer_size; + int horizontal_buffer_size; + int ring_buffer_size; + int encode_buffer_size; +} stbir__info; + +static const float stbir__max_uint8_as_float = 255.0f; +static const float stbir__max_uint16_as_float = 65535.0f; +static const double stbir__max_uint32_as_float = 4294967295.0; + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline float stbir__saturate(float x) +{ + if (x < 0) + return 0; + + if (x > 1) + return 1; + + return x; +} + +#ifdef STBIR_SATURATE_INT +static stbir__inline stbir_uint8 stbir__saturate8(int x) +{ + if ((unsigned int) x <= 255) + return x; + + if (x < 0) + return 0; + + return 255; +} + +static stbir__inline stbir_uint16 stbir__saturate16(int x) +{ + if ((unsigned int) x <= 65535) + return x; + + if (x < 0) + return 0; + + return 65535; +} +#endif + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +static float stbir__srgb_to_linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return (float)pow((f + 0.055f) / 1.055f, 2.4f); +} + +static float stbir__linear_to_srgb(float f) +{ + if (f <= 0.0031308f) + return f * 12.92f; + else + return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f; +} + +#ifndef STBIR_NON_IEEE_FLOAT +// From https://gist.github.com/rygorous/2203834 + +typedef union +{ + stbir_uint32 u; + float f; +} stbir__FP32; + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + in = minval.f; + if (in > almostone.f) + in = almostone.f; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#else +// sRGB transition values, scaled by 1<<28 +static int stbir__srgb_offset_to_linear_scaled[256] = +{ + 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603, + 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926, + 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148, + 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856, + 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731, + 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369, + 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021, + 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073, + 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389, + 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552, + 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066, + 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490, + 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568, + 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316, + 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096, + 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700, + 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376, + 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912, + 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648, + 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512, + 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072, + 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544, + 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832, + 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528, + 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968, + 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184, + 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992, + 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968, + 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480, + 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656, + 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464, + 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float f) +{ + int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp + int v = 0; + int i; + + // Refine the guess with a short binary search. + i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + + return (stbir_uint8) v; +} +#endif + +static float stbir__filter_trapezoid(float x, float scale) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + + x = (float)fabs(x); + + if (x >= t) + return 0; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale) +{ + STBIR_ASSERT(scale <= 1); + return 0.5f + scale / 2; +} + +static float stbir__filter_triangle(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x <= 1.0f) + return 1 - x; + else + return 0; +} + +static float stbir__filter_cubic(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (4 + x*x*(3*x - 6))/6; + else if (x < 2.0f) + return (8 + x*(-12 + x*(6 - x)))/6; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return 1 - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2 - x*(4 + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (16 + x*x*(21 * x - 36))/18; + else if (x < 2.0f) + return (32 + x*(-60 + x*(36 - 7*x)))/18; + + return (0.0f); +} + +static float stbir__support_zero(float s) +{ + STBIR__UNUSED_PARAM(s); + return 0; +} + +static float stbir__support_one(float s) +{ + STBIR__UNUSED_PARAM(s); + return 1; +} + +static float stbir__support_two(float s) +{ + STBIR__UNUSED_PARAM(s); + return 2; +} + +static stbir__filter_info stbir__filter_info_table[] = { + { NULL, stbir__support_zero }, + { stbir__filter_trapezoid, stbir__support_trapezoid }, + { stbir__filter_triangle, stbir__support_one }, + { stbir__filter_cubic, stbir__support_two }, + { stbir__filter_catmullrom, stbir__support_two }, + { stbir__filter_mitchell, stbir__support_two }, +}; + +stbir__inline static int stbir__use_upsampling(float ratio) +{ + return ratio > 1; +} + +stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->horizontal_scale); +} + +stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->vertical_scale); +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter +static int stbir__get_filter_pixel_width(stbir_filter filter, float scale) +{ + STBIR_ASSERT(filter != 0); + STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale); +} + +// This is how much to expand buffers to account for filters seeking outside +// the image boundaries. +static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale) +{ + return stbir__get_filter_pixel_width(filter, scale) / 2; +} + +static int stbir__get_coefficient_width(stbir_filter filter, float scale) +{ + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2); +} + +static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size) +{ + if (stbir__use_upsampling(scale)) + return output_size; + else + return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2); +} + +static int stbir__get_total_horizontal_coefficients(stbir__info* info) +{ + return info->horizontal_num_contributors + * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); +} + +static int stbir__get_total_vertical_coefficients(stbir__info* info) +{ + return info->vertical_num_contributors + * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale); +} + +static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n) +{ + return &contributors[n]; +} + +// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample, +// if you change it here change it there too. +static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c) +{ + int width = stbir__get_coefficient_width(filter, scale); + return &coefficients[width*n + c]; +} + +static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max) +{ + switch (edge) + { + case STBIR_EDGE_ZERO: + return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later + + case STBIR_EDGE_CLAMP: + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED + + case STBIR_EDGE_REFLECT: + { + if (n < 0) + { + if (n < max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED + } + + case STBIR_EDGE_WRAP: + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } + // NOTREACHED + + default: + STBIR_ASSERT(!"Unimplemented edge type"); + return 0; + } +} + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow(edge, n, max); +} + +// What input pixels contribute to this output pixel? +static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out) +{ + float out_pixel_center = (float)n + 0.5f; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio; + + *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio; + *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5)); + *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5)); +} + +// What output pixels does this input pixel contribute to? +static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in) +{ + float in_pixel_center = (float)n + 0.5f; + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift; + + *out_center_of_in = in_pixel_center * scale_ratio - out_shift; + *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5)); + *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5)); +} + +static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + float total_filter = 0; + float filter_scale; + + STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = in_first_pixel; + contributor->n1 = in_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale); + + // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.) + if (i == 0 && !coefficient_group[i]) + { + contributor->n0 = ++in_first_pixel; + i--; + continue; + } + + total_filter += coefficient_group[i]; + } + + STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0); + + STBIR_ASSERT(total_filter > 0.9); + STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off. + + // Make sure the sum of all coefficients is 1. + filter_scale = 1 / total_filter; + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + coefficient_group[i] *= filter_scale; + + for (i = in_last_pixel - in_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + + STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = out_first_pixel; + contributor->n1 = out_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio; + } + + STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0); + + for (i = out_last_pixel - out_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size) +{ + int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio); + int i, j; + int skip; + + for (i = 0; i < output_size; i++) + { + float scale; + float total = 0; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + { + float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0); + total += coefficient; + } + else if (i < contributors[j].n0) + break; + } + + STBIR_ASSERT(total > 0.9f); + STBIR_ASSERT(total < 1.1f); + + scale = 1 / total; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale; + else if (i < contributors[j].n0) + break; + } + } + + // Optimize: Skip zero coefficients and contributions outside of image bounds. + // Do this after normalizing because normalization depends on the n0/n1 values. + for (j = 0; j < num_contributors; j++) + { + int range, max, width; + + skip = 0; + while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0) + skip++; + + contributors[j].n0 += skip; + + while (contributors[j].n0 < 0) + { + contributors[j].n0++; + skip++; + } + + range = contributors[j].n1 - contributors[j].n0 + 1; + max = stbir__min(num_coefficients, range); + + width = stbir__get_coefficient_width(filter, scale_ratio); + for (i = 0; i < max; i++) + { + if (i + skip >= width) + break; + + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip); + } + + continue; + } + + // Using min to avoid writing into invalid pixels. + for (i = 0; i < num_contributors; i++) + contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1); +} + +// Each scan line uses the same kernel values so we should calculate the kernel +// values once and then we can use them for every scan line. +static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size) +{ + int n; + int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + + if (stbir__use_upsampling(scale_ratio)) + { + float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio; + + // Looping through out pixels + for (n = 0; n < total_contributors; n++) + { + float in_center_of_out; // Center of the current out pixel in the in pixel space + int in_first_pixel, in_last_pixel; + + stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out); + + stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + } + else + { + float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio; + + // Looping through in pixels + for (n = 0; n < total_contributors; n++) + { + float out_center_of_in; // Center of the current out pixel in the in pixel space + int out_first_pixel, out_last_pixel; + int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio); + + stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in); + + stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + + stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size); + } +} + +static float* stbir__get_decode_buffer(stbir__info* stbir_info) +{ + // The 0 index of the decode buffer starts after the margin. This makes + // it okay to use negative indexes on the decode buffer. + return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels]; +} + +#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace)) + +static void stbir__decode_scanline(stbir__info* stbir_info, int n) +{ + int c; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int input_w = stbir_info->input_w; + size_t input_stride_bytes = stbir_info->input_stride_bytes; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir_edge edge_horizontal = stbir_info->edge_horizontal; + stbir_edge edge_vertical = stbir_info->edge_vertical; + size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes; + const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset; + int max_x = input_w + stbir_info->horizontal_filter_pixel_margin; + int decode = STBIR__DECODE(type, colorspace); + + int x = -stbir_info->horizontal_filter_pixel_margin; + + // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input, + // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO + if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h)) + { + for (; x < max_x; x++) + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + return; + } + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float)); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c]; + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel]; + } + + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++) + { + int decode_pixel_index = x * channels; + + // If the alpha value is 0 it will clobber the color values. Make sure it's not. + float alpha = decode_buffer[decode_pixel_index + alpha_channel]; +#ifndef STBIR_NO_ALPHA_EPSILON + if (stbir_info->type != STBIR_TYPE_FLOAT) { + alpha += STBIR_ALPHA_EPSILON; + decode_buffer[decode_pixel_index + alpha_channel] = alpha; + } +#endif + for (c = 0; c < channels; c++) + { + if (c == alpha_channel) + continue; + + decode_buffer[decode_pixel_index + c] *= alpha; + } + } + } + + if (edge_horizontal == STBIR_EDGE_ZERO) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + for (x = input_w; x < max_x; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + } +} + +static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length) +{ + return &ring_buffer[index * ring_buffer_length]; +} + +static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + stbir_info->ring_buffer_last_scanline = n; + + if (stbir_info->ring_buffer_begin_index < 0) + { + ring_buffer_index = stbir_info->ring_buffer_begin_index = 0; + stbir_info->ring_buffer_first_scanline = n; + } + else + { + ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index); + } + + ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float)); + memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes); + + return ring_buffer; +} + +static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int output_w = stbir_info->output_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + + for (x = 0; x < output_w; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int out_pixel_index = x * channels; + int coefficient_group = coefficient_width * x; + int coefficient_counter = 0; + + STBIR_ASSERT(n1 >= n0); + STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + int c; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int input_w = stbir_info->input_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin; + int max_x = input_w + filter_pixel_margin * 2; + + STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info)); + + switch (channels) { + case 1: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 1; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + } + break; + + case 2: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 2; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + } + break; + + case 3: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 3; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + } + break; + + case 4: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 4; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + } + break; + + default: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * channels; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int c; + int out_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + } + break; + } +} + +static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + // Now resample it into the ring buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + else + stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float)); + + // Now resample it into the horizontal buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer); + else + stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer); + + // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers. +} + +// Get the specified scan line from the ring buffer. +static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length) +{ + int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries; + return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length); +} + +static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode) +{ + int x; + int n; + int num_nonalpha; + stbir_uint16 nonalpha[STBIR_MAX_CHANNELS]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + float alpha = encode_buffer[pixel_index + alpha_channel]; + float reciprocal_alpha = alpha ? 1.0f / alpha : 0; + + // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb + for (n = 0; n < channels; n++) + if (n != alpha_channel) + encode_buffer[pixel_index + n] *= reciprocal_alpha; + + // We added in a small epsilon to prevent the color channel from being deleted with zero alpha. + // Because we only add it for integer types, it will automatically be discarded on integer + // conversion, so we don't need to subtract it back out (which would be problematic for + // numeric precision reasons). + } + } + + // build a table of all channels that need colorspace correction, so + // we don't perform colorspace correction on channels that don't need it. + for (x = 0, num_nonalpha = 0; x < channels; ++x) + { + if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + { + nonalpha[num_nonalpha++] = (stbir_uint16)x; + } + } + + #define STBIR__ROUND_INT(f) ((int) ((f)+0.5)) + #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5)) + + #ifdef STBIR__SATURATE_INT + #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float )) + #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float)) + #else + #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float ) + #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float) + #endif + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]); + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]); + } + + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((float*)output_buffer)[index] = encode_buffer[index]; + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel]; + } + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } +} + +static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + void* output_data = stbir_info->output_data; + float* encode_buffer = stbir_info->encode_buffer; + int decode = STBIR__DECODE(type, colorspace); + int coefficient_width = stbir_info->vertical_coefficient_width; + int coefficient_counter; + int contributor = n; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + int n0,n1, output_row_start; + int coefficient_group = coefficient_width * contributor; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + output_row_start = n * stbir_info->output_stride_bytes; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + memset(encode_buffer, 0, output_w * sizeof(float) * channels); + + // I tried reblocking this for better cache usage of encode_buffer + // (using x_outer, k, x_inner), but it lost speed. -- stb + + coefficient_counter = 0; + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 1; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + } + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 2; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + } + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 3; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + } + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 4; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient; + } + } + break; + default: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * channels; + int c; + for (c = 0; c < channels; c++) + encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient; + } + } + break; + } + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode); +} + +static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + float* horizontal_buffer = stbir_info->horizontal_buffer; + int coefficient_width = stbir_info->vertical_coefficient_width; + int contributor = n + stbir_info->vertical_filter_pixel_margin; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + int n0,n1; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (k = n0; k <= n1; k++) + { + int coefficient_index = k - n0; + int coefficient_group = coefficient_width * contributor; + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + + switch (channels) { + case 1: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 1; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 2; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 3; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 4; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * channels; + + int c; + for (c = 0; c < channels; c++) + ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__buffer_loop_upsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + for (y = 0; y < stbir_info->output_h; y++) + { + float in_center_of_out = 0; // Center of the current out scanline in the in scanline space + int in_first_scanline = 0, in_last_scanline = 0; + + stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out); + + STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (in_first_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__decode_and_resample_upsample(stbir_info, in_first_scanline); + + while (in_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now all buffers should be ready to write a row of vertical sampling. + stbir__resample_vertical_upsample(stbir_info, y); + + STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h); + } +} + +static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline) +{ + int output_stride_bytes = stbir_info->output_stride_bytes; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int output_w = stbir_info->output_w; + void* output_data = stbir_info->output_data; + int decode = STBIR__DECODE(type, colorspace); + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h) + { + int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes; + float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length); + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode); + STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h); + } + + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } +} + +static void stbir__buffer_loop_downsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + int output_h = stbir_info->output_h; + float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio; + int pixel_margin = stbir_info->vertical_filter_pixel_margin; + int max_y = stbir_info->input_h + pixel_margin; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (y = -pixel_margin; y < max_y; y++) + { + float out_center_of_in; // Center of the current out scanline in the in scanline space + int out_first_scanline, out_last_scanline; + + stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in); + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (out_last_scanline < 0 || out_first_scanline >= output_h) + continue; + + stbir__empty_ring_buffer(stbir_info, out_first_scanline); + + stbir__decode_and_resample_downsample(stbir_info, y); + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline); + + while (out_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now the horizontal buffer is ready to write to all ring buffer rows. + stbir__resample_vertical_downsample(stbir_info, y); + } + + stbir__empty_ring_buffer(stbir_info, stbir_info->output_h); +} + +static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels) +{ + info->input_w = input_w; + info->input_h = input_h; + info->output_w = output_w; + info->output_h = output_h; + info->channels = channels; +} + +static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform) +{ + info->s0 = s0; + info->t0 = t0; + info->s1 = s1; + info->t1 = t1; + + if (transform) + { + info->horizontal_scale = transform[0]; + info->vertical_scale = transform[1]; + info->horizontal_shift = transform[2]; + info->vertical_shift = transform[3]; + } + else + { + info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0); + info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0); + + info->horizontal_shift = s0 * info->output_w / (s1 - s0); + info->vertical_shift = t0 * info->output_h / (t1 - t0); + } +} + +static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter) +{ + if (h_filter == 0) + h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + if (v_filter == 0) + v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + info->horizontal_filter = h_filter; + info->vertical_filter = v_filter; +} + +static stbir_uint32 stbir__calculate_memory(stbir__info *info) +{ + int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale); + + info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w); + info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h); + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + info->ring_buffer_num_entries = filter_height + 1; + + info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors); + info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float); + info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors); + info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float); + info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float); + info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float); + info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float); + info->encode_buffer_size = info->output_w * info->channels * sizeof(float); + + STBIR_ASSERT(info->horizontal_filter != 0); + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + STBIR_ASSERT(info->vertical_filter != 0); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + + if (stbir__use_height_upsampling(info)) + // The horizontal buffer is for when we're downsampling the height and we + // can't output the result of sampling the decode buffer directly into the + // ring buffers. + info->horizontal_buffer_size = 0; + else + // The encode buffer is to retain precision in the height upsampling method + // and isn't used when height downsampling. + info->encode_buffer_size = 0; + + return info->horizontal_contributors_size + info->horizontal_coefficients_size + + info->vertical_contributors_size + info->vertical_coefficients_size + + info->decode_buffer_size + info->horizontal_buffer_size + + info->ring_buffer_size + info->encode_buffer_size; +} + +static int stbir__resize_allocated(stbir__info *info, + const void* input_data, int input_stride_in_bytes, + void* output_data, int output_stride_in_bytes, + int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace, + void* tempmem, size_t tempmem_size_in_bytes) +{ + size_t memory_required = stbir__calculate_memory(info); + + int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type]; + int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type]; + +#ifdef STBIR_DEBUG_OVERWRITE_TEST +#define OVERWRITE_ARRAY_SIZE 8 + unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE]; + + size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type]; + memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE); +#endif + + STBIR_ASSERT(info->channels >= 0); + STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS); + + if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS) + return 0; + + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + + if (alpha_channel < 0) + flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED; + + if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) { + STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels); + } + + if (alpha_channel >= info->channels) + return 0; + + STBIR_ASSERT(tempmem); + + if (!tempmem) + return 0; + + STBIR_ASSERT(tempmem_size_in_bytes >= memory_required); + + if (tempmem_size_in_bytes < memory_required) + return 0; + + memset(tempmem, 0, tempmem_size_in_bytes); + + info->input_data = input_data; + info->input_stride_bytes = width_stride_input; + + info->output_data = output_data; + info->output_stride_bytes = width_stride_output; + + info->alpha_channel = alpha_channel; + info->flags = flags; + info->type = type; + info->edge_horizontal = edge_horizontal; + info->edge_vertical = edge_vertical; + info->colorspace = colorspace; + + info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale ); + + info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float); + info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2; + +#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size) + + info->horizontal_contributors = (stbir__contributors *) tempmem; + info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float); + info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors); + info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float); + info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float); + + if (stbir__use_height_upsampling(info)) + { + info->horizontal_buffer = NULL; + info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float); + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + else + { + info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float); + info->encode_buffer = NULL; + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + +#undef STBIR__NEXT_MEMPTR + + // This signals that the ring buffer is empty + info->ring_buffer_begin_index = -1; + + stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w); + stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h); + + STBIR_PROGRESS_REPORT(0); + + if (stbir__use_height_upsampling(info)) + stbir__buffer_loop_upsample(info); + else + stbir__buffer_loop_downsample(info); + + STBIR_PROGRESS_REPORT(1); + +#ifdef STBIR_DEBUG_OVERWRITE_TEST + STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0); +#endif + + return 1; +} + +static int stbir__resize_arbitrary( + void *alloc_context, + const void* input_data, int input_w, int input_h, int input_stride_in_bytes, + void* output_data, int output_w, int output_h, int output_stride_in_bytes, + float s0, float t0, float s1, float t1, float *transform, + int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_filter h_filter, stbir_filter v_filter, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace) +{ + stbir__info info; + int result; + size_t memory_required; + void* extra_memory; + + stbir__setup(&info, input_w, input_h, output_w, output_h, channels); + stbir__calculate_transform(&info, s0,t0,s1,t1,transform); + stbir__choose_filter(&info, h_filter, v_filter); + memory_required = stbir__calculate_memory(&info); + extra_memory = STBIR_MALLOC(memory_required, alloc_context); + + if (!extra_memory) + return 0; + + result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes, + output_data, output_stride_in_bytes, + alpha_channel, flags, type, + edge_horizontal, edge_vertical, + colorspace, extra_memory, memory_required); + + STBIR_FREE(extra_memory, alloc_context); + + return result; +} + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset) +{ + float transform[4]; + transform[0] = x_scale; + transform[1] = y_scale; + transform[2] = x_offset; + transform[3] = y_offset; + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +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. +------------------------------------------------------------------------------ +*/ +/*** End of inlined file: stb_image_resize.h ***/ + + +#pragma endregion + +#pragma region stb_rect_pack +#define STB_RECT_PACK_IMPLEMENTATION +#define STBRP_ASSERT RF_ASSERT +#define STBRP_STATIC + +/*** Start of inlined file: stb_rect_pack.h ***/ +// stb_rect_pack.h - v1.00 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +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. +------------------------------------------------------------------------------ +*/ +/*** End of inlined file: stb_rect_pack.h ***/ + + +#pragma endregion + +#pragma region stb_perlin +#define STB_PERLIN_IMPLEMENTATION +#define STBPDEF RF_INTERNAL + +/*** Start of inlined file: stb_perlin.h ***/ +// stb_perlin.h - v0.5 - perlin noise +// public domain single-file C implementation by Sean Barrett +// +// LICENSE +// +// See end of file. +// +// +// to create the implementation, +// #define STB_PERLIN_IMPLEMENTATION +// in *one* C/CPP file that includes this file. +// +// +// Documentation: +// +// float stb_perlin_noise3( float x, +// float y, +// float z, +// int x_wrap=0, +// int y_wrap=0, +// int z_wrap=0) +// +// This function computes a random value at the coordinate (x,y,z). +// Adjacent random values are continuous but the noise fluctuates +// its randomness with period 1, i.e. takes on wholly unrelated values +// at integer points. Specifically, this implements Ken Perlin's +// revised noise function from 2002. +// +// The "wrap" parameters can be used to create wraparound noise that +// wraps at powers of two. The numbers MUST be powers of two. Specify +// 0 to mean "don't care". (The noise always wraps every 256 due +// details of the implementation, even if you ask for larger or no +// wrapping.) +// +// float stb_perlin_noise3_seed( float x, +// float y, +// float z, +// int x_wrap=0, +// int y_wrap=0, +// int z_wrap=0, +// int seed) +// +// As above, but 'seed' selects from multiple different variations of the +// noise function. The current implementation only uses the bottom 8 bits +// of 'seed', but possibly in the future more bits will be used. +// +// +// Fractal Noise: +// +// Three common fractal noise functions are included, which produce +// a wide variety of nice effects depending on the parameters +// provided. Note that each function will call stb_perlin_noise3 +// 'octaves' times, so this parameter will affect runtime. +// +// float stb_perlin_ridge_noise3(float x, float y, float z, +// float lacunarity, float gain, float offset, int octaves) +// +// float stb_perlin_fbm_noise3(float x, float y, float z, +// float lacunarity, float gain, int octaves) +// +// float stb_perlin_turbulence_noise3(float x, float y, float z, +// float lacunarity, float gain, int octaves) +// +// Typical values to start playing with: +// octaves = 6 -- number of "octaves" of noise3() to sum +// lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) +// gain = 0.5 -- relative weighting applied to each successive octave +// offset = 1.0? -- used to invert the ridges, may need to be larger, not sure +// +// +// Contributors: +// Jack Mott - additional noise functions +// Jordan Peck - seeded noise +// + +#ifndef STBPDEF + #define STBPDEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif +STBPDEF float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap); +STBPDEF float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed); +STBPDEF float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves); +STBPDEF float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves); +STBPDEF float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves); +STBPDEF float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed); +#ifdef __cplusplus +} +#endif + +#ifdef STB_PERLIN_IMPLEMENTATION + +#include // fabs() + +// not same permutation table as Perlin's reference to avoid copyright issues; +// Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/ +static unsigned char stb__perlin_randtab[512] = +{ + 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, + 152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72, + 175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240, + 8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57, + 225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233, + 94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172, + 165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243, + 65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122, + 26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76, + 250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246, + 132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3, + 91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231, + 38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221, + 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, + 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, + 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, + + // and a second copy so we don't need an extra mask or static initializer + 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, + 152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72, + 175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240, + 8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57, + 225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233, + 94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172, + 165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243, + 65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122, + 26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76, + 250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246, + 132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3, + 91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231, + 38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221, + 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, + 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, + 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, +}; + +// perlin's gradient has 12 cases so some get used 1/16th of the time +// and some 2/16ths. We reduce bias by changing those fractions +// to 5/64ths and 6/64ths + +// this array is designed to match the previous implementation +// of gradient hash: indices[stb__perlin_randtab[i]&63] +static unsigned char stb__perlin_randtab_grad_idx[512] = +{ + 7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7, + 8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8, + 7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8, + 8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5, + 5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1, + 2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4, + 9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11, + 1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6, + 10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0, + 6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2, + 4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3, + 11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11, + 10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1, + 3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10, + 11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7, + 9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5, + + // and a second copy so we don't need an extra mask or static initializer + 7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7, + 8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8, + 7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8, + 8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5, + 5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1, + 2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4, + 9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11, + 1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6, + 10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0, + 6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2, + 4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3, + 11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11, + 10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1, + 3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10, + 11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7, + 9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5, +}; + +static float stb__perlin_lerp(float a, float b, float t) +{ + return a + (b-a) * t; +} + +static int stb__perlin_fastfloor(float a) +{ + int ai = (int) a; + return (a < ai) ? ai-1 : ai; +} + +// different grad function from Perlin's, but easy to modify to match reference +static float stb__perlin_grad(int grad_idx, float x, float y, float z) +{ + static float basis[12][4] = + { + { 1, 1, 0 }, + { -1, 1, 0 }, + { 1,-1, 0 }, + { -1,-1, 0 }, + { 1, 0, 1 }, + { -1, 0, 1 }, + { 1, 0,-1 }, + { -1, 0,-1 }, + { 0, 1, 1 }, + { 0,-1, 1 }, + { 0, 1,-1 }, + { 0,-1,-1 }, + }; + + float *grad = basis[grad_idx]; + return grad[0]*x + grad[1]*y + grad[2]*z; +} + +STBPDEF float stb_perlin_noise3_internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed) +{ + float u,v,w; + float n000,n001,n010,n011,n100,n101,n110,n111; + float n00,n01,n10,n11; + float n0,n1; + + unsigned int x_mask = (x_wrap-1) & 255; + unsigned int y_mask = (y_wrap-1) & 255; + unsigned int z_mask = (z_wrap-1) & 255; + int px = stb__perlin_fastfloor(x); + int py = stb__perlin_fastfloor(y); + int pz = stb__perlin_fastfloor(z); + int x0 = px & x_mask, x1 = (px+1) & x_mask; + int y0 = py & y_mask, y1 = (py+1) & y_mask; + int z0 = pz & z_mask, z1 = (pz+1) & z_mask; + int r0,r1, r00,r01,r10,r11; + + #define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a) + + x -= px; u = stb__perlin_ease(x); + y -= py; v = stb__perlin_ease(y); + z -= pz; w = stb__perlin_ease(z); + + r0 = stb__perlin_randtab[x0+seed]; + r1 = stb__perlin_randtab[x1+seed]; + + r00 = stb__perlin_randtab[r0+y0]; + r01 = stb__perlin_randtab[r0+y1]; + r10 = stb__perlin_randtab[r1+y0]; + r11 = stb__perlin_randtab[r1+y1]; + + n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z ); + n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 ); + n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z ); + n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 ); + n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z ); + n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 ); + n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z ); + n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 ); + + n00 = stb__perlin_lerp(n000,n001,w); + n01 = stb__perlin_lerp(n010,n011,w); + n10 = stb__perlin_lerp(n100,n101,w); + n11 = stb__perlin_lerp(n110,n111,w); + + n0 = stb__perlin_lerp(n00,n01,v); + n1 = stb__perlin_lerp(n10,n11,v); + + return stb__perlin_lerp(n0,n1,u); +} + +STBPDEF float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap) +{ + return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap,0); +} + +STBPDEF float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed) +{ + return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap, (unsigned char) seed); +} + +STBPDEF float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves) +{ + int i; + float frequency = 1.0f; + float prev = 1.0f; + float amplitude = 0.5f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i); + r = offset - (float) fabs(r); + r = r*r; + sum += r*amplitude*prev; + prev = r; + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +STBPDEF float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves) +{ + int i; + float frequency = 1.0f; + float amplitude = 1.0f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + sum += stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude; + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +STBPDEF float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves) +{ + int i; + float frequency = 1.0f; + float amplitude = 1.0f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude; + sum += (float) fabs(r); + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +STBPDEF float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed) +{ + float u,v,w; + float n000,n001,n010,n011,n100,n101,n110,n111; + float n00,n01,n10,n11; + float n0,n1; + + int px = stb__perlin_fastfloor(x); + int py = stb__perlin_fastfloor(y); + int pz = stb__perlin_fastfloor(z); + int x_wrap2 = (x_wrap ? x_wrap : 256); + int y_wrap2 = (y_wrap ? y_wrap : 256); + int z_wrap2 = (z_wrap ? z_wrap : 256); + int x0 = px % x_wrap2, x1; + int y0 = py % y_wrap2, y1; + int z0 = pz % z_wrap2, z1; + int r0,r1, r00,r01,r10,r11; + + if (x0 < 0) x0 += x_wrap2; + if (y0 < 0) y0 += y_wrap2; + if (z0 < 0) z0 += z_wrap2; + x1 = (x0+1) % x_wrap2; + y1 = (y0+1) % y_wrap2; + z1 = (z0+1) % z_wrap2; + + #define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a) + + x -= px; u = stb__perlin_ease(x); + y -= py; v = stb__perlin_ease(y); + z -= pz; w = stb__perlin_ease(z); + + r0 = stb__perlin_randtab[x0]; + r0 = stb__perlin_randtab[r0+seed]; + r1 = stb__perlin_randtab[x1]; + r1 = stb__perlin_randtab[r1+seed]; + + r00 = stb__perlin_randtab[r0+y0]; + r01 = stb__perlin_randtab[r0+y1]; + r10 = stb__perlin_randtab[r1+y0]; + r11 = stb__perlin_randtab[r1+y1]; + + n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z ); + n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 ); + n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z ); + n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 ); + n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z ); + n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 ); + n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z ); + n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 ); + + n00 = stb__perlin_lerp(n000,n001,w); + n01 = stb__perlin_lerp(n010,n011,w); + n10 = stb__perlin_lerp(n100,n101,w); + n11 = stb__perlin_lerp(n110,n111,w); + + n0 = stb__perlin_lerp(n00,n01,v); + n1 = stb__perlin_lerp(n10,n11,v); + + return stb__perlin_lerp(n0,n1,u); +} +#endif // STB_PERLIN_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +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. +------------------------------------------------------------------------------ +*/ +/*** End of inlined file: stb_perlin.h ***/ + + +#pragma endregion + +#pragma endregion + +#pragma region extract image data functions + +RF_API int rf_image_size(rf_image image) +{ + return rf_pixel_buffer_size(image.width, image.height, image.format); +} + +RF_API int rf_image_size_in_format(rf_image image, rf_pixel_format format) +{ + return image.width * image.height * rf_bytes_per_pixel(format); +} + +RF_API bool rf_image_get_pixels_as_rgba32_to_buffer(rf_image image, rf_color* dst, rf_int dst_size) +{ + bool success = false; + + if (rf_is_uncompressed_format(image.format)) + { + if (image.format == RF_UNCOMPRESSED_R32 || image.format == RF_UNCOMPRESSED_R32G32B32 || image.format == RF_UNCOMPRESSED_R32G32B32A32) + { + RF_LOG(RF_LOG_TYPE_WARNING, "32bit pixel format converted to 8bit per channel."); + } + + success = rf_format_pixels_to_rgba32(image.data, rf_image_size(image), image.format, dst, dst_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Function only works for uncompressed formats but was called with format %d.", image.format); + + return success; +} + +RF_API bool rf_image_get_pixels_as_normalized_to_buffer(rf_image image, rf_vec4* dst, rf_int dst_size) +{ + bool success = false; + + if (rf_is_uncompressed_format(image.format)) + { + if ((image.format == RF_UNCOMPRESSED_R32) || (image.format == RF_UNCOMPRESSED_R32G32B32) || (image.format == RF_UNCOMPRESSED_R32G32B32A32)) + { + RF_LOG(RF_LOG_TYPE_WARNING, "32bit pixel format converted to 8bit per channel"); + } + + success = rf_format_pixels_to_normalized(image.data, rf_image_size(image), RF_UNCOMPRESSED_R32G32B32A32, dst, dst_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Function only works for uncompressed formats but was called with format %d.", image.format); + + return success; +} + +// Get pixel data from image in the form of rf_color struct array +RF_API rf_color* rf_image_pixels_to_rgba32(rf_image image, rf_allocator allocator) +{ + rf_color* result = NULL; + + if (rf_is_uncompressed_format(image.format)) + { + int size = image.width * image.height * sizeof(rf_color); + result = RF_ALLOC(allocator, size); + + if (result) + { + bool success = rf_image_get_pixels_as_rgba32_to_buffer(image, result, size); + RF_ASSERT(success); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Allocation of size %d failed.", size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Function only works for uncompressed formats but was called with format %d.", image.format); + + return result; +} + +// Get pixel data from image as rf_vec4 array (float normalized) +RF_API rf_vec4* rf_image_compute_pixels_to_normalized(rf_image image, rf_allocator allocator) +{ + rf_vec4* result = NULL; + + if (rf_is_compressed_format(image.format)) + { + int size = image.width * image.height * sizeof(rf_color); + result = RF_ALLOC(allocator, size); + + if (result) + { + bool success = rf_image_get_pixels_as_normalized_to_buffer(image, result, size); + RF_ASSERT(success); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Allocation of size %d failed.", size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Function only works for uncompressed formats but was called with format %d.", image.format); + + return result; +} + +// Extract color palette from image to maximum size +RF_API void rf_image_extract_palette_to_buffer(rf_image image, rf_color* palette_dst, rf_int palette_size) +{ + if (rf_is_uncompressed_format(image.format)) + { + if (palette_size > 0) + { + int img_size = rf_image_size(image); + int img_bpp = rf_bytes_per_pixel(image.format); + const unsigned char* img_data = (unsigned char*) image.data; + + for (rf_int img_iter = 0, palette_iter = 0; img_iter < img_size && palette_iter < palette_size; img_iter += img_bpp) + { + rf_color color = rf_format_one_pixel_to_rgba32(img_data, image.format); + + bool color_found = false; + + for (rf_int i = 0; i < palette_iter; i++) + { + if (rf_color_match(palette_dst[i], color)) + { + color_found = true; + break; + } + } + + if (!color_found) + { + palette_dst[palette_iter] = color; + palette_iter++; + } + } + } + else RF_LOG(RF_LOG_TYPE_WARNING, "Palette size was 0."); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Function only works for uncompressed formats but was called with format %d.", image.format); +} + +RF_API rf_palette rf_image_extract_palette(rf_image image, rf_int palette_size, rf_allocator allocator) +{ + rf_palette result = {0}; + + if (rf_is_uncompressed_format(image.format)) + { + rf_color* dst = RF_ALLOC(allocator, sizeof(rf_color) * palette_size); + rf_image_extract_palette_to_buffer(image, dst, palette_size); + + result.colors = dst; + result.count = palette_size; + } + + return result; +} + +// Get image alpha border rectangle +RF_API rf_rec rf_image_alpha_border(rf_image image, float threshold) +{ + rf_rec crop = {0}; + + if (rf_is_uncompressed_format(image.format)) + { + int x_min = 65536; // Define a big enough number + int x_max = 0; + int y_min = 65536; + int y_max = 0; + + int src_bpp = rf_bytes_per_pixel(image.format); + int src_size = rf_image_size(image); + unsigned char* src = image.data; + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + int src_pos = (y * image.width + x) * src_bpp; + + rf_color rgba32_pixel = rf_format_one_pixel_to_rgba32(&src[src_pos], image.format); + + if (rgba32_pixel.a > (unsigned char)(threshold * 255.0f)) + { + if (x < x_min) x_min = x; + if (x > x_max) x_max = x; + if (y < y_min) y_min = y; + if (y > y_max) y_max = y; + } + } + } + + crop = (rf_rec) { x_min, y_min, (x_max + 1) - x_min, (y_max + 1) - y_min }; + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Function only works for uncompressed formats but was called with format %d.", image.format); + + return crop; +} + +#pragma endregion + +#pragma region loading and unloading functions + +RF_API bool rf_supports_image_file_type(const char* filename) +{ + return rf_is_file_extension(filename, ".png") + || rf_is_file_extension(filename, ".bmp") + || rf_is_file_extension(filename, ".tga") + || rf_is_file_extension(filename, ".pic") + || rf_is_file_extension(filename, ".psd") + || rf_is_file_extension(filename, ".hdr"); +} + +RF_API rf_image rf_load_image_from_file_data_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size, rf_desired_channels channels, rf_allocator temp_allocator) +{ + if (src == NULL || src_size <= 0) { RF_LOG_ERROR(RF_BAD_ARGUMENT, "Argument `image` was invalid."); return (rf_image){0}; } + + rf_image result = {0}; + + int img_width = 0; + int img_height = 0; + int img_bpp = 0; + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + void* output_buffer = stbi_load_from_memory(src, src_size, &img_width, &img_height, &img_bpp, channels); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + if (output_buffer) + { + int output_buffer_size = img_width * img_height * img_bpp; + + if (dst_size >= output_buffer_size) + { + result.data = dst; + result.width = img_width; + result.height = img_height; + result.valid = true; + + switch (img_bpp) + { + case 1: result.format = RF_UNCOMPRESSED_GRAYSCALE; break; + case 2: result.format = RF_UNCOMPRESSED_GRAY_ALPHA; break; + case 3: result.format = RF_UNCOMPRESSED_R8G8B8; break; + case 4: result.format = RF_UNCOMPRESSED_R8G8B8A8; break; + default: break; + } + + memcpy(dst, output_buffer, output_buffer_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Buffer is not big enough", img_width, img_height, img_bpp); + + RF_FREE(temp_allocator, output_buffer); + } + else RF_LOG_ERROR(RF_STBI_FAILED, "File format not supported or could not be loaded. STB Image returned { x: %d, y: %d, channels: %d }", img_width, img_height, img_bpp); + + return result; +} + +RF_API rf_image rf_load_image_from_file_data(const void* src, rf_int src_size, rf_allocator allocator, rf_allocator temp_allocator) +{ + // Preconditions + if (!src || src_size <= 0) + { + RF_LOG_ERROR(RF_BAD_ARGUMENT, "Argument `src` was null."); + return (rf_image) {0}; + } + + // Compute the result + rf_image result = {0}; + + // Use stb image with the `temp_allocator` to decompress the image and get it's data + int width = 0, height = 0, channels = 0; + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + void* stbi_result = stbi_load_from_memory(src, src_size, &width, &height, &channels, RF_ANY_CHANNELS); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + if (stbi_result && channels) + { + // Allocate a result buffer using the `allocator` and copy the data to it + int stbi_result_size = width * height * channels; + void* result_buffer = RF_ALLOC(allocator, stbi_result_size); + + if (result_buffer) + { + result.data = result_buffer; + result.width = width; + result.height = height; + result.valid = true; + + // Set the format appropriately depending on the `channels` count + switch (channels) + { + case 1: result.format = RF_UNCOMPRESSED_GRAYSCALE; break; + case 2: result.format = RF_UNCOMPRESSED_GRAY_ALPHA; break; + case 3: result.format = RF_UNCOMPRESSED_R8G8B8; break; + case 4: result.format = RF_UNCOMPRESSED_R8G8B8A8; break; + default: break; + } + + memcpy(result_buffer, stbi_result, stbi_result_size); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Buffer is not big enough", width, height, channels); + + // Free the temp buffer allocated by stbi + RF_FREE(temp_allocator, stbi_result); + } + else RF_LOG_ERROR(RF_STBI_FAILED, "File format not supported or could not be loaded. STB Image returned { x: %d, y: %d, channels: %d }", width, height, channels); + + return result; +} + +RF_API rf_image rf_load_image_from_hdr_file_data_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size, rf_desired_channels channels, rf_allocator temp_allocator) +{ + rf_image result = {0}; + + if (src && src_size > 0) + { + int img_width = 0, img_height = 0, img_bpp = 0; + + // NOTE: Using stb_image to load images (Supports multiple image formats) + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + void* output_buffer = stbi_load_from_memory(src, src_size, &img_width, &img_height, &img_bpp, channels); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + if (output_buffer) + { + int output_buffer_size = img_width * img_height * img_bpp; + + if (dst_size >= output_buffer_size) + { + result.data = dst; + result.width = img_width; + result.height = img_height; + result.valid = true; + + if (img_bpp == 1) result.format = RF_UNCOMPRESSED_R32; + else if (img_bpp == 3) result.format = RF_UNCOMPRESSED_R32G32B32; + else if (img_bpp == 4) result.format = RF_UNCOMPRESSED_R32G32B32A32; + + memcpy(dst, output_buffer, output_buffer_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Buffer is not big enough", img_width, img_height, img_bpp); + + RF_FREE(temp_allocator, output_buffer); + } + else RF_LOG_ERROR(RF_STBI_FAILED, "File format not supported or could not be loaded. STB Image returned { x: %d, y: %d, channels: %d }", img_width, img_height, img_bpp); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Argument `image` was invalid."); + + return result; +} + +RF_API rf_image rf_load_image_from_hdr_file_data(const void* src, rf_int src_size, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_image result = {0}; + + if (src && src_size) + { + int width = 0, height = 0, bpp = 0; + + // NOTE: Using stb_image to load images (Supports multiple image formats) + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + void* stbi_result = stbi_load_from_memory(src, src_size, &width, &height, &bpp, RF_ANY_CHANNELS); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + if (stbi_result && bpp) + { + int stbi_result_size = width * height * bpp; + void* result_buffer = RF_ALLOC(allocator, stbi_result_size); + + if (result_buffer) + { + result.data = result_buffer; + result.width = width; + result.height = height; + result.valid = true; + + if (bpp == 1) result.format = RF_UNCOMPRESSED_R32; + else if (bpp == 3) result.format = RF_UNCOMPRESSED_R32G32B32; + else if (bpp == 4) result.format = RF_UNCOMPRESSED_R32G32B32A32; + + memcpy(result_buffer, stbi_result, stbi_result_size); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Buffer is not big enough", width, height, bpp); + + RF_FREE(temp_allocator, stbi_result); + } + else RF_LOG_ERROR(RF_STBI_FAILED, "File format not supported or could not be loaded. STB Image returned { x: %d, y: %d, channels: %d }", width, height, bpp); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Argument `image` was invalid."); + + return result; +} + +RF_API rf_image rf_load_image_from_format_to_buffer(const void* src, rf_int src_size, int src_width, int src_height, rf_uncompressed_pixel_format src_format, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format) +{ + rf_image result = {0}; + + if (rf_is_uncompressed_format(dst_format)) + { + result = (rf_image) { + .data = dst, + .width = src_width, + .height = src_height, + .format = dst_format, + }; + + bool success = rf_format_pixels(src, src_size, src_format, dst, dst_size, dst_format); + RF_ASSERT(success); + } + + return result; +} + +RF_API rf_image rf_load_image_from_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_image image = {0}; + + if (rf_supports_image_file_type(filename)) + { + int file_size = RF_FILE_SIZE(io, filename); + + if (file_size > 0) + { + unsigned char* image_file_buffer = RF_ALLOC(temp_allocator, file_size); + + if (image_file_buffer) + { + if (RF_READ_FILE(io, filename, image_file_buffer, file_size)) + { + if (rf_is_file_extension(filename, ".hdr")) + { + image = rf_load_image_from_hdr_file_data(image_file_buffer, file_size, allocator, temp_allocator); + } + else + { + image = rf_load_image_from_file_data(image_file_buffer, file_size, allocator, temp_allocator); + } + } + else RF_LOG_ERROR(RF_BAD_IO, "File size for %s is 0", filename); + + RF_FREE(temp_allocator, image_file_buffer); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Temporary allocation of size %d failed", file_size); + } + else RF_LOG_ERROR(RF_BAD_IO, "File size for %s is 0", filename); + } + else RF_LOG_ERROR(RF_UNSUPPORTED, "Image fileformat not supported", filename); + + return image; +} + +// Unloads the image using its allocator +RF_API void rf_unload_image(rf_image image, rf_allocator allocator) +{ + RF_FREE(allocator, image.data); +} + +#pragma endregion + +#pragma region image manipulation + +/** + * Copy an existing image into a buffer. + * @param image a valid image to copy from. + * @param dst a buffer for the resulting image. + * @param dst_size size of the `dst` buffer. + * @return a deep copy of the image into the provided `dst` buffer. + */ +RF_API rf_image rf_image_copy_to_buffer(rf_image image, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + int width = image.width; + int height = image.height; + int size = width * height * rf_bytes_per_pixel(image.format); + + if (dst_size >= size) + { + memcpy(dst, image.data, size); + + result.data = dst; + result.width = image.width; + result.height = image.height; + result.format = image.format; + result.valid = true; + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Destination buffer is too small. Expected at least %d bytes but was %d", size, dst_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image was invalid."); + + return result; +} + +/** + * Copy an existing image. + * @param image a valid image to copy from. + * @param allocator used to allocate the new buffer for the resulting image. + * @return a deep copy of the image. + */ +RF_API rf_image rf_image_copy(rf_image image, rf_allocator allocator) +{ + rf_image result = {0}; + + if (image.valid) + { + int size = image.width * image.height * rf_bytes_per_pixel(image.format); + void* dst = RF_ALLOC(allocator, size); + + if (dst) + { + result = rf_image_copy_to_buffer(image, dst, size); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Failed to allocate %d bytes", size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image was invalid."); + + return result; +} + +/** + * Crop an image and store the result in a provided buffer. + * @param image a valid image that we crop from. + * @param crop a rectangle representing which part of the image to crop. + * @param dst a buffer for the resulting image. Must be of size at least `rf_pixel_buffer_size(RF_UNCOMPRESSED_R8G8B8A8, crop.width, crop.height)`. + * @param dst_size size of the `dst` buffer. + * @return a cropped image using the `dst` buffer in the same format as `image`. + */ +RF_API rf_image rf_image_crop_to_buffer(rf_image image, rf_rec crop, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format) +{ + rf_image result = {0}; + + if (image.valid) + { + // Security checks to validate crop rectangle + if (crop.x < 0) { crop.width += crop.x; crop.x = 0; } + if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } + if ((crop.x + crop.width) > image.width) { crop.width = image.width - crop.x; } + if ((crop.y + crop.height) > image.height) { crop.height = image.height - crop.y; } + + if ((crop.x < image.width) && (crop.y < image.height)) + { + int expected_size = rf_pixel_buffer_size(crop.width, crop.height, dst_format); + if (dst_size >= expected_size) + { + rf_pixel_format src_format = image.format; + int src_size = rf_image_size(image); + + unsigned char* src_ptr = image.data; + unsigned char* dst_ptr = dst; + + int src_bpp = rf_bytes_per_pixel(image.format); + int dst_bpp = rf_bytes_per_pixel(dst_format); + + int crop_y = crop.y; + int crop_h = crop.height; + int crop_x = crop.x; + int crop_w = crop.width; + + for (rf_int y = 0; y < crop_h; y++) + { + for (rf_int x = 0; x < crop_w; x++) + { + int src_x = x + crop_x; + int src_y = y + crop_y; + + int src_pixel = (src_y * image.width + src_x) * src_bpp; + int dst_pixel = (y * crop_w + x) * src_bpp; + RF_ASSERT(src_pixel < src_size); + RF_ASSERT(dst_pixel < dst_size); + + rf_format_one_pixel(&src_ptr[src_pixel], src_format, &dst_ptr[dst_pixel], dst_format); + } + } + + result.data = dst; + result.format = dst_format; + result.width = crop.width; + result.height = crop.height; + result.valid = true; + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Destination buffer is too small. Expected at least %d bytes but was %d", expected_size, dst_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image can not be cropped, crop rectangle out of bounds."); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +/** + * Crop an image and store the result in a provided buffer. + * @param image a valid image that we crop from. + * @param crop a rectangle representing which part of the image to crop. + * @param dst a buffer for the resulting image. Must be of size at least `rf_pixel_buffer_size(RF_UNCOMPRESSED_R8G8B8A8, crop.width, crop.height)`. + * @param dst_size size of the `dst` buffer. + * @return a cropped image using the `dst` buffer in the same format as `image`. + */ +RF_API rf_image rf_image_crop(rf_image image, rf_rec crop, rf_allocator allocator) +{ + rf_image result = {0}; + + if (image.valid) + { + int size = rf_pixel_buffer_size(crop.width, crop.height, image.format); + void* dst = RF_ALLOC(allocator, size); + + if (dst) + { + result = rf_image_crop_to_buffer(image, crop, dst, size, image.format); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Allocation of size %d failed.", size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +RF_INTERNAL int rf_format_to_stb_channel_count(rf_pixel_format format) +{ + switch (format) + { + case RF_UNCOMPRESSED_GRAYSCALE: return 1; + case RF_UNCOMPRESSED_GRAY_ALPHA: return 2; + case RF_UNCOMPRESSED_R8G8B8: return 3; + case RF_UNCOMPRESSED_R8G8B8A8: return 4; + default: return 0; + } +} + +// Resize and image to new size. +// Note: Uses stb default scaling filters (both bicubic): STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL (high-quality Catmull-Rom) +RF_API rf_image rf_image_resize_to_buffer(rf_image image, int new_width, int new_height, void* dst, rf_int dst_size, rf_allocator temp_allocator) +{ + if (!image.valid || dst_size < new_width * new_height * rf_bytes_per_pixel(image.format)) return (rf_image){0}; + + rf_image result = {0}; + + int stb_format = rf_format_to_stb_channel_count(image.format); + + if (stb_format) + { + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + stbir_resize_uint8((unsigned char*) image.data, image.width, image.height, 0, (unsigned char*) dst, new_width, new_height, 0, stb_format); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + result.data = dst; + result.width = new_width; + result.height = new_height; + result.format = image.format; + result.valid = true; + } + else // if the format of the image is not supported by stbir + { + int pixels_size = image.width * image.height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + rf_color* pixels = RF_ALLOC(temp_allocator, pixels_size); + + if (pixels) + { + bool format_success = rf_format_pixels_to_rgba32(image.data, rf_image_size(image), image.format, pixels, pixels_size); + RF_ASSERT(format_success); + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + stbir_resize_uint8((unsigned char*)pixels, image.width, image.height, 0, (unsigned char*) dst, new_width, new_height, 0, 4); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + format_success = rf_format_pixels(pixels, pixels_size, RF_UNCOMPRESSED_R8G8B8A8, dst, dst_size, image.format); + RF_ASSERT(format_success); + + result.data = dst; + result.width = new_width; + result.height = new_height; + result.format = image.format; + result.valid = true; + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Allocation of size %d failed.", image.width * image.height * sizeof(rf_color)); + + RF_FREE(temp_allocator, pixels); + } + + return result; +} + +RF_API rf_image rf_image_resize(rf_image image, int new_width, int new_height, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_image result = {0}; + + if (image.valid) + { + int dst_size = new_width * new_height * rf_bytes_per_pixel(image.format); + void* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_image_resize_to_buffer(image, new_width, new_height, dst, dst_size, temp_allocator); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Allocation of size %d failed.", dst_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +/** + * Resize and image to new size using Nearest-Neighbor scaling algorithm + * @param image + * @param new_width + * @param new_height + * @param dst + * @param dst_size + * @return a resized version of the `image`, with the `dst` buffer, in the same format. + */ +RF_API rf_image rf_image_resize_nn_to_buffer(rf_image image, int new_width, int new_height, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + int bpp = rf_bytes_per_pixel(image.format); + int expected_size = new_width * new_height * bpp; + + if (dst_size >= expected_size) + { + // EDIT: added +1 to account for an early rounding problem + int x_ratio = (int)((image.width << 16) / new_width ) + 1; + int y_ratio = (int)((image.height << 16) / new_height) + 1; + + unsigned char* src = image.data; + + int x2, y2; + for (rf_int y = 0; y < new_height; y++) + { + for (rf_int x = 0; x < new_width; x++) + { + x2 = ((x * x_ratio) >> 16); + y2 = ((y * y_ratio) >> 16); + + rf_format_one_pixel(src + ((y2 * image.width) + x2) * bpp, image.format, + ((unsigned char *) dst) + ((y * new_width) + x) * bpp, image.format); + } + } + + result.data = dst; + result.width = new_width; + result.height = new_height; + result.format = image.format; + result.valid = true; + } + else RF_LOG_ERROR(RF_BAD_BUFFER_SIZE, "Expected `dst` to be at least %d bytes but was %d bytes", expected_size, dst_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +RF_API rf_image rf_image_resize_nn(rf_image image, int new_width, int new_height, rf_allocator allocator) +{ + rf_image result = {0}; + + if (image.valid) + { + int dst_size = new_width * new_height * rf_bytes_per_pixel(image.format); + void* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_image_resize_nn_to_buffer(image, new_width, new_height, dst, dst_size); + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Allocation of size %d failed.", dst_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +// Convert image data to desired format +RF_API rf_image rf_image_format_to_buffer(rf_image image, rf_uncompressed_pixel_format dst_format, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (rf_is_uncompressed_format(dst_format) && rf_is_uncompressed_format(image.format)) + { + bool success = rf_format_pixels(image.data, rf_image_size(image), image.format, dst, dst_size, dst_format); + RF_ASSERT(success); + + result = (rf_image) + { + .data = dst, + .width = image.width, + .height = image.height, + .format = dst_format, + .valid = true, + }; + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Cannot format compressed pixel formats. Image format: %d, Destination format: %d.", image.format, dst_format); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +RF_API rf_image rf_image_format(rf_image image, rf_uncompressed_pixel_format new_format, rf_allocator allocator) +{ + rf_image result = {0}; + + if (image.valid) + { + if (rf_is_uncompressed_format(new_format) && rf_is_uncompressed_format(image.format)) + { + int dst_size = image.width * image.height * rf_bytes_per_pixel(image.format); + void* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + bool format_success = rf_format_pixels(image.data, rf_image_size(image), image.format, dst, dst_size, new_format); + RF_ASSERT(format_success); + + result = (rf_image) + { + .data = dst, + .width = image.width, + .height = image.height, + .format = new_format, + .valid = true, + }; + } + else RF_LOG_ERROR(RF_BAD_ALLOC, "Allocation of size %d failed.", dst_size); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Cannot format compressed pixel formats. `image.format`: %d, `dst_format`: %d", image.format, new_format); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +// Apply alpha mask to image. Note 1: Returned image is GRAY_ALPHA (16bit) or RGBA (32bit). Note 2: alphaMask should be same size as image +RF_API rf_image rf_image_alpha_mask_to_buffer(rf_image image, rf_image alpha_mask, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid && alpha_mask.valid) + { + if (image.width == alpha_mask.width && image.height == alpha_mask.height) + { + if (rf_is_compressed_format(image.format) && alpha_mask.format == RF_UNCOMPRESSED_GRAYSCALE) + { + if (dst_size >= rf_image_size_in_format(image, RF_UNCOMPRESSED_GRAY_ALPHA)) + { + // Apply alpha mask to alpha channel + for (rf_int i = 0; i < image.width * image.height; i++) + { + unsigned char mask_pixel = 0; + rf_format_one_pixel(((unsigned char*)alpha_mask.data) + i, alpha_mask.format, &mask_pixel, RF_UNCOMPRESSED_GRAYSCALE); + + // Todo: Finish implementing this function + //((unsigned char*)dst)[k] = mask_pixel; + } + + result.data = dst; + result.width = image.width; + result.height = image.height; + result.format = RF_UNCOMPRESSED_GRAY_ALPHA; + result.valid = true; + } + } else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Expected compressed pixel formats. `image.format`: %d, `alpha_mask.format`: %d", image.format, alpha_mask.format); + } else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Alpha mask must be same size as image but was w: %d, h: %d", alpha_mask.width, alpha_mask.height); + } else RF_LOG_ERROR(RF_BAD_ARGUMENT, "One image was invalid. `image.valid`: %d, `alpha_mask.valid`: %d", image.valid, alpha_mask.valid); + + return result; +} + +// Clear alpha channel to desired color. Note: Threshold defines the alpha limit, 0.0f to 1.0f +RF_API rf_image rf_image_alpha_clear(rf_image image, rf_color color, float threshold, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_image result = {0}; + + if (image.valid) + { + rf_color* pixels = rf_image_pixels_to_rgba32(image, temp_allocator); + + if (pixels) + { + for (rf_int i = 0; i < image.width * image.height; i++) + { + if (pixels[i].a <= (unsigned char)(threshold * 255.0f)) + { + pixels[i] = color; + } + } + + rf_image temp_image = { + .data = pixels, + .width = image.width, + .height = image.height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true + }; + + result = rf_image_format(temp_image, image.format, allocator); + } + + RF_FREE(temp_allocator, pixels); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +// Premultiply alpha channel +RF_API rf_image rf_image_alpha_premultiply(rf_image image, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_image result = {0}; + + if (image.valid) + { + float alpha = 0.0f; + rf_color* pixels = rf_image_pixels_to_rgba32(image, temp_allocator); + + if (pixels) + { + for (rf_int i = 0; i < image.width * image.height; i++) + { + alpha = (float)pixels[i].a / 255.0f; + pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); + pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); + pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); + } + + rf_image temp_image = { + .data = pixels, + .width = image.width, + .height = image.height, + .format = RF_UNCOMPRESSED_RGBA32, + .valid = true, + }; + + result = rf_image_format(temp_image, image.format, allocator); + } + + RF_FREE(temp_allocator, pixels); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Image is invalid."); + + return result; +} + +RF_API rf_rec rf_image_alpha_crop_rec(rf_image image, float threshold) +{ + if (!image.valid) return (rf_rec){0}; + + int bpp = rf_bytes_per_pixel(image.format); + + int x_min = INT_MAX; + int x_max = 0; + int y_min = INT_MAX; + int y_max = 0; + + char* src = image.data; + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + int pixel = (y * image.width + x) * bpp; + rf_color pixel_rgba32 = rf_format_one_pixel_to_rgba32(&src[pixel], image.format); + + if (pixel_rgba32.a > (unsigned char)(threshold * 255.0f)) + { + if (x < x_min) x_min = x; + if (x > x_max) x_max = x; + if (y < y_min) y_min = y; + if (y > y_max) y_max = y; + } + } + } + + return (rf_rec) { x_min, y_min, (x_max + 1) - x_min, (y_max + 1) - y_min }; +} + +// Crop image depending on alpha value +RF_API rf_image rf_image_alpha_crop(rf_image image, float threshold, rf_allocator allocator) +{ + rf_rec crop = rf_image_alpha_crop_rec(image, threshold); + + return rf_image_crop(image, crop, allocator); +} + +// Dither image data to 16bpp or lower (Floyd-Steinberg dithering) Note: In case selected bpp do not represent an known 16bit format, dithered data is stored in the LSB part of the unsigned short +RF_API rf_image rf_image_dither(const rf_image image, int r_bpp, int g_bpp, int b_bpp, int a_bpp, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_image result = {0}; + + if (image.valid) + { + if (image.format == RF_UNCOMPRESSED_R8G8B8) + { + if (image.format == RF_UNCOMPRESSED_R8G8B8 && (r_bpp + g_bpp + b_bpp + a_bpp) < 16) + { + rf_color* pixels = rf_image_pixels_to_rgba32(image, temp_allocator); + + if ((image.format != RF_UNCOMPRESSED_R8G8B8) && (image.format != RF_UNCOMPRESSED_R8G8B8A8)) + { + RF_LOG(RF_LOG_TYPE_WARNING, "rf_image format is already 16bpp or lower, dithering could have no effect"); + } + + // Todo: Finish implementing this function +// // Define new image format, check if desired bpp match internal known format +// if ((r_bpp == 5) && (g_bpp == 6) && (b_bpp == 5) && (a_bpp == 0)) image.format = RF_UNCOMPRESSED_R5G6B5; +// else if ((r_bpp == 5) && (g_bpp == 5) && (b_bpp == 5) && (a_bpp == 1)) image.format = RF_UNCOMPRESSED_R5G5B5A1; +// else if ((r_bpp == 4) && (g_bpp == 4) && (b_bpp == 4) && (a_bpp == 4)) image.format = RF_UNCOMPRESSED_R4G4B4A4; +// else +// { +// image.format = 0; +// RF_LOG(RF_LOG_TYPE_WARNING, "Unsupported dithered OpenGL internal format: %ibpp (R%i_g%i_b%i_a%i)", (r_bpp + g_bpp + b_bpp + a_bpp), r_bpp, g_bpp, b_bpp, a_bpp); +// } +// +// // NOTE: We will store the dithered data as unsigned short (16bpp) +// image.data = (unsigned short*) RF_ALLOC(image.allocator, image.width * image.height * sizeof(unsigned short)); + + rf_color old_pixel = RF_WHITE; + rf_color new_pixel = RF_WHITE; + + int r_error, g_error, b_error; + unsigned short r_pixel, g_pixel, b_pixel, a_pixel; // Used for 16bit pixel composition + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + old_pixel = pixels[y * image.width + x]; + + // NOTE: New pixel obtained by bits truncate, it would be better to round values (check rf_image_format()) + new_pixel.r = old_pixel.r >> (8 - r_bpp); // R bits + new_pixel.g = old_pixel.g >> (8 - g_bpp); // G bits + new_pixel.b = old_pixel.b >> (8 - b_bpp); // B bits + new_pixel.a = old_pixel.a >> (8 - a_bpp); // A bits (not used on dithering) + + // NOTE: Error must be computed between new and old pixel but using same number of bits! + // We want to know how much color precision we have lost... + r_error = (int)old_pixel.r - (int)(new_pixel.r << (8 - r_bpp)); + g_error = (int)old_pixel.g - (int)(new_pixel.g << (8 - g_bpp)); + b_error = (int)old_pixel.b - (int)(new_pixel.b << (8 - b_bpp)); + + pixels[y*image.width + x] = new_pixel; + + // NOTE: Some cases are out of the array and should be ignored + if (x < (image.width - 1)) + { + pixels[y*image.width + x+1].r = rf_min_i((int)pixels[y * image.width + x + 1].r + (int)((float)r_error * 7.0f / 16), 0xff); + pixels[y*image.width + x+1].g = rf_min_i((int)pixels[y * image.width + x + 1].g + (int)((float)g_error * 7.0f / 16), 0xff); + pixels[y*image.width + x+1].b = rf_min_i((int)pixels[y * image.width + x + 1].b + (int)((float)b_error * 7.0f / 16), 0xff); + } + + if ((x > 0) && (y < (image.height - 1))) + { + pixels[(y+1)*image.width + x-1].r = rf_min_i((int)pixels[(y + 1) * image.width + x - 1].r + (int)((float)r_error * 3.0f / 16), 0xff); + pixels[(y+1)*image.width + x-1].g = rf_min_i((int)pixels[(y + 1) * image.width + x - 1].g + (int)((float)g_error * 3.0f / 16), 0xff); + pixels[(y+1)*image.width + x-1].b = rf_min_i((int)pixels[(y + 1) * image.width + x - 1].b + (int)((float)b_error * 3.0f / 16), 0xff); + } + + if (y < (image.height - 1)) + { + pixels[(y+1)*image.width + x].r = rf_min_i((int)pixels[(y+1)*image.width + x].r + (int)((float)r_error*5.0f/16), 0xff); + pixels[(y+1)*image.width + x].g = rf_min_i((int)pixels[(y+1)*image.width + x].g + (int)((float)g_error*5.0f/16), 0xff); + pixels[(y+1)*image.width + x].b = rf_min_i((int)pixels[(y+1)*image.width + x].b + (int)((float)b_error*5.0f/16), 0xff); + } + + if ((x < (image.width - 1)) && (y < (image.height - 1))) + { + pixels[(y+1)*image.width + x+1].r = rf_min_i((int)pixels[(y+1)*image.width + x+1].r + (int)((float)r_error*1.0f/16), 0xff); + pixels[(y+1)*image.width + x+1].g = rf_min_i((int)pixels[(y+1)*image.width + x+1].g + (int)((float)g_error*1.0f/16), 0xff); + pixels[(y+1)*image.width + x+1].b = rf_min_i((int)pixels[(y+1)*image.width + x+1].b + (int)((float)b_error*1.0f/16), 0xff); + } + + r_pixel = (unsigned short)new_pixel.r; + g_pixel = (unsigned short)new_pixel.g; + b_pixel = (unsigned short)new_pixel.b; + a_pixel = (unsigned short)new_pixel.a; + + ((unsigned short *)image.data)[y*image.width + x] = (r_pixel << (g_bpp + b_bpp + a_bpp)) | (g_pixel << (b_bpp + a_bpp)) | (b_pixel << a_bpp) | a_pixel; + } + } + + RF_FREE(temp_allocator, pixels); + } + else RF_LOG_ERROR(RF_BAD_ARGUMENT, "Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (r_bpp + g_bpp + b_bpp + a_bpp)); + } + } + + return result; +} + +// Flip image vertically +RF_API rf_image rf_image_flip_vertical_to_buffer(rf_image image, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (dst_size >= rf_image_size(image)) + { + int bpp = rf_bytes_per_pixel(image.format); + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + void* dst_pixel = ((unsigned char*)dst) + (y * image.width + x) * bpp; + void* src_pixel = ((unsigned char*)image.data) + ((image.height - 1 - y) * image.width + x) * bpp; + + memcpy(dst_pixel, src_pixel, bpp); + } + } + + result = image; + result.data = dst; + } + } + + return result; +} + +RF_API rf_image rf_image_flip_vertical(rf_image image, rf_allocator allocator) +{ + if (!image.valid) return (rf_image) {0}; + + int size = rf_image_size(image); + void* dst = RF_ALLOC(allocator, size); + + rf_image result = rf_image_flip_vertical_to_buffer(image, dst, size); + if (!result.valid) RF_FREE(allocator, dst); + + return result; +} + +// Flip image horizontally +RF_API rf_image rf_image_flip_horizontal_to_buffer(rf_image image, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (dst_size >= rf_image_size(image)) + { + int bpp = rf_bytes_per_pixel(image.format); + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + void* dst_pixel = ((unsigned char*)dst) + (y * image.width + x) * bpp; + void* src_pixel = ((unsigned char*)image.data) + (y * image.width + (image.width - 1 - x)) * bpp; + + memcpy(dst_pixel, src_pixel, bpp); + } + } + + result = image; + result.data = dst; + } + } + + return result; +} + +RF_API rf_image rf_image_flip_horizontal(rf_image image, rf_allocator allocator) +{ + if (!image.valid) return (rf_image) {0}; + + int size = rf_image_size(image); + void* dst = RF_ALLOC(allocator, size); + + rf_image result = rf_image_flip_horizontal_to_buffer(image, dst, size); + if (!result.valid) RF_FREE(allocator, dst); + + return result; +} + +// Rotate image clockwise 90deg +RF_API rf_image rf_image_rotate_cw_to_buffer(rf_image image, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (dst_size >= rf_image_size(image)) + { + int bpp = rf_bytes_per_pixel(image.format); + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + void* dst_pixel = ((unsigned char*)dst) + (x * image.height + (image.height - y - 1)) * bpp; + void* src_pixel = ((unsigned char*)image.data) + (y * image.width + x) * bpp; + + memcpy(dst_pixel, src_pixel, bpp); + } + } + + result = image; + result.data = dst; + } + } + + return result; +} + +RF_API rf_image rf_image_rotate_cw(rf_image image) +{ + return rf_image_rotate_cw_to_buffer(image, image.data, rf_image_size(image)); +} + +// Rotate image counter-clockwise 90deg +RF_API rf_image rf_image_rotate_ccw_to_buffer(rf_image image, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (dst_size >= rf_image_size(image)) + { + int bpp = rf_bytes_per_pixel(image.format); + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + void* dst_pixel = ((unsigned char*)dst) + (x * image.height + y) * bpp; + void* src_pixel = ((unsigned char*)image.data) + (y * image.width + (image.width - x - 1)) * bpp; + + memcpy(dst_pixel, src_pixel, bpp); + } + } + + result = image; + result.data = dst; + } + } + + return result; +} + +RF_API rf_image rf_image_rotate_ccw(rf_image image) +{ + return rf_image_rotate_ccw_to_buffer(image, image.data, rf_image_size(image)); +} + +// Modify image color: tint +RF_API rf_image rf_image_color_tint_to_buffer(rf_image image, rf_color color, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (dst_size >= rf_image_size(image)) + { + int bpp = rf_bytes_per_pixel(image.format); + + float c_r = ((float) color.r) / 255.0f; + float c_g = ((float) color.g) / 255.0f; + float c_b = ((float) color.b) / 255.0f; + float c_a = ((float) color.a) / 255.0f; + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + int index = y * image.width + x; + void* src_pixel = ((unsigned char*)image.data) + index * bpp; + void* dst_pixel = ((unsigned char*)image.data) + index * bpp; + + rf_color pixel_rgba32 = rf_format_one_pixel_to_rgba32(src_pixel, image.format); + + pixel_rgba32.r = (unsigned char) (255.f * (((float)pixel_rgba32.r) / 255.f * c_r)); + pixel_rgba32.g = (unsigned char) (255.f * (((float)pixel_rgba32.g) / 255.f * c_g)); + pixel_rgba32.b = (unsigned char) (255.f * (((float)pixel_rgba32.b) / 255.f * c_b)); + pixel_rgba32.a = (unsigned char) (255.f * (((float)pixel_rgba32.a) / 255.f * c_a)); + + rf_format_one_pixel(&pixel_rgba32, RF_UNCOMPRESSED_R8G8B8A8, dst_pixel, image.format); + } + } + + result = image; + result.data = dst; + } + } + + return result; +} + +RF_API rf_image rf_image_color_tint(rf_image image, rf_color color) +{ + return rf_image_color_tint_to_buffer(image, color, image.data, rf_image_size(image)); +} + +// Modify image color: invert +RF_API rf_image rf_image_color_invert_to_buffer(rf_image image, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (dst_size >= rf_image_size(image)) + { + int bpp = rf_bytes_per_pixel(image.format); + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + int index = y * image.width + x; + void* src_pixel = ((unsigned char*)image.data) + index * bpp; + void* dst_pixel = ((unsigned char*)dst) + index * bpp; + + rf_color pixel_rgba32 = rf_format_one_pixel_to_rgba32(src_pixel, image.format); + pixel_rgba32.r = 255 - pixel_rgba32.r; + pixel_rgba32.g = 255 - pixel_rgba32.g; + pixel_rgba32.b = 255 - pixel_rgba32.b; + + rf_format_one_pixel(&pixel_rgba32, RF_UNCOMPRESSED_R8G8B8A8, dst_pixel, image.format); + } + } + + result = image; + result.data = dst; + } + } + + return result; +} + +RF_API rf_image rf_image_color_invert(rf_image image) +{ + return rf_image_color_invert_to_buffer(image, image.data, rf_image_size(image)); +} + +// Modify image color: grayscale +RF_API rf_image rf_image_color_grayscale_to_buffer(rf_image image, void* dst, rf_int dst_size) +{ + return rf_image_format_to_buffer(image, RF_UNCOMPRESSED_GRAYSCALE, dst, dst_size); +} + +RF_API rf_image rf_image_color_grayscale(rf_image image) +{ + rf_image result = {0}; + + if (image.valid) + { + result = rf_image_color_grayscale_to_buffer(image, image.data, rf_image_size(image)); + } + + return result; +} + +// Modify image color: contrast +// NOTE: Contrast values between -100 and 100 +RF_API rf_image rf_image_color_contrast_to_buffer(rf_image image, float contrast, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (dst_size >= rf_image_size(image)) + { + if (contrast < -100) contrast = -100; + if (contrast > +100) contrast = +100; + + contrast = (100.0f + contrast) / 100.0f; + contrast *= contrast; + + int bpp = rf_bytes_per_pixel(image.format); + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + int index = y * image.width + x; + void* src_pixel = ((unsigned char*)image.data) + index * bpp; + void* dst_pixel = ((unsigned char*)dst) + index * bpp; + + rf_color src_pixel_rgba32 = rf_format_one_pixel_to_rgba32(src_pixel, image.format); + + float p_r = ((float)src_pixel_rgba32.r) / 255.0f; + p_r -= 0.5; + p_r *= contrast; + p_r += 0.5; + p_r *= 255; + if (p_r < 0) p_r = 0; + if (p_r > 255) p_r = 255; + + float p_g = ((float)src_pixel_rgba32.g) / 255.0f; + p_g -= 0.5; + p_g *= contrast; + p_g += 0.5; + p_g *= 255; + if (p_g < 0) p_g = 0; + if (p_g > 255) p_g = 255; + + float p_b = ((float)src_pixel_rgba32.b) / 255.0f; + p_b -= 0.5; + p_b *= contrast; + p_b += 0.5; + p_b *= 255; + if (p_b < 0) p_b = 0; + if (p_b > 255) p_b = 255; + + src_pixel_rgba32.r = (unsigned char)p_r; + src_pixel_rgba32.g = (unsigned char)p_g; + src_pixel_rgba32.b = (unsigned char)p_b; + + rf_format_one_pixel(&src_pixel_rgba32, RF_UNCOMPRESSED_R8G8B8A8, dst_pixel, image.format); + } + } + + result = image; + result.data = dst; + } + } + + return result; +} + +RF_API rf_image rf_image_color_contrast(rf_image image, int contrast) +{ + return rf_image_color_contrast_to_buffer(image, contrast, image.data, rf_image_size(image)); +} + +// Modify image color: brightness +// NOTE: Brightness values between -255 and 255 +RF_API rf_image rf_image_color_brightness_to_buffer(rf_image image, int brightness, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (image.valid) + { + if (dst_size >= rf_image_size(image)) + { + if (brightness < -255) brightness = -255; + if (brightness > +255) brightness = +255; + + int bpp = rf_bytes_per_pixel(image.format); + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + int index = y * image.width + x; + + void* src_pixel = ((unsigned char*)image.data) + index * bpp; + void* dst_pixel = ((unsigned char*)dst) + index * bpp; + + rf_color pixel_rgba32 = rf_format_one_pixel_to_rgba32(src_pixel, image.format); + + int c_r = pixel_rgba32.r + brightness; + int c_g = pixel_rgba32.g + brightness; + int c_b = pixel_rgba32.b + brightness; + + if (c_r < 0) c_r = 1; + if (c_r > 255) c_r = 255; + + if (c_g < 0) c_g = 1; + if (c_g > 255) c_g = 255; + + if (c_b < 0) c_b = 1; + if (c_b > 255) c_b = 255; + + pixel_rgba32.r = (unsigned char) c_r; + pixel_rgba32.g = (unsigned char) c_g; + pixel_rgba32.b = (unsigned char) c_b; + + rf_format_one_pixel(&pixel_rgba32, RF_UNCOMPRESSED_R8G8B8A8, dst_pixel, image.format); + } + } + + result = image; + result.data = dst; + } + } + + return result; +} + +RF_API rf_image rf_image_color_brightness(rf_image image, int brightness) +{ + return rf_image_color_brightness_to_buffer(image, brightness, image.data, rf_image_size(image)); +} + +// Modify image color: replace color +RF_API rf_image rf_image_color_replace_to_buffer(rf_image image, rf_color color, rf_color replace, void* dst, rf_int dst_size) +{ + if (image.valid && dst_size >= rf_image_size(image)) return (rf_image) {0}; + + rf_image result = {0}; + + int bpp = rf_bytes_per_pixel(image.format); + + for (rf_int y = 0; y < image.height; y++) + { + for (rf_int x = 0; x < image.width; x++) + { + int index = y * image.width + x; + + void* src_pixel = ((unsigned char*)image.data) + index * bpp; + void* dst_pixel = ((unsigned char*)dst) + index * bpp; + + rf_color pixel_rgba32 = rf_format_one_pixel_to_rgba32(src_pixel, image.format); + + if (rf_color_match(pixel_rgba32, color)) + { + rf_format_one_pixel(&replace, RF_UNCOMPRESSED_R8G8B8A8, dst_pixel, image.format); + } + } + } + + result = image; + result.data = dst; + + return result; +} + +RF_API rf_image rf_image_color_replace(rf_image image, rf_color color, rf_color replace) +{ + return rf_image_color_replace_to_buffer(image, color, replace, image.data, rf_image_size(image)); +} + +// Generate image: plain color +RF_API rf_image rf_gen_image_color_to_buffer(int width, int height, rf_color color, rf_color* dst, rf_int dst_size) +{ + rf_image result = {0}; + + for (rf_int i = 0; i < dst_size; i++) + { + dst[i] = color; + } + + return (rf_image) { + .data = dst, + .width = width, + .height = height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true, + }; +} + +RF_API rf_image rf_gen_image_color(int width, int height, rf_color color, rf_allocator allocator) +{ + rf_image result = {0}; + + int dst_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + void* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_gen_image_color_to_buffer(width, height, color, dst, width * height); + } + + return result; +} + +// Generate image: vertical gradient +RF_API rf_image rf_gen_image_gradient_v_to_buffer(int width, int height, rf_color top, rf_color bottom, rf_color* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (dst_size >= width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8)) + { + for (rf_int j = 0; j < height; j++) + { + float factor = ((float)j) / ((float)height); + + for (rf_int i = 0; i < width; i++) + { + ((rf_color*)dst)[j * width + i].r = (int)((float)bottom.r * factor + (float)top.r * (1.f - factor)); + ((rf_color*)dst)[j * width + i].g = (int)((float)bottom.g * factor + (float)top.g * (1.f - factor)); + ((rf_color*)dst)[j * width + i].b = (int)((float)bottom.b * factor + (float)top.b * (1.f - factor)); + ((rf_color*)dst)[j * width + i].a = (int)((float)bottom.a * factor + (float)top.a * (1.f - factor)); + } + } + + result = (rf_image) + { + .data = dst, + .width = width, + .height = height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true + }; + } + + return result; +} + +RF_API rf_image rf_gen_image_gradient_v(int width, int height, rf_color top, rf_color bottom, rf_allocator allocator) +{ + rf_image result = {0}; + + int dst_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + rf_color* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_gen_image_gradient_v_to_buffer(width, height, top, bottom, dst, dst_size); + } + + return result; +} + +// Generate image: horizontal gradient +RF_API rf_image rf_gen_image_gradient_h_to_buffer(int width, int height, rf_color left, rf_color right, rf_color* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (dst_size >= width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8)) + { + for (rf_int i = 0; i < width; i++) + { + float factor = ((float)i) / ((float)width); + + for (rf_int j = 0; j < height; j++) + { + ((rf_color*)dst)[j * width + i].r = (int)((float)right.r * factor + (float)left.r * (1.f - factor)); + ((rf_color*)dst)[j * width + i].g = (int)((float)right.g * factor + (float)left.g * (1.f - factor)); + ((rf_color*)dst)[j * width + i].b = (int)((float)right.b * factor + (float)left.b * (1.f - factor)); + ((rf_color*)dst)[j * width + i].a = (int)((float)right.a * factor + (float)left.a * (1.f - factor)); + } + } + + result = (rf_image) + { + .data = dst, + .width = width, + .height = height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true + }; + } + + return result; +} + +RF_API rf_image rf_gen_image_gradient_h(int width, int height, rf_color left, rf_color right, rf_allocator allocator) +{ + rf_image result = {0}; + + int dst_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + rf_color* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_gen_image_gradient_h_to_buffer(width, height, left, right, dst, dst_size); + } + + return result; +} + +// Generate image: radial gradient +RF_API rf_image rf_gen_image_gradient_radial_to_buffer(int width, int height, float density, rf_color inner, rf_color outer, rf_color* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (dst_size >= width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8)) + { + float radius = (width < height) ? ((float)width) / 2.0f : ((float)height) / 2.0f; + + float center_x = ((float)width ) / 2.0f; + float center_y = ((float)height) / 2.0f; + + for (rf_int y = 0; y < height; y++) + { + for (rf_int x = 0; x < width; x++) + { + float dist = hypotf((float)x - center_x, (float)y - center_y); + float factor = (dist - radius * density) / (radius * (1.0f - density)); + + factor = (float)fmax(factor, 0.f); + factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check + + dst[y * width + x].r = (int)((float)outer.r * factor + (float)inner.r * (1.0f - factor)); + dst[y * width + x].g = (int)((float)outer.g * factor + (float)inner.g * (1.0f - factor)); + dst[y * width + x].b = (int)((float)outer.b * factor + (float)inner.b * (1.0f - factor)); + dst[y * width + x].a = (int)((float)outer.a * factor + (float)inner.a * (1.0f - factor)); + } + } + + result = (rf_image) + { + .data = dst, + .width = width, + .height = height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true, + }; + } + + return result; +} + +RF_API rf_image rf_gen_image_gradient_radial(int width, int height, float density, rf_color inner, rf_color outer, rf_allocator allocator) +{ + rf_image result = {0}; + + int dst_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + rf_color* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_gen_image_gradient_radial_to_buffer(width, height, density, inner, outer, dst, dst_size); + } + + return result; +} + +// Generate image: checked +RF_API rf_image rf_gen_image_checked_to_buffer(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2, rf_color* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (dst_size >= width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8)) + { + float radius = (width < height) ? ((float)width) / 2.0f : ((float)height) / 2.0f; + + float center_x = ((float)width ) / 2.0f; + float center_y = ((float)height) / 2.0f; + + for (rf_int y = 0; y < height; y++) + { + for (rf_int x = 0; x < width; x++) + { + if ((x / checks_x + y / checks_y) % 2 == 0) dst[y * width + x] = col1; + else dst[y * width + x] = col2; + } + } + + result = (rf_image) + { + .data = dst, + .width = width, + .height = height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true, + }; + } + + return result; +} + +RF_API rf_image rf_gen_image_checked(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2, rf_allocator allocator) +{ + rf_image result = {0}; + + int dst_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + rf_color* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_gen_image_checked_to_buffer(width, height, checks_x, checks_y, col1, col2, dst, dst_size); + } + + return result; +} + +// Generate image: white noise +RF_API rf_image rf_gen_image_white_noise_to_buffer(int width, int height, float factor, rf_rand_proc rand, rf_color* dst, rf_int dst_size) +{ + int result_image_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + rf_image result = {0}; + + if (dst_size < result_image_size || !rand || result_image_size <= 0) return result; + + for (rf_int i = 0; i < width * height; i++) + { + if (rand(0, 99) < (int)(factor * 100.0f)) + { + dst[i] = RF_WHITE; + } + else + { + dst[i] = RF_BLACK; + } + } + + result = (rf_image) + { + .data = dst, + .width = width, + .height = height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true, + }; + + return result; +} + +RF_API rf_image rf_gen_image_white_noise(int width, int height, float factor, rf_rand_proc rand, rf_allocator allocator) +{ + rf_image result = {0}; + + int dst_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + + if (!rand || dst_size <= 0) return result; + + rf_color* dst = RF_ALLOC(allocator, dst_size); + result = rf_gen_image_white_noise_to_buffer(width, height, factor, rand, dst, dst_size); + + return result; +} + +// Generate image: perlin noise +RF_API rf_image rf_gen_image_perlin_noise_to_buffer(int width, int height, int offset_x, int offset_y, float scale, rf_color* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (dst_size >= width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8)) + { + for (rf_int y = 0; y < height; y++) + { + for (rf_int x = 0; x < width; x++) + { + float nx = (float)(x + offset_x)*scale/(float)width; + float ny = (float)(y + offset_y)*scale/(float)height; + + // Typical values to start playing with: + // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) + // gain = 0.5 -- relative weighting applied to each successive octave + // octaves = 6 -- number of "octaves" of noise3() to sum + + // NOTE: We need to translate the data from [-1..1] to [0..1] + float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6) + 1.0f) / 2.0f; + + int intensity = (int)(p * 255.0f); + dst[y * width + x] = (rf_color){ intensity, intensity, intensity, 255 }; + } + } + + result = (rf_image) + { + .data = dst, + .width = width, + .height = height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true, + }; + } + + return result; +} + +RF_API rf_image rf_gen_image_perlin_noise(int width, int height, int offset_x, int offset_y, float scale, rf_allocator allocator) +{ + rf_image result = {0}; + + int dst_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + rf_color* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_gen_image_perlin_noise_to_buffer(width, height, offset_x, offset_y, scale, dst, dst_size); + } + + return result; +} + +RF_API rf_vec2 rf_get_seed_for_cellular_image(int seeds_per_row, int tile_size, int i, rf_rand_proc rand) +{ + rf_vec2 result = {0}; + + int y = (i / seeds_per_row) * tile_size + rand(0, tile_size - 1); + int x = (i % seeds_per_row) * tile_size + rand(0, tile_size - 1); + result = (rf_vec2) { (float) x, (float) y }; + + return result; +} + +// Generate image: cellular algorithm. Bigger tileSize means bigger cells +RF_API rf_image rf_gen_image_cellular_to_buffer(int width, int height, int tile_size, rf_rand_proc rand, rf_color* dst, rf_int dst_size) +{ + rf_image result = {0}; + + int seeds_per_row = width / tile_size; + int seeds_per_col = height / tile_size; + int seeds_count = seeds_per_row * seeds_per_col; + + if (dst_size >= width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8)) + { + for (rf_int y = 0; y < height; y++) + { + int tile_y = y / tile_size; + + for (rf_int x = 0; x < width; x++) + { + int tile_x = x / tile_size; + + float min_distance = INFINITY; + + // Check all adjacent tiles + for (rf_int i = -1; i < 2; i++) + { + if ((tile_x + i < 0) || (tile_x + i >= seeds_per_row)) continue; + + for (rf_int j = -1; j < 2; j++) + { + if ((tile_y + j < 0) || (tile_y + j >= seeds_per_col)) continue; + + rf_vec2 neighbor_seed = rf_get_seed_for_cellular_image(seeds_per_row, tile_size, (tile_y + j) * seeds_per_row + tile_x + i, rand); + + float dist = (float)hypot(x - (int)neighbor_seed.x, y - (int)neighbor_seed.y); + min_distance = (float)fmin(min_distance, dist); + } + } + + // I made this up but it seems to give good results at all tile sizes + int intensity = (int)(min_distance * 256.0f / tile_size); + if (intensity > 255) intensity = 255; + + dst[y * width + x] = (rf_color) { intensity, intensity, intensity, 255 }; + } + } + + result = (rf_image) + { + .data = dst, + .width = width, + .height = height, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true, + }; + } + + return result; +} + +RF_API rf_image rf_gen_image_cellular(int width, int height, int tile_size, rf_rand_proc rand, rf_allocator allocator) +{ + rf_image result = {0}; + + int dst_size = width * height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + + rf_color* dst = RF_ALLOC(allocator, dst_size); + + if (dst) + { + result = rf_gen_image_cellular_to_buffer(width, height, tile_size, rand, dst, dst_size); + } + + return result; +} + +// Draw an image (source) within an image (destination) +// NOTE: rf_color tint is applied to source image +RF_API void rf_image_draw(rf_image* dst, rf_image src, rf_rec src_rec, rf_rec dst_rec, rf_color tint, rf_allocator temp_allocator) +{ + if (src.valid && dst->valid) + { + dst->valid = false; + + if (src_rec.x < 0) src_rec.x = 0; + if (src_rec.y < 0) src_rec.y = 0; + + if ((src_rec.x + src_rec.width) > src.width) + { + src_rec.width = src.width - src_rec.x; + RF_LOG(RF_LOG_TYPE_WARNING, "Source rectangle width out of bounds, rescaled width: %i", src_rec.width); + } + + if ((src_rec.y + src_rec.height) > src.height) + { + src_rec.height = src.height - src_rec.y; + RF_LOG(RF_LOG_TYPE_WARNING, "Source rectangle height out of bounds, rescaled height: %i", src_rec.height); + } + + rf_image src_copy = rf_image_copy(src, temp_allocator); // Make a copy of source src to work with it + + // Crop source image to desired source rectangle (if required) + if ((src.width != (int)src_rec.width) && (src.height != (int)src_rec.height)) + { + rf_image old_src_copy = src_copy; + src_copy = rf_image_crop(src_copy, src_rec, temp_allocator); + rf_unload_image(old_src_copy, temp_allocator); + } + + // Scale source image in case destination rec size is different than source rec size + if (((int)dst_rec.width != (int)src_rec.width) || ((int)dst_rec.height != (int)src_rec.height)) + { + rf_image old_src_copy = src_copy; + src_copy = rf_image_resize(src_copy, (int)dst_rec.width, (int)dst_rec.height, temp_allocator, temp_allocator); + rf_unload_image(old_src_copy, temp_allocator); + } + + // Check that dstRec is inside dst image + // Allow negative position within destination with cropping + if (dst_rec.x < 0) + { + rf_image old_src_copy = src_copy; + src_copy = rf_image_crop(src_copy, (rf_rec) { -dst_rec.x, 0, dst_rec.width + dst_rec.x, dst_rec.height }, temp_allocator); + dst_rec.width = dst_rec.width + dst_rec.x; + dst_rec.x = 0; + rf_unload_image(old_src_copy, temp_allocator); + } + + if ((dst_rec.x + dst_rec.width) > dst->width) + { + rf_image old_src_copy = src_copy; + src_copy = rf_image_crop(src_copy, (rf_rec) { 0, 0, dst->width - dst_rec.x, dst_rec.height }, temp_allocator); + dst_rec.width = dst->width - dst_rec.x; + rf_unload_image(old_src_copy, temp_allocator); + } + + if (dst_rec.y < 0) + { + rf_image old_src_copy = src_copy; + src_copy = rf_image_crop(src_copy, (rf_rec) { 0, -dst_rec.y, dst_rec.width, dst_rec.height + dst_rec.y }, temp_allocator); + dst_rec.height = dst_rec.height + dst_rec.y; + dst_rec.y = 0; + rf_unload_image(old_src_copy, temp_allocator); + } + + if ((dst_rec.y + dst_rec.height) > dst->height) + { + rf_image old_src_copy = src_copy; + src_copy = rf_image_crop(src_copy, (rf_rec) { 0, 0, dst_rec.width, dst->height - dst_rec.y }, temp_allocator); + dst_rec.height = dst->height - dst_rec.y; + rf_unload_image(old_src_copy, temp_allocator); + } + + if (src_copy.valid) + { + // Get image data as rf_color pixels array to work with it + rf_color* dst_pixels = rf_image_pixels_to_rgba32(*dst, temp_allocator); + rf_color* src_pixels = rf_image_pixels_to_rgba32(src_copy, temp_allocator); + + rf_unload_image(src_copy, temp_allocator); // Source copy not required any more + + rf_vec4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) + rf_vec4 ftint = rf_color_normalize(tint); // Normalized color tint + + // Blit pixels, copy source image into destination + // TODO: Maybe out-of-bounds blitting could be considered here instead of so much cropping + for (rf_int j = (int)dst_rec.y; j < (int)(dst_rec.y + dst_rec.height); j++) + { + for (rf_int i = (int)dst_rec.x; i < (int)(dst_rec.x + dst_rec.width); i++) + { + // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing) + + fdst = rf_color_normalize(dst_pixels[j * (int)dst->width + i]); + fsrc = rf_color_normalize(src_pixels[(j - (int)dst_rec.y) * (int)dst_rec.width + (i - (int)dst_rec.x)]); + + // Apply color tint to source image + fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; + + fout.w = fsrc.w + fdst.w * (1.0f - fsrc.w); + + if (fout.w <= 0.0f) + { + fout.x = 0.0f; + fout.y = 0.0f; + fout.z = 0.0f; + } + else + { + fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; + fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; + fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + } + + dst_pixels[j*(int)dst->width + i] = (rf_color) { (unsigned char)(fout.x * 255.0f), + (unsigned char)(fout.y * 255.0f), + (unsigned char)(fout.z * 255.0f), + (unsigned char)(fout.w * 255.0f) }; + + // TODO: Support other blending options + } + } + + bool format_success = rf_format_pixels(dst_pixels, rf_image_size_in_format(*dst, RF_UNCOMPRESSED_R8G8B8A8), RF_UNCOMPRESSED_R8G8B8A8, + dst->data, rf_image_size(*dst), dst->format); + RF_ASSERT(format_success); + + RF_FREE(temp_allocator, src_pixels); + RF_FREE(temp_allocator, dst_pixels); + + dst->valid = true; + } + } +} + +// Draw rectangle within an image +RF_API void rf_image_draw_rectangle(rf_image* dst, rf_rec rec, rf_color color, rf_allocator temp_allocator) +{ + if (dst->valid) + { + dst->valid = false; + + rf_image src = rf_gen_image_color((int)rec.width, (int)rec.height, color, temp_allocator); + + if (src.valid) + { + rf_rec src_rec = (rf_rec){ 0, 0, rec.width, rec.height }; + + rf_image_draw(dst, src, src_rec, rec, RF_WHITE, temp_allocator); + + rf_unload_image(src, temp_allocator); + } + } +} + +// Draw rectangle lines within an image +RF_API void rf_image_draw_rectangle_lines(rf_image* dst, rf_rec rec, int thick, rf_color color, rf_allocator temp_allocator) +{ + rf_image_draw_rectangle(dst, (rf_rec) { rec.x, rec.y, rec.width, thick }, color, temp_allocator); + rf_image_draw_rectangle(dst, (rf_rec) { rec.x, rec.y + thick, thick, rec.height - thick * 2 }, color, temp_allocator); + rf_image_draw_rectangle(dst, (rf_rec) { rec.x + rec.width - thick, rec.y + thick, thick, rec.height - thick * 2 }, color, temp_allocator); + rf_image_draw_rectangle(dst, (rf_rec) { rec.x, rec.y + rec.height - thick, rec.width, thick }, color, temp_allocator); +} + +#pragma endregion + +#pragma region mipmaps + +RF_API int rf_mipmaps_image_size(rf_mipmaps_image image) +{ + int size = 0; + int width = image.width; + int height = image.height; + + for (rf_int i = 0; i < image.mipmaps; i++) + { + size += width * height * rf_bytes_per_pixel(image.format); + + width /= 2; + height /= 2; + + // Security check for NPOT textures + if (width < 1) width = 1; + if (height < 1) height = 1; + } + + return size; +} + +RF_API rf_mipmaps_stats rf_compute_mipmaps_stats(rf_image image, int desired_mipmaps_count) +{ + if (!image.valid) return (rf_mipmaps_stats) {0}; + + int possible_mip_count = 1; + int mipmaps_size = rf_image_size(image); + + int mip_width = image.width; + int mip_height = image.height; + + while (mip_width != 1 || mip_height != 1 || possible_mip_count == desired_mipmaps_count) + { + if (mip_width != 1) mip_width /= 2; + if (mip_height != 1) mip_height /= 2; + + // Safety check for NPOT textures + if (mip_width < 1) mip_width = 1; + if (mip_height < 1) mip_height = 1; + + mipmaps_size += mip_width * mip_height * rf_bytes_per_pixel(image.format); + + possible_mip_count++; + } + + return (rf_mipmaps_stats) { possible_mip_count, mipmaps_size }; +} + +// Generate all mipmap levels for a provided image. image.data is scaled to include mipmap levels. Mipmaps format is the same as base image +RF_API rf_mipmaps_image rf_image_gen_mipmaps_to_buffer(rf_image image, int gen_mipmaps_count, void* dst, rf_int dst_size, rf_allocator temp_allocator) +{ + if (image.valid) return (rf_mipmaps_image) {0}; + + rf_mipmaps_image result = {0}; + rf_mipmaps_stats mipmap_stats = rf_compute_mipmaps_stats(image, gen_mipmaps_count); + + if (mipmap_stats.possible_mip_counts <= gen_mipmaps_count) + { + if (dst_size == mipmap_stats.mipmaps_buffer_size) + { + // Pointer to current mip location in the dst buffer + unsigned char* dst_iter = dst; + + // Copy the image to the dst as the first mipmap level + memcpy(dst_iter, image.data, rf_image_size(image)); + dst_iter += rf_image_size(image); + + // Create a rgba32 buffer for the mipmap result, half the image size is enough for any mipmap level + int temp_mipmap_buffer_size = (image.width / 2) * (image.height / 2) * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + void* temp_mipmap_buffer = RF_ALLOC(temp_allocator, temp_mipmap_buffer_size); + + if (temp_mipmap_buffer) + { + int mip_width = image.width / 2; + int mip_height = image.height / 2; + int mip_count = 1; + for (; mip_count < gen_mipmaps_count; mip_count++) + { + rf_image mipmap = rf_image_resize_to_buffer(image, mip_width, mip_height, temp_mipmap_buffer, temp_mipmap_buffer_size, temp_allocator); + + if (mipmap.valid) + { + int dst_iter_size = dst_size - ((int)(dst_iter - ((unsigned char*)(dst)))); + + bool success = rf_format_pixels(mipmap.data, rf_image_size(mipmap), mipmap.format, dst_iter, dst_iter_size, image.format); + RF_ASSERT(success); + } + else break; + + mip_width /= 2; + mip_height /= 2; + + // Security check for NPOT textures + if (mip_width < 1) mip_width = 1; + if (mip_height < 1) mip_height = 1; + + // Compute next mipmap location in the dst buffer + dst_iter += mip_width * mip_height * rf_bytes_per_pixel(image.format); + } + + if (mip_count == gen_mipmaps_count) + { + result = (rf_mipmaps_image){ + .data = dst, + .width = image.width, + .height = image.height, + .mipmaps = gen_mipmaps_count, + .format = image.format, + .valid = true + }; + } + } + + RF_FREE(temp_allocator, temp_mipmap_buffer); + } + } + else RF_LOG(RF_LOG_TYPE_WARNING, "rf_image mipmaps already available"); + + return result; +} + +RF_API rf_mipmaps_image rf_image_gen_mipmaps(rf_image image, int desired_mipmaps_count, rf_allocator allocator, rf_allocator temp_allocator) +{ + if (!image.valid) return (rf_mipmaps_image) {0}; + + rf_mipmaps_image result = {0}; + rf_mipmaps_stats mipmap_stats = rf_compute_mipmaps_stats(image, desired_mipmaps_count); + + if (mipmap_stats.possible_mip_counts <= desired_mipmaps_count) + { + void* dst = RF_ALLOC(allocator, mipmap_stats.mipmaps_buffer_size); + + if (dst) + { + result = rf_image_gen_mipmaps_to_buffer(image, desired_mipmaps_count, dst, mipmap_stats.mipmaps_buffer_size, temp_allocator); + if (!result.valid) + { + RF_FREE(allocator, dst); + } + } + } + + return result; +} + +RF_API void rf_unload_mipmaps_image(rf_mipmaps_image image, rf_allocator allocator) +{ + RF_FREE(allocator, image.data); +} + +#pragma endregion + +#pragma region dds + +/* + Required extension: + GL_EXT_texture_compression_s3tc + + Supported tokens (defined by extensions) + GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 + GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 + GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +*/ + +#define RF_FOURCC_DXT1 (0x31545844) // Equivalent to "DXT1" in ASCII +#define RF_FOURCC_DXT3 (0x33545844) // Equivalent to "DXT3" in ASCII +#define RF_FOURCC_DXT5 (0x35545844) // Equivalent to "DXT5" in ASCII + +typedef struct rf_dds_pixel_format rf_dds_pixel_format; +struct rf_dds_pixel_format +{ + unsigned int size; + unsigned int flags; + unsigned int four_cc; + unsigned int rgb_bit_count; + unsigned int r_bit_mask; + unsigned int g_bit_mask; + unsigned int b_bit_mask; + unsigned int a_bit_mask; +}; + +// DDS Header (must be 124 bytes) +typedef struct rf_dds_header rf_dds_header; +struct rf_dds_header +{ + char id[4]; + unsigned int size; + unsigned int flags; + unsigned int height; + unsigned int width; + unsigned int pitch_or_linear_size; + unsigned int depth; + unsigned int mipmap_count; + unsigned int reserved_1[11]; + rf_dds_pixel_format ddspf; + unsigned int caps; + unsigned int caps_2; + unsigned int caps_3; + unsigned int caps_4; + unsigned int reserved_2; +}; + +RF_API rf_int rf_get_dds_image_size(const void* src, rf_int src_size) +{ + int result = 0; + + if (src && src_size >= sizeof(rf_dds_header)) + { + rf_dds_header header = *(rf_dds_header*)src; + + // Verify the type of file + if (rf_match_str_cstr(header.id, sizeof(header.id), "DDS ")) + { + if (header.ddspf.rgb_bit_count == 16) // 16bit mode, no compressed + { + if (header.ddspf.flags == 0x40) // no alpha channel + { + result = header.width * header.height * sizeof(unsigned short); + } + else if (header.ddspf.flags == 0x41) // with alpha channel + { + if (header.ddspf.a_bit_mask == 0x8000) // 1bit alpha + { + result = header.width * header.height * sizeof(unsigned short); + } + else if (header.ddspf.a_bit_mask == 0xf000) // 4bit alpha + { + result = header.width * header.height * sizeof(unsigned short); + } + } + } + else if (header.ddspf.flags == 0x40 && header.ddspf.rgb_bit_count == 24) // DDS_RGB, no compressed + { + // Not sure if this case exists... + result = header.width * header.height * 3 * sizeof(unsigned char); + } + else if (header.ddspf.flags == 0x41 && header.ddspf.rgb_bit_count == 32) // DDS_RGBA, no compressed + { + result = header.width * header.height * 4 * sizeof(unsigned char); + } + else if (((header.ddspf.flags == 0x04) || (header.ddspf.flags == 0x05)) && (header.ddspf.four_cc > 0)) // Compressed + { + int size; // DDS result data size + + // Calculate data size, including all mipmaps + if (header.mipmap_count > 1) size = header.pitch_or_linear_size * 2; + else size = header.pitch_or_linear_size; + + result = size * sizeof(unsigned char); + } + } + } + + return result; +} + +RF_API rf_mipmaps_image rf_load_dds_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size) +{ + rf_mipmaps_image result = { 0 }; + + if (src && dst && dst_size > 0 && src_size >= sizeof(rf_dds_header)) + { + rf_dds_header header = *(rf_dds_header*)src; + + src = ((char*)src) + sizeof(rf_dds_header); + src_size -= sizeof(rf_dds_header); + + // Verify the type of file + if (rf_match_str_cstr(header.id, sizeof(header.id), "DDS ")) + { + result.width = header.width; + result.height = header.height; + result.mipmaps = (header.mipmap_count == 0) ? 1 : header.mipmap_count; + + if (header.ddspf.rgb_bit_count == 16) // 16bit mode, no compressed + { + if (header.ddspf.flags == 0x40) // no alpha channel + { + int dds_result_size = header.width * header.height * sizeof(unsigned short); + + if (src_size >= dds_result_size && dst_size >= dds_result_size) + { + memcpy(dst, src, dds_result_size); + result.format = RF_UNCOMPRESSED_R5G6B5; + result.data = dst; + result.valid = true; + } + } + else if (header.ddspf.flags == 0x41) // with alpha channel + { + if (header.ddspf.a_bit_mask == 0x8000) // 1bit alpha + { + int dds_result_size = header.width * header.height * sizeof(unsigned short); + + if (src_size >= dds_result_size && dst_size >= dds_result_size) + { + memcpy(dst, src, dds_result_size); + + unsigned char alpha = 0; + + // Data comes as A1R5G5B5, it must be reordered to R5G5B5A1 + for (rf_int i = 0; i < result.width * result.height; i++) + { + alpha = ((unsigned short *)result.data)[i] >> 15; + ((unsigned short*)result.data)[i] = ((unsigned short *)result.data)[i] << 1; + ((unsigned short*)result.data)[i] += alpha; + } + + result.format = RF_UNCOMPRESSED_R5G5B5A1; + result.data = dst; + result.valid = true; + } + } + else if (header.ddspf.a_bit_mask == 0xf000) // 4bit alpha + { + int dds_result_size = header.width * header.height * sizeof(unsigned short); + + if (src_size >= dds_result_size && dst_size >= dds_result_size) + { + memcpy(dst, src, dds_result_size); + + unsigned char alpha = 0; + + // Data comes as A4R4G4B4, it must be reordered R4G4B4A4 + for (rf_int i = 0; i < result.width * result.height; i++) + { + alpha = ((unsigned short*)result.data)[i] >> 12; + ((unsigned short*)result.data)[i] = ((unsigned short*)result.data)[i] << 4; + ((unsigned short*)result.data)[i] += alpha; + } + + result.format = RF_UNCOMPRESSED_R4G4B4A4; + result.data = dst; + result.valid = true; + } + } + } + } + else if (header.ddspf.flags == 0x40 && header.ddspf.rgb_bit_count == 24) // DDS_RGB, no compressed, not sure if this case exists... + { + int dds_result_size = header.width * header.height * 3; + + if (src_size >= dds_result_size && dst_size >= dds_result_size) + { + memcpy(dst, src, dds_result_size); + result.format = RF_UNCOMPRESSED_R8G8B8; + result.data = dst; + result.valid = true; + } + } + else if (header.ddspf.flags == 0x41 && header.ddspf.rgb_bit_count == 32) // DDS_RGBA, no compressed + { + int dds_result_size = header.width * header.height * 4; + + if (src_size >= dds_result_size && dst_size >= dds_result_size) + { + memcpy(dst, src, dds_result_size); + + unsigned char blue = 0; + + // Data comes as A8R8G8B8, it must be reordered R8G8B8A8 (view next comment) + // DirecX understand ARGB as a 32bit DWORD but the actual memory byte alignment is BGRA + // So, we must realign B8G8R8A8 to R8G8B8A8 + for (rf_int i = 0; i < header.width * header.height * 4; i += 4) + { + blue = ((unsigned char*)dst)[i]; + ((unsigned char*)dst)[i + 0] = ((unsigned char*)dst)[i + 2]; + ((unsigned char*)dst)[i + 2] = blue; + } + + result.format = RF_UNCOMPRESSED_R8G8B8A8; + result.data = dst; + result.valid = true; + } + } + else if (((header.ddspf.flags == 0x04) || (header.ddspf.flags == 0x05)) && (header.ddspf.four_cc > 0)) // Compressed + { + int dds_result_size = (header.mipmap_count > 1) ? (header.pitch_or_linear_size * 2) : header.pitch_or_linear_size; + + if (src_size >= dds_result_size && dst_size >= dds_result_size) + { + memcpy(dst, src, dds_result_size); + + switch (header.ddspf.four_cc) + { + case RF_FOURCC_DXT1: result.format = header.ddspf.flags == 0x04 ? RF_COMPRESSED_DXT1_RGB : RF_COMPRESSED_DXT1_RGBA; break; + case RF_FOURCC_DXT3: result.format = RF_COMPRESSED_DXT3_RGBA; break; + case RF_FOURCC_DXT5: result.format = RF_COMPRESSED_DXT5_RGBA; break; + default: break; + } + + result.data = dst; + result.valid = true; + } + } + } + else RF_LOG_ERROR(RF_BAD_FORMAT, "DDS file does not seem to be a valid result"); + } + + return result; +} + +RF_API rf_mipmaps_image rf_load_dds_image(const void* src, rf_int src_size, rf_allocator allocator) +{ + rf_mipmaps_image result = {0}; + + int dst_size = rf_get_dds_image_size(src, src_size); + void* dst = RF_ALLOC(allocator, dst_size); + + result = rf_load_dds_image_to_buffer(src, src_size, dst, dst_size); + + return result; +} + +RF_API rf_mipmaps_image rf_load_dds_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_mipmaps_image result = {0}; + + int src_size = RF_FILE_SIZE(io, file); + void* src = RF_ALLOC(temp_allocator, src_size); + + if (RF_READ_FILE(io, file, src, src_size)) + { + result = rf_load_dds_image(src, src_size, allocator); + } + + RF_FREE(temp_allocator, src); + + return result; +} +#pragma endregion + +#pragma region pkm + +/* + Required extensions: + GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0) + GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0) + + Supported tokens (defined by extensions) + GL_ETC1_RGB8_OES 0x8D64 + GL_COMPRESSED_RGB8_ETC2 0x9274 + GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + + Formats list + version 10: format: 0=ETC1_RGB, [1=ETC1_RGBA, 2=ETC1_RGB_MIP, 3=ETC1_RGBA_MIP] (not used) + version 20: format: 0=ETC1_RGB, 1=ETC2_RGB, 2=ETC2_RGBA_OLD, 3=ETC2_RGBA, 4=ETC2_RGBA1, 5=ETC2_R, 6=ETC2_RG, 7=ETC2_SIGNED_R, 8=ETC2_SIGNED_R + + NOTE: The extended width and height are the widths rounded up to a multiple of 4. + NOTE: ETC is always 4bit per pixel (64 bit for each 4x4 block of pixels) +*/ + +// PKM file (ETC1) Header (16 bytes) +typedef struct rf_pkm_header rf_pkm_header; +struct rf_pkm_header +{ + char id[4]; // "PKM " + char version[2]; // "10" or "20" + unsigned short format; // Data format (big-endian) (Check list below) + unsigned short width; // Texture width (big-endian) (origWidth rounded to multiple of 4) + unsigned short height; // Texture height (big-endian) (origHeight rounded to multiple of 4) + unsigned short orig_width; // Original width (big-endian) + unsigned short orig_height; // Original height (big-endian) +}; + +RF_API rf_int rf_get_pkm_image_size(const void* src, rf_int src_size) +{ + int result = 0; + + if (src && src_size > sizeof(rf_pkm_header)) + { + rf_pkm_header header = *(rf_pkm_header*)src; + + // Verify the type of file + if (rf_match_str_cstr(header.id, sizeof(header.id), "PKM ")) + { + // Note: format, width and height come as big-endian, data must be swapped to little-endian + header.format = ((header.format & 0x00FF) << 8) | ((header.format & 0xFF00) >> 8); + header.width = ((header.width & 0x00FF) << 8) | ((header.width & 0xFF00) >> 8); + header.height = ((header.height & 0x00FF) << 8) | ((header.height & 0xFF00) >> 8); + + int bpp = 4; + if (header.format == 3) bpp = 8; + + result = header.width * header.height * bpp / 8; + } + else RF_LOG_ERROR(RF_BAD_FORMAT, "PKM file does not seem to be a valid image"); + } + + return result; +} + +// Load image from .pkm +RF_API rf_image rf_load_pkm_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size) +{ + rf_image result = {0}; + + if (src && src_size >= sizeof(rf_pkm_header)) + { + rf_pkm_header header = *(rf_pkm_header*)src; + + src = (char*)src + sizeof(rf_pkm_header); + src_size -= sizeof(rf_pkm_header); + + // Verify the type of file + if (rf_match_str_cstr(header.id, sizeof(header.id), "PKM ")) + { + // Note: format, width and height come as big-endian, data must be swapped to little-endian + result.format = ((header.format & 0x00FF) << 8) | ((header.format & 0xFF00) >> 8); + result.width = ((header.width & 0x00FF) << 8) | ((header.width & 0xFF00) >> 8); + result.height = ((header.height & 0x00FF) << 8) | ((header.height & 0xFF00) >> 8); + + int bpp = (result.format == 3) ? 8 : 4; + int size = result.width * result.height * bpp / 8; // Total data size in bytes + + if (dst_size >= size && src_size >= size) + { + memcpy(dst, src, size); + + if (header.format == 0) result.format = RF_COMPRESSED_ETC1_RGB; + else if (header.format == 1) result.format = RF_COMPRESSED_ETC2_RGB; + else if (header.format == 3) result.format = RF_COMPRESSED_ETC2_EAC_RGBA; + + result.valid = true; + } + } + else RF_LOG_ERROR(RF_BAD_FORMAT, "PKM file does not seem to be a valid image"); + } + + return result; +} + +RF_API rf_image rf_load_pkm_image(const void* src, rf_int src_size, rf_allocator allocator) +{ + rf_image result = {0}; + + if (src && src_size > 0) + { + int dst_size = rf_get_pkm_image_size(src, src_size); + void* dst = RF_ALLOC(allocator, dst_size); + + result = rf_load_pkm_image_to_buffer(src, src_size, dst, dst_size); + } + + return result; +} + +RF_API rf_image rf_load_pkm_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_image result = {0}; + + int src_size = RF_FILE_SIZE(io, file); + void* src = RF_ALLOC(temp_allocator, src_size); + + if (RF_READ_FILE(io, file, src, src_size)) + { + result = rf_load_pkm_image(src, src_size, allocator); + } + + RF_FREE(temp_allocator, src); + + return result; +} + +#pragma endregion + +#pragma region ktx + +/* + Required extensions: + GL_OES_compressed_ETC1_RGB8_texture (ETC1) + GL_ARB_ES3_compatibility (ETC2/EAC) + + Supported tokens (defined by extensions) + GL_ETC1_RGB8_OES 0x8D64 + GL_COMPRESSED_RGB8_ETC2 0x9274 + GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + + KTX file Header (64 bytes) + v1.1 - https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + v2.0 - http://github.khronos.org/KTX-Specification/ + + NOTE: Before start of every mipmap data block, we have: unsigned int dataSize + TODO: Support KTX 2.2 specs! +*/ + +typedef struct rf_ktx_header rf_ktx_header; +struct rf_ktx_header +{ + char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" + unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 + unsigned int gl_type; // For compressed textures, gl_type must equal 0 + unsigned int gl_type_size; // For compressed texture data, usually 1 + unsigned int gl_format; // For compressed textures is 0 + unsigned int gl_internal_format; // Compressed internal format + unsigned int gl_base_internal_format; // Same as gl_format (RGB, RGBA, ALPHA...) + unsigned int width; // Texture image width in pixels + unsigned int height; // Texture image height in pixels + unsigned int depth; // For 2D textures is 0 + unsigned int elements; // Number of array elements, usually 0 + unsigned int faces; // Cubemap faces, for no-cubemap = 1 + unsigned int mipmap_levels; // Non-mipmapped textures = 1 + unsigned int key_value_data_size; // Used to encode any arbitrary data... +}; + +RF_API rf_int rf_get_ktx_image_size(const void* src, rf_int src_size) +{ + int result = 0; + + if (src && src_size >= sizeof(rf_ktx_header)) + { + rf_ktx_header header = *(rf_ktx_header*)src; + src = (char*)src + sizeof(rf_ktx_header) + header.key_value_data_size; + src_size -= sizeof(rf_ktx_header) + header.key_value_data_size; + + if (rf_match_str_cstr(header.id + 1, 6, "KTX 11")) + { + if (src_size > sizeof(unsigned int)) + { + memcpy(&result, src, sizeof(unsigned int)); + } + } + } + + return result; +} + +RF_API rf_mipmaps_image rf_load_ktx_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size) +{ + rf_mipmaps_image result = {0}; + + if (src && src_size > sizeof(rf_ktx_header)) + { + rf_ktx_header header = *(rf_ktx_header*)src; + src = (char*)src + sizeof(rf_ktx_header) + header.key_value_data_size; + src_size -= sizeof(rf_ktx_header) + header.key_value_data_size; + + if (rf_match_str_cstr(header.id + 1, 6, "KTX 11")) + { + result.width = header.width; + result.height = header.height; + result.mipmaps = header.mipmap_levels; + + int image_size = 0; + if (src_size > sizeof(unsigned int)) + { + memcpy(&image_size, src, sizeof(unsigned int)); + src = (char*)src + sizeof(unsigned int); + src_size -= sizeof(unsigned int); + + if (image_size >= src_size && dst_size >= image_size) + { + memcpy(dst, src, image_size); + result.data = dst; + + switch (header.gl_internal_format) + { + case 0x8D64: result.format = RF_COMPRESSED_ETC1_RGB; break; + case 0x9274: result.format = RF_COMPRESSED_ETC2_RGB; break; + case 0x9278: result.format = RF_COMPRESSED_ETC2_EAC_RGBA; break; + default: return result; + } + + result.valid = true; + } + } + } + } + + return result; +} + +RF_API rf_mipmaps_image rf_load_ktx_image(const void* src, rf_int src_size, rf_allocator allocator) +{ + rf_mipmaps_image result = {0}; + + if (src && src_size > 0) + { + int dst_size = rf_get_ktx_image_size(src, src_size); + void* dst = RF_ALLOC(allocator, dst_size); + + result = rf_load_ktx_image_to_buffer(src, src_size, dst, dst_size); + } + + return result; +} + +RF_API rf_mipmaps_image rf_load_ktx_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_mipmaps_image result = {0}; + + int src_size = RF_FILE_SIZE(io, file); + void* src = RF_ALLOC(temp_allocator, src_size); + + if (RF_READ_FILE(io, file, src, src_size)) + { + result = rf_load_ktx_image(src, src_size, allocator); + } + + RF_FREE(temp_allocator, src); + + return result; +} + +#pragma endregion + +#pragma region gif + +// Load animated GIF data +// - rf_image.data buffer includes all frames: [image#0][image#1][image#2][...] +// - Number of frames is returned through 'frames' parameter +// - Frames delay is returned through 'delays' parameter (int array) +// - All frames are returned in RGBA format +RF_API rf_gif rf_load_animated_gif(const void* data, rf_int data_size, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_gif gif = {0}; + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + { + int component_count = 0; + void* loaded_gif = stbi_load_gif_from_memory(data, data_size, &gif.frame_delays, &gif.width, &gif.height, &gif.frames_count, &component_count, 4); + + if (loaded_gif && component_count == 4) + { + int loaded_gif_size = gif.width * gif.height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8); + void* dst = RF_ALLOC(allocator, loaded_gif_size); + + if (dst) + { + memcpy(dst, loaded_gif, loaded_gif_size); + + gif.data = dst; + gif.format = RF_UNCOMPRESSED_R8G8B8A8; + gif.valid = true; + } + } + + RF_FREE(temp_allocator, loaded_gif); + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + return gif; +} + +RF_API rf_gif rf_load_animated_gif_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_gif result = (rf_gif) {0}; + + int file_size = RF_FILE_SIZE(io, filename); + unsigned char* buffer = RF_ALLOC(temp_allocator, file_size); + + if (RF_READ_FILE(io, filename, buffer, file_size)) + { + result = rf_load_animated_gif(buffer, file_size, allocator, temp_allocator); + } + + RF_FREE(temp_allocator, buffer); + + return result; +} + +RF_API rf_sizei rf_gif_frame_size(rf_gif gif) +{ + rf_sizei result = {0}; + + if (gif.valid) + { + result = (rf_sizei) { gif.width / gif.frames_count, gif.height / gif.frames_count }; + } + + return result; +} + +// Returns an image pointing to the frame in the gif +RF_API rf_image rf_get_frame_from_gif(rf_gif gif, int frame) +{ + rf_image result = {0}; + + if (gif.valid) + { + rf_sizei size = rf_gif_frame_size(gif); + + result = (rf_image) + { + .data = ((unsigned char*)gif.data) + (size.width * size.height * rf_bytes_per_pixel(gif.format)) * frame, + .width = size.width, + .height = size.height, + .format = gif.format, + .valid = true, + }; + } + + return result; +} + +RF_API void rf_unload_gif(rf_gif gif, rf_allocator allocator) +{ + if (gif.valid) + { + RF_FREE(allocator, gif.frame_delays); + rf_unload_image(gif.image, allocator); + } +} + +#pragma endregion +/*** End of inlined file: rayfork-image.c ***/ + + +/*** Start of inlined file: rayfork-texture.c ***/ +// Load texture from file into GPU memory (VRAM) +RF_API rf_texture2d rf_load_texture_from_file(const char* filename, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_texture2d result = {0}; + + rf_image img = rf_load_image_from_file(filename, temp_allocator, temp_allocator, io); + + result = rf_load_texture_from_image(img); + + rf_unload_image(img, temp_allocator); + + return result; +} + +// Load texture from an image file data +RF_API rf_texture2d rf_load_texture_from_file_data(const void* data, rf_int dst_size, rf_allocator temp_allocator) +{ + rf_image img = rf_load_image_from_file_data(data, dst_size, temp_allocator, temp_allocator); + + rf_texture2d texture = rf_load_texture_from_image(img); + + rf_unload_image(img, temp_allocator); + + return texture; +} + +// Load texture from image data +RF_API rf_texture2d rf_load_texture_from_image(rf_image image) +{ + return rf_load_texture_from_image_with_mipmaps((rf_mipmaps_image) { + .image = image, + .mipmaps = 1 + }); +} + +RF_API rf_texture2d rf_load_texture_from_image_with_mipmaps(rf_mipmaps_image image) +{ + rf_texture2d result = {0}; + + if (image.valid) + { + result.id = rf_gfx_load_texture(image.data, image.width, image.height, image.format, image.mipmaps); + + if (result.id != 0) + { + result.width = image.width; + result.height = image.height; + result.format = image.format; + result.valid = true; + } + } + else RF_LOG(RF_LOG_TYPE_WARNING, "rf_texture could not be loaded from rf_image"); + + return result; +} + +// Load cubemap from image, multiple image cubemap layouts supported +RF_API rf_texture_cubemap rf_load_texture_cubemap_from_image(rf_image image, rf_cubemap_layout_type layout_type, rf_allocator temp_allocator) +{ + rf_texture_cubemap cubemap = {0}; + + if (layout_type == RF_CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type + { + // Check image width/height to determine the type of cubemap provided + if (image.width > image.height) + { + if ((image.width / 6) == image.height) { layout_type = RF_CUBEMAP_LINE_HORIZONTAL; cubemap.width = image.width / 6; } + else if ((image.width / 4) == (image.height/3)) { layout_type = RF_CUBEMAP_CROSS_FOUR_BY_TREE; cubemap.width = image.width / 4; } + else if (image.width >= (int)((float)image.height * 1.85f)) { layout_type = RF_CUBEMAP_PANORAMA; cubemap.width = image.width / 4; } + } + else if (image.height > image.width) + { + if ((image.height / 6) == image.width) { layout_type = RF_CUBEMAP_LINE_VERTICAL; cubemap.width = image.height / 6; } + else if ((image.width / 3) == (image.height/4)) { layout_type = RF_CUBEMAP_CROSS_THREE_BY_FOUR; cubemap.width = image.width / 3; } + } + + cubemap.height = cubemap.width; + } + + if (layout_type != RF_CUBEMAP_AUTO_DETECT) + { + int size = cubemap.width; + + rf_image faces = {0}; // Vertical column image + rf_rec face_recs[6] = {0}; // Face source rectangles + for (rf_int i = 0; i < 6; i++) face_recs[i] = (rf_rec) { 0, 0, size, size }; + + if (layout_type == RF_CUBEMAP_LINE_VERTICAL) + { + faces = image; + for (rf_int i = 0; i < 6; i++) face_recs[i].y = size*i; + } + else if (layout_type == RF_CUBEMAP_PANORAMA) + { + // TODO: Convert panorama image to square faces... + // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp + } + else + { + if (layout_type == RF_CUBEMAP_LINE_HORIZONTAL) { for (rf_int i = 0; i < 6; i++) { face_recs[i].x = size * i; } } + else if (layout_type == RF_CUBEMAP_CROSS_THREE_BY_FOUR) + { + face_recs[0].x = size; face_recs[0].y = size; + face_recs[1].x = size; face_recs[1].y = 3*size; + face_recs[2].x = size; face_recs[2].y = 0; + face_recs[3].x = size; face_recs[3].y = 2*size; + face_recs[4].x = 0; face_recs[4].y = size; + face_recs[5].x = 2*size; face_recs[5].y = size; + } + else if (layout_type == RF_CUBEMAP_CROSS_FOUR_BY_TREE) + { + face_recs[0].x = 2*size; face_recs[0].y = size; + face_recs[1].x = 0; face_recs[1].y = size; + face_recs[2].x = size; face_recs[2].y = 0; + face_recs[3].x = size; face_recs[3].y = 2*size; + face_recs[4].x = size; face_recs[4].y = size; + face_recs[5].x = 3*size; face_recs[5].y = size; + } + + // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading + rf_image faces_colors = rf_gen_image_color(size, size * 6, RF_MAGENTA, temp_allocator); + faces = rf_image_format(faces_colors, image.format, temp_allocator); + rf_unload_image(faces_colors, temp_allocator); + + // TODO: rf_image formating does not work with compressed textures! + } + + for (rf_int i = 0; i < 6; i++) + { + rf_image_draw(&faces, image, face_recs[i], (rf_rec) {0, size * i, size, size }, RF_WHITE, temp_allocator); + } + + cubemap.id = rf_gfx_load_texture_cubemap(faces.data, size, faces.format); + + if (cubemap.id == 0) { RF_LOG(RF_LOG_TYPE_WARNING, "Cubemap image could not be loaded."); } + + rf_unload_image(faces, temp_allocator); + } + else RF_LOG(RF_LOG_TYPE_WARNING, "Cubemap image layout can not be detected."); + + return cubemap; +} + +// Load texture for rendering (framebuffer) +RF_API rf_render_texture2d rf_load_render_texture(int width, int height) +{ + rf_render_texture2d target = rf_gfx_load_render_texture(width, height, RF_UNCOMPRESSED_R8G8B8A8, 24, false); + + return target; +} + +// Update GPU texture with new data. Pixels data must match texture.format +RF_API void rf_update_texture(rf_texture2d texture, const void* pixels, rf_int pixels_size) +{ + rf_gfx_update_texture(texture.id, texture.width, texture.height, texture.format, pixels, pixels_size); +} + +// Generate GPU mipmaps for a texture +RF_API void rf_gen_texture_mipmaps(rf_texture2d* texture) +{ + // NOTE: NPOT textures support check inside function + // On WebGL (OpenGL ES 2.0) NPOT textures support is limited + rf_gfx_generate_mipmaps(texture); +} + +// Set texture wrapping mode +RF_API void rf_set_texture_wrap(rf_texture2d texture, rf_texture_wrap_mode wrap_mode) +{ + rf_gfx_set_texture_wrap(texture, wrap_mode); +} + +// Set texture scaling filter mode +RF_API void rf_set_texture_filter(rf_texture2d texture, rf_texture_filter_mode filter_mode) +{ + rf_gfx_set_texture_filter(texture, filter_mode); +} + +// Unload texture from GPU memory (VRAM) +RF_API void rf_unload_texture(rf_texture2d texture) +{ + if (texture.id > 0) + { + rf_gfx_delete_textures(texture.id); + + RF_LOG(RF_LOG_TYPE_INFO, "[TEX ID %i] Unloaded texture data from VRAM (GPU)", texture.id); + } +} + +// Unload render texture from GPU memory (VRAM) +RF_API void rf_unload_render_texture(rf_render_texture2d target) +{ + if (target.id > 0) + { + rf_gfx_delete_render_textures(target); + + RF_LOG(RF_LOG_TYPE_INFO, "[TEX ID %i] Unloaded render texture data from VRAM (GPU)", target.id); + } +} +/*** End of inlined file: rayfork-texture.c ***/ + + +/*** Start of inlined file: rayfork-font.c ***/ +#pragma region dependencies + +#pragma region stb_truetype +#define STB_TRUETYPE_IMPLEMENTATION +#define STBTT_malloc(sz, u) RF_ALLOC(rf__global_allocator_for_dependencies, sz) +#define STBTT_free(p, u) RF_FREE(rf__global_allocator_for_dependencies, p) +#define STBTT_assert(it) RF_ASSERT(it) +#define STBTT_STATIC + +/*** Start of inlined file: stb_truetype.h ***/ +// stb_truetype.h - v1.24 - public domain +// authored from 2009-2020 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +//#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before //#include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +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. +------------------------------------------------------------------------------ +*/ +/*** End of inlined file: stb_truetype.h ***/ + + +#pragma endregion + +#pragma endregion + +#pragma region ttf font + +RF_API rf_ttf_font_info rf_parse_ttf_font(const void* ttf_data, rf_int font_size) +{ + rf_ttf_font_info result = {0}; + + if (ttf_data && font_size > 0) + { + stbtt_fontinfo font_info = {0}; + if (stbtt_InitFont(&font_info, ttf_data, 0)) + { + // Calculate font scale factor + float scale_factor = stbtt_ScaleForPixelHeight(&font_info, (float)font_size); + + // Calculate font basic metrics + // NOTE: ascent is equivalent to font baseline + int ascent, descent, line_gap; + stbtt_GetFontVMetrics(&font_info, &ascent, &descent, &line_gap); + + result = (rf_ttf_font_info) + { + .ttf_data = ttf_data, + .font_size = font_size, + .scale_factor = scale_factor, + .ascent = ascent, + .descent = descent, + .line_gap = line_gap, + .valid = true, + }; + + RF_ASSERT(sizeof(stbtt_fontinfo) == sizeof(result.internal_stb_font_info)); + memcpy(&result.internal_stb_font_info, &font_info, sizeof(stbtt_fontinfo)); + } + else RF_LOG_ERROR(RF_STBTT_FAILED, "STB failed to parse ttf font."); + } + + return result; +} + +RF_API void rf_compute_ttf_font_glyph_metrics(rf_ttf_font_info* font_info, const int* codepoints, rf_int codepoints_count, rf_glyph_info* dst, rf_int dst_count) +{ + if (font_info && font_info->valid) + { + if (dst && dst_count >= codepoints_count) + { + // The stbtt functions called here should not require any allocations + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + font_info->largest_glyph_size = 0; + + float required_area = 0; + + // NOTE: Using simple packaging, one char after another + for (rf_int i = 0; i < codepoints_count; i++) + { + stbtt_fontinfo* stbtt_ctx = (stbtt_fontinfo*) &font_info->internal_stb_font_info; + + dst[i].codepoint = codepoints[i]; + + int begin_x = 0; + int begin_y = 0; + int end_x = 0; + int end_y = 0; + stbtt_GetCodepointBitmapBox(stbtt_ctx, dst[i].codepoint, font_info->scale_factor, font_info->scale_factor, &begin_x, &begin_y, &end_x, &end_y); + + dst[i].width = end_x - begin_x; + dst[i].height = end_y - begin_y; + dst[i].offset_x = begin_x; + dst[i].offset_y = begin_y; + dst[i].offset_y += (int) ((float)font_info->ascent * font_info->scale_factor); + + stbtt_GetCodepointHMetrics(stbtt_ctx, dst[i].codepoint, &dst[i].advance_x, NULL); + dst[i].advance_x *= font_info->scale_factor; + + const int char_size = dst[i].width * dst[i].height; + if (char_size > font_info->largest_glyph_size) font_info->largest_glyph_size = char_size; + } + } + } +} + +// Note: the atlas is square and this value is the width of the atlas +RF_API int rf_compute_ttf_font_atlas_width(int padding, rf_glyph_info* glyph_metrics, rf_int glyphs_count) +{ + int result = 0; + + for (rf_int i = 0; i < glyphs_count; i++) + { + // Calculate the area of all glyphs + padding + // The padding is applied both on the (left and right) and (top and bottom) of the glyph, which is why we multiply by 2 + result += ((glyph_metrics[i].width + 2 * padding) * (glyph_metrics[i].height + 2 * padding)); + } + + // Calculate the width required for a square atlas containing all glyphs + result = rf_next_pot(sqrtf(result) * 1.3f); + + return result; +} + +RF_API rf_image rf_generate_ttf_font_atlas(rf_ttf_font_info* font_info, int atlas_width, int padding, rf_glyph_info* glyphs, rf_int glyphs_count, rf_font_antialias antialias, unsigned short* dst, rf_int dst_count, rf_allocator temp_allocator) +{ + rf_image result = {0}; + + if (font_info && font_info->valid) + { + int atlas_pixel_count = atlas_width * atlas_width; + + if (dst && dst_count >= atlas_pixel_count) + { + memset(dst, 0, atlas_pixel_count * 2); + + int largest_glyph_size = 0; + for (rf_int i = 0; i < glyphs_count; i++) + { + int area = glyphs[i].width * glyphs[i].height; + if (area > largest_glyph_size) + { + largest_glyph_size = area; + } + } + + // Allocate a grayscale buffer large enough to store the largest glyph + unsigned char* glyph_buffer = RF_ALLOC(temp_allocator, largest_glyph_size); + + // Use basic packing algorithm to generate the atlas + if (glyph_buffer) + { + // We update these for every pixel in the loop + int offset_x = RF_BUILTIN_FONT_PADDING; + int offset_y = RF_BUILTIN_FONT_PADDING; + + // Set the allocator for stbtt + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + { + // Using simple packaging, one char after another + for (rf_int i = 0; i < glyphs_count; i++) + { + // Extract these variables to shorter names + stbtt_fontinfo* stbtt_ctx = (stbtt_fontinfo*) &font_info->internal_stb_font_info; + + // Get glyph bitmap + stbtt_MakeCodepointBitmap(stbtt_ctx, glyph_buffer, glyphs[i].width, glyphs[i].height, glyphs[i].width, font_info->scale_factor, font_info->scale_factor, glyphs[i].codepoint); + + // Copy pixel data from fc.data to atlas + for (rf_int y = 0; y < glyphs[i].height; y++) + { + for (rf_int x = 0; x < glyphs[i].width; x++) + { + unsigned char glyph_pixel = glyph_buffer[y * ((int)glyphs[i].width) + x]; + if (antialias == RF_FONT_NO_ANTIALIAS && glyph_pixel > RF_BITMAP_ALPHA_THRESHOLD) glyph_pixel = 0; + + int dst_index = (offset_y + y) * atlas_width + (offset_x + x); + // dst is in RF_UNCOMPRESSED_GRAY_ALPHA which is 2 bytes + // for fonts we write the glyph_pixel in the alpha channel which is byte 2 + ((unsigned char*)(&dst[dst_index]))[0] = 255; + ((unsigned char*)(&dst[dst_index]))[1] = glyph_pixel; + } + } + + // Fill chars rectangles in atlas info + glyphs[i].x = (float)offset_x; + glyphs[i].y = (float)offset_y; + + // Move atlas position X for next character drawing + offset_x += (glyphs[i].width + 2 * padding); + if (offset_x >= (atlas_width - glyphs[i].width - 2 * padding)) + { + offset_x = padding; + offset_y += font_info->font_size + padding; + + if (offset_y > (atlas_width - font_info->font_size - padding)) break; + //else error dst buffer is not big enough + } + } + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + result.data = dst; + result.width = atlas_width; + result.height = atlas_width; + result.format = RF_UNCOMPRESSED_GRAY_ALPHA; + result.valid = true; + } + + RF_FREE(temp_allocator, glyph_buffer); + } + } + + return result; +} + +RF_API rf_font rf_ttf_font_from_atlas(int font_size, rf_image atlas, rf_glyph_info* glyph_metrics, rf_int glyphs_count) +{ + rf_font result = {0}; + + rf_texture2d texture = rf_load_texture_from_image(atlas); + + if (texture.valid) + { + result = (rf_font) + { + .glyphs = glyph_metrics, + .glyphs_count = glyphs_count, + .texture = texture, + .base_size = font_size, + .valid = true, + }; + } + + return result; +} + +// Load rf_font from TTF font file with generation parameters +// NOTE: You can pass an array with desired characters, those characters should be available in the font +// if array is NULL, default char set is selected 32..126 +RF_API rf_font rf_load_ttf_font_from_data(const void* font_file_data, int font_size, rf_font_antialias antialias, const int* chars, rf_int char_count, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_font result = {0}; + + rf_ttf_font_info font_info = rf_parse_ttf_font(font_file_data, font_size); + + // Get the glyph metrics + rf_glyph_info* glyph_metrics = RF_ALLOC(allocator, char_count * sizeof(rf_glyph_info)); + rf_compute_ttf_font_glyph_metrics(&font_info, chars, char_count, glyph_metrics, char_count); + + // Build the atlas and font + int atlas_size = rf_compute_ttf_font_atlas_width(RF_BUILTIN_FONT_PADDING, glyph_metrics, char_count); + int atlas_pixel_count = atlas_size * atlas_size; + unsigned short* atlas_buffer = RF_ALLOC(temp_allocator, atlas_pixel_count * sizeof(unsigned short)); + rf_image atlas = rf_generate_ttf_font_atlas(&font_info, atlas_size, RF_BUILTIN_FONT_PADDING, glyph_metrics, char_count, antialias, atlas_buffer, atlas_pixel_count, temp_allocator); + + // Get the font + result = rf_ttf_font_from_atlas(font_size, atlas, glyph_metrics, char_count); + + // Free the atlas bitmap + RF_FREE(temp_allocator, atlas_buffer); + + return result; +} + +// Load rf_font from file into GPU memory (VRAM) +RF_API rf_font rf_load_ttf_font_from_file(const char* filename, int font_size, rf_font_antialias antialias, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_font font = {0}; + + if (rf_is_file_extension(filename, ".ttf") || rf_is_file_extension(filename, ".otf")) + { + int file_size = RF_FILE_SIZE(io, filename); + void* data = RF_ALLOC(temp_allocator, file_size); + + if (RF_READ_FILE(io, filename, data, file_size)) + { + font = rf_load_ttf_font_from_data(data, RF_DEFAULT_FONT_SIZE, antialias, (int[]) RF_BUILTIN_FONT_CHARS, RF_BUILTIN_CODEPOINTS_COUNT, allocator, temp_allocator); + + // By default we set point filter (best performance) + rf_set_texture_filter(font.texture, RF_FILTER_POINT); + } + + RF_FREE(temp_allocator, data); + } + + return font; +} + +#pragma endregion + +#pragma region image font + +RF_API bool rf_compute_glyph_metrics_from_image(rf_image image, rf_color key, const int* codepoints, rf_glyph_info* dst, rf_int codepoints_and_dst_count) +{ + bool result = false; + + if (image.valid && codepoints && dst && codepoints_and_dst_count > 0) + { + const int bpp = rf_bytes_per_pixel(image.format); + const int image_data_size = rf_image_size(image); + + // This macro uses `bpp` and returns the pixel from the image at the index provided by calling rf_format_one_pixel_to_rgba32. + #define RF_GET_PIXEL(index) rf_format_one_pixel_to_rgba32((char*)image.data + ((index) * bpp), image.format) + + // Parse image data to get char_spacing and line_spacing + int char_spacing = 0; + int line_spacing = 0; + { + int x = 0; + int y = 0; + for (y = 0; y < image.height; y++) + { + rf_color pixel = {0}; + for (x = 0; x < image.width; x++) + { + pixel = RF_GET_PIXEL(y * image.width + x); + if (!rf_color_match(pixel, key)) break; + } + + if (!rf_color_match(pixel, key)) break; + } + char_spacing = x; + line_spacing = y; + } + + // Compute char height + int char_height = 0; + { + while (!rf_color_match(RF_GET_PIXEL((line_spacing + char_height) * image.width + char_spacing), key)) + { + char_height++; + } + } + + // Check array values to get characters: value, x, y, w, h + int index = 0; + int line_to_read = 0; + int x_pos_to_read = char_spacing; + + // Parse image data to get rectangle sizes + while ((line_spacing + line_to_read * (char_height + line_spacing)) < image.height && index < codepoints_and_dst_count) + { + while (x_pos_to_read < image.width && !rf_color_match(RF_GET_PIXEL((line_spacing + (char_height + line_spacing) * line_to_read) * image.width + x_pos_to_read), key)) + { + int char_width = 0; + while (!rf_color_match(RF_GET_PIXEL(((line_spacing + (char_height + line_spacing) * line_to_read) * image.width + x_pos_to_read + char_width)), key)) { + char_width++; + } + + dst[index].codepoint = codepoints[index]; + dst[index].x = (float) x_pos_to_read; + dst[index].y = (float) (line_spacing + line_to_read * (char_height + line_spacing)); + dst[index].width = (float) char_width; + dst[index].height = (float) char_height; + + // On image based fonts (XNA style), character offsets and x_advance are not required (set to 0) + dst[index].offset_x = 0; + dst[index].offset_y = 0; + dst[index].advance_x = 0; + + index++; + + x_pos_to_read += (char_width + char_spacing); + } + + line_to_read++; + x_pos_to_read = char_spacing; + } + + result = true; + + #undef RF_GET_PIXEL + } + + return result; +} + +RF_API rf_font rf_load_image_font_from_data(rf_image image, rf_glyph_info* glyphs, rf_int glyphs_count) +{ + rf_font result = { + .texture = rf_load_texture_from_image(image), + .glyphs = glyphs, + .glyphs_count = glyphs_count, + }; + + if (image.valid && glyphs && glyphs_count > 0) + { + result.base_size = glyphs[0].height; + result.valid = true; + } + + return result; +} + +RF_API rf_font rf_load_image_font(rf_image image, rf_color key, rf_allocator allocator) +{ + rf_font result = {0}; + + if (image.valid) + { + const int codepoints[] = RF_BUILTIN_FONT_CHARS; + const int codepoints_count = RF_BUILTIN_CODEPOINTS_COUNT; + + rf_glyph_info* glyphs = RF_ALLOC(allocator, codepoints_count * sizeof(rf_glyph_info)); + + rf_compute_glyph_metrics_from_image(image, key, codepoints, glyphs, codepoints_count); + + result = rf_load_image_font_from_data(image, glyphs, codepoints_count); + } + + return result; +} + +RF_API rf_font rf_load_image_font_from_file(const char* path, rf_color key, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_font result = {0}; + + rf_image image = rf_load_image_from_file(path, temp_allocator, temp_allocator, io); + + result = rf_load_image_font(image, key, allocator); + + rf_unload_image(image, temp_allocator); + + return result; +} + +#pragma endregion + +// Unload rf_font from GPU memory (VRAM) +RF_API void rf_unload_font(rf_font font, rf_allocator allocator) +{ + if (font.valid) + { + rf_unload_texture(font.texture); + RF_FREE(allocator, font.glyphs); + } +} + +// Returns index position for a unicode character on spritefont +RF_API rf_glyph_index rf_get_glyph_index(rf_font font, int character) +{ + rf_glyph_index result = RF_GLYPH_NOT_FOUND; + + for (rf_int i = 0; i < font.glyphs_count; i++) + { + if (font.glyphs[i].codepoint == character) + { + result = i; + break; + } + } + + return result; +} + +RF_API int rf_font_height(rf_font font, float font_size) +{ + float scale_factor = font_size / font.base_size; + return (font.base_size + font.base_size / 2) * scale_factor; +} + +RF_API rf_sizef rf_measure_text(rf_font font, const char* text, float font_size, float extra_spacing) +{ + rf_sizef result = rf_measure_string(font, text, strlen(text), font_size, extra_spacing); + return result; +} + +RF_API rf_sizef rf_measure_text_rec(rf_font font, const char* text, rf_rec rec, float font_size, float extra_spacing, bool wrap) +{ + rf_sizef result = rf_measure_string_rec(font, text, strlen(text), rec, font_size, extra_spacing, wrap); + return result; +} + +RF_API rf_sizef rf_measure_string(rf_font font, const char* text, int len, float font_size, float extra_spacing) +{ + rf_sizef result = {0}; + + if (font.valid) + { + int temp_len = 0; // Used to count longer text line num chars + int len_counter = 0; + + float text_width = 0.0f; + float temp_text_width = 0.0f; // Used to count longer text line width + + float text_height = (float)font.base_size; + float scale_factor = font_size/(float)font.base_size; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + for (rf_int i = 0; i < len; i++) + { + len_counter++; + + rf_decoded_rune decoded_rune = rf_decode_utf8_char(&text[i], len - i); + index = rf_get_glyph_index(font, decoded_rune.codepoint); + + // NOTE: normally we exit the decoding sequence as soon as a bad unsigned char is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) { decoded_rune.bytes_processed = 1; } + i += decoded_rune.bytes_processed - 1; + + if (letter != '\n') + { + if (font.glyphs[index].advance_x != 0) { text_width += font.glyphs[index].advance_x; } + else { text_width += (font.glyphs[index].width + font.glyphs[index].offset_x); } + } + else + { + if (temp_text_width < text_width) { temp_text_width = text_width; } + + len_counter = 0; + text_width = 0; + text_height += ((float)font.base_size*1.5f); // NOTE: Fixed line spacing of 1.5 lines + } + + if (temp_len < len_counter) { temp_len = len_counter; } + } + + if (temp_text_width < text_width) temp_text_width = text_width; + + result.width = temp_text_width * scale_factor + (float)((temp_len - 1) * extra_spacing); // Adds chars spacing to measure + result.height = text_height * scale_factor; + } + + return result; +} + +RF_API rf_sizef rf_measure_string_rec(rf_font font, const char* text, int text_len, rf_rec rec, float font_size, float extra_spacing, bool wrap) +{ + rf_sizef result = {0}; + + if (font.valid) + { + int text_offset_x = 0; // Offset between characters + int text_offset_y = 0; // Required for line break! + float scale_factor = 0.0f; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + scale_factor = font_size / font.base_size; + + enum + { + MEASURE_WRAP_STATE = 0, + MEASURE_REGULAR_STATE = 1 + }; + + int state = wrap ? MEASURE_WRAP_STATE : MEASURE_REGULAR_STATE; + int start_line = -1; // Index where to begin drawing (where a line begins) + int end_line = -1; // Index where to stop drawing (where a line ends) + int lastk = -1; // Holds last value of the character position + + int max_y = 0; + int first_y = 0; + bool first_y_set = false; + + for (rf_int i = 0, k = 0; i < text_len; i++, k++) + { + int glyph_width = 0; + + rf_decoded_rune decoded_rune = rf_decode_utf8_char(&text[i], text_len - i); + letter = decoded_rune.codepoint; + index = rf_get_glyph_index(font, letter); + + // NOTE: normally we exit the decoding sequence as soon as a bad unsigned char is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) decoded_rune.bytes_processed = 1; + i += decoded_rune.bytes_processed - 1; + + if (letter != '\n') + { + glyph_width = (font.glyphs[index].advance_x == 0) ? + (int)(font.glyphs[index].width * scale_factor + extra_spacing) : + (int)(font.glyphs[index].advance_x * scale_factor + extra_spacing); + } + + // NOTE: When word_wrap is ON we first measure how much of the text we can draw before going outside of the rec container + // We store this info in start_line and end_line, then we change states, draw the text between those two variables + // and change states again and again recursively until the end of the text (or until we get outside of the container). + // When word_wrap is OFF we don't need the measure state so we go to the drawing state immediately + // and begin drawing on the next line before we can get outside the container. + if (state == MEASURE_WRAP_STATE) + { + // TODO: there are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more + // See: http://jkorpela.fi/chars/spaces.html + if ((letter == ' ') || (letter == '\t') || (letter == '\n')) { end_line = i; } + + if ((text_offset_x + glyph_width + 1) >= rec.width) + { + end_line = (end_line < 1) ? i : end_line; + if (i == end_line) { end_line -= decoded_rune.bytes_processed; } + if ((start_line + decoded_rune.bytes_processed) == end_line) { end_line = i - decoded_rune.bytes_processed; } + state = !state; + } + else if ((i + 1) == text_len) + { + end_line = i; + state = !state; + } + else if (letter == '\n') + { + state = !state; + } + + if (state == MEASURE_REGULAR_STATE) + { + text_offset_x = 0; + i = start_line; + glyph_width = 0; + + // Save character position when we switch states + int tmp = lastk; + lastk = k - 1; + k = tmp; + } + } + else + { + if (letter == '\n') + { + if (!wrap) + { + text_offset_y += (int)((font.base_size + font.base_size/2)*scale_factor); + text_offset_x = 0; + } + } + else + { + if (!wrap && (text_offset_x + glyph_width + 1) >= rec.width) + { + text_offset_y += (int)((font.base_size + font.base_size/2)*scale_factor); + text_offset_x = 0; + } + + if ((text_offset_y + (int)(font.base_size*scale_factor)) > rec.height) break; + + // The right side expression is the offset of the latest character plus its width (so the end of the line) + // We want the highest value of that expression by the end of the function + result.width = rf_max_f(result.width, rec.x + text_offset_x - 1 + glyph_width); + + if (!first_y_set) + { + first_y = rec.y + text_offset_y; + first_y_set = true; + } + + max_y = rf_max_i(max_y, rec.y + text_offset_y + font.base_size * scale_factor); + } + + if (wrap && i == end_line) + { + text_offset_y += (int)((font.base_size + font.base_size/2)*scale_factor); + text_offset_x = 0; + start_line = end_line; + end_line = -1; + glyph_width = 0; + k = lastk; + state = !state; + } + } + + text_offset_x += glyph_width; + } + + result.height = max_y - first_y; + } + + return result; +} +/*** End of inlined file: rayfork-font.c ***/ + + +/*** Start of inlined file: rayfork-render-batch.c ***/ +RF_API rf_render_batch rf_create_custom_render_batch_from_buffers(rf_vertex_buffer* vertex_buffers, rf_int vertex_buffers_count, rf_draw_call* draw_calls, rf_int draw_calls_count) +{ + if (!vertex_buffers || !draw_calls || vertex_buffers_count < 0 || draw_calls_count < 0) { + return (rf_render_batch) {0}; + } + + rf_render_batch batch = {0}; + batch.vertex_buffers = vertex_buffers; + batch.vertex_buffers_count = vertex_buffers_count; + batch.draw_calls = draw_calls; + batch.draw_calls_size = draw_calls_count; + + for (rf_int i = 0; i < vertex_buffers_count; i++) + { + memset(vertex_buffers[i].vertices, 0, RF_GFX_VERTEX_COMPONENT_COUNT * vertex_buffers[i].elements_count); + memset(vertex_buffers[i].texcoords, 0, RF_GFX_TEXCOORD_COMPONENT_COUNT * vertex_buffers[i].elements_count); + memset(vertex_buffers[i].colors, 0, RF_GFX_COLOR_COMPONENT_COUNT * vertex_buffers[i].elements_count); + + int k = 0; + + // Indices can be initialized right now + for (rf_int j = 0; j < (RF_GFX_VERTEX_INDEX_COMPONENT_COUNT * vertex_buffers[i].elements_count); j += 6) + { + vertex_buffers[i].indices[j + 0] = 4 * k + 0; + vertex_buffers[i].indices[j + 1] = 4 * k + 1; + vertex_buffers[i].indices[j + 2] = 4 * k + 2; + vertex_buffers[i].indices[j + 3] = 4 * k + 0; + vertex_buffers[i].indices[j + 4] = 4 * k + 2; + vertex_buffers[i].indices[j + 5] = 4 * k + 3; + + k++; + } + + vertex_buffers[i].v_counter = 0; + vertex_buffers[i].tc_counter = 0; + vertex_buffers[i].c_counter = 0; + + rf_gfx_init_vertex_buffer(&vertex_buffers[i]); + } + + for (rf_int i = 0; i < RF_DEFAULT_BATCH_DRAW_CALLS_COUNT; i++) + { + batch.draw_calls[i] = (rf_draw_call) { + .mode = RF_QUADS, + .texture_id = rf_ctx.default_texture_id, + }; + } + + batch.draw_calls_counter = 1; // Reset draws counter + batch.current_depth = -1.0f; // Reset depth value + batch.valid = true; + + return batch; +} + +// TODO: Not working yet +RF_API rf_render_batch rf_create_custom_render_batch(rf_int vertex_buffers_count, rf_int draw_calls_count, rf_int vertex_buffer_elements_count, rf_allocator allocator) +{ + if (vertex_buffers_count < 0 || draw_calls_count < 0 || vertex_buffer_elements_count < 0) { + return (rf_render_batch) {0}; + } + + rf_render_batch result = {0}; + + rf_int vertex_buffer_array_size = sizeof(rf_vertex_buffer) * vertex_buffers_count; + rf_int vertex_buffers_memory_size = (sizeof(rf_one_element_vertex_buffer) * vertex_buffer_elements_count) * vertex_buffers_count; + rf_int draw_calls_array_size = sizeof(rf_draw_call) * draw_calls_count; + rf_int allocation_size = vertex_buffer_array_size + draw_calls_array_size + vertex_buffers_memory_size; + + char* memory = RF_ALLOC(allocator, allocation_size); + + if (memory) + { + rf_vertex_buffer* buffers = (rf_vertex_buffer*) memory; + rf_draw_call* draw_calls = (rf_draw_call*) (memory + vertex_buffer_array_size); + char* buffers_memory = memory + vertex_buffer_array_size + draw_calls_array_size; + + RF_ASSERT(((char*)draw_calls - memory) == draw_calls_array_size + vertex_buffers_memory_size); + RF_ASSERT((buffers_memory - memory) == vertex_buffers_memory_size); + RF_ASSERT((buffers_memory - memory) == sizeof(rf_one_element_vertex_buffer) * vertex_buffer_elements_count * vertex_buffers_count); + + for (rf_int i = 0; i < vertex_buffers_count; i++) + { + rf_int one_vertex_buffer_memory_size = sizeof(rf_one_element_vertex_buffer) * vertex_buffer_elements_count; + rf_int vertices_size = sizeof(rf_gfx_vertex_data_type) * vertex_buffer_elements_count; + rf_int texcoords_size = sizeof(rf_gfx_texcoord_data_type) * vertex_buffer_elements_count; + rf_int colors_size = sizeof(rf_gfx_color_data_type) * vertex_buffer_elements_count; + rf_int indices_size = sizeof(rf_gfx_vertex_index_data_type) * vertex_buffer_elements_count; + + char* this_buffer_memory = buffers_memory + one_vertex_buffer_memory_size * i; + + buffers[i].elements_count = vertex_buffer_elements_count; + buffers[i].vertices = (rf_gfx_vertex_data_type*) this_buffer_memory; + buffers[i].texcoords = (rf_gfx_texcoord_data_type*) this_buffer_memory + vertices_size; + buffers[i].colors = (rf_gfx_color_data_type*) this_buffer_memory + vertices_size + texcoords_size; + buffers[i].indices = (rf_gfx_vertex_index_data_type*) this_buffer_memory + vertices_size + texcoords_size + colors_size; + } + + result = rf_create_custom_render_batch_from_buffers(buffers, vertex_buffers_count, draw_calls, draw_calls_count); + } + + return result; +} + +RF_API rf_render_batch rf_create_default_render_batch_from_memory(rf_default_render_batch* memory) +{ + if (!memory) { + return (rf_render_batch) {0}; + } + + for (rf_int i = 0; i < RF_DEFAULT_BATCH_VERTEX_BUFFERS_COUNT; i++) + { + memory->vertex_buffers[i].elements_count = RF_DEFAULT_BATCH_ELEMENTS_COUNT; + memory->vertex_buffers[i].vertices = memory->vertex_buffers_memory[i].vertices; + memory->vertex_buffers[i].texcoords = memory->vertex_buffers_memory[i].texcoords; + memory->vertex_buffers[i].colors = memory->vertex_buffers_memory[i].colors; + memory->vertex_buffers[i].indices = memory->vertex_buffers_memory[i].indices; + } + + return rf_create_custom_render_batch_from_buffers(memory->vertex_buffers, RF_DEFAULT_BATCH_VERTEX_BUFFERS_COUNT, memory->draw_calls, RF_DEFAULT_BATCH_DRAW_CALLS_COUNT); +} + +RF_API rf_render_batch rf_create_default_render_batch(rf_allocator allocator) +{ + rf_default_render_batch* memory = RF_ALLOC(allocator, sizeof(rf_default_render_batch)); + return rf_create_default_render_batch_from_memory(memory); +} + +RF_API void rf_set_active_render_batch(rf_render_batch* batch) +{ + rf_ctx.current_batch = batch; +} + +RF_API void rf_unload_render_batch(rf_render_batch batch, rf_allocator allocator) +{ + RF_FREE(allocator, batch.vertex_buffers); +} +/*** End of inlined file: rayfork-render-batch.c ***/ + + +/*** Start of inlined file: rayfork-3d.c ***/ +#pragma region dependencies + +#pragma region par shapes +#define PAR_SHAPES_IMPLEMENTATION +#define PAR_MALLOC(T, N) ((T*)RF_ALLOC(rf__global_allocator_for_dependencies, N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*)rf_calloc_wrapper(rf__global_allocator_for_dependencies, N, sizeof(T))) +#define PAR_FREE(BUF) (RF_FREE(rf__global_allocator_for_dependencies, BUF)) +#define PAR_REALLOC(T, BUF, N, OLD_SZ) ((T*) rf_realloc_wrapper(rf__global_allocator_for_dependencies, BUF, sizeof(T) * (N), (OLD_SZ))) +#define PARDEF RF_INTERNAL + +/*** Start of inlined file: par_shapes.h ***/ +// SHAPES :: https://github.com/prideout/par +// Simple C library for creation and manipulation of triangle meshes. +// +// The API is divided into three sections: +// +// - Generators. Create parametric surfaces, platonic solids, etc. +// - Queries. Ask a mesh for its axis-aligned bounding box, etc. +// - Transforms. Rotate a mesh, merge it with another, add normals, etc. +// +// In addition to the comment block above each function declaration, the API +// has informal documentation here: +// +// https://prideout.net/shapes +// +// For our purposes, a "mesh" is a list of points and a list of triangles; the +// former is a flattened list of three-tuples (32-bit floats) and the latter is +// also a flattened list of three-tuples (16-bit uints). Triangles are always +// oriented such that their front face winds counter-clockwise. +// +// Optionally, meshes can contain 3D normals (one per vertex), and 2D texture +// coordinates (one per vertex). That's it! If you need something fancier, +// look elsewhere. +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_SHAPES_H +#define PAR_SHAPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#if !defined(_MSC_VER) +# include +#else // MSVC +# if _MSC_VER >= 1800 +# include +# else // stdbool.h missing prior to MSVC++ 12.0 (VS2013) +# define bool int +# define true 1 +# define false 0 +# endif +#endif + +#ifndef PAR_SHAPES_T +#define PAR_SHAPES_T uint16_t +#endif + +#ifndef PARDEF +#define PARDEF extern +#endif + +typedef struct par_shapes_mesh_s { + float* points; // Flat list of 3-tuples (X Y Z X Y Z...) + int npoints; // Number of points + PAR_SHAPES_T* triangles; // Flat list of 3-tuples (I J K I J K...) + int ntriangles; // Number of triangles + float* normals; // Optional list of 3-tuples (X Y Z X Y Z...) + float* tcoords; // Optional list of 2-tuples (U V U V U V...) +} par_shapes_mesh; + +PARDEF void par_shapes_free_mesh(par_shapes_mesh*); + +// Generators ------------------------------------------------------------------ + +// Instance a cylinder that sits on the Z=0 plane using the given tessellation +// levels across the UV domain. Think of "slices" like a number of pizza +// slices, and "stacks" like a number of stacked rings. Height and radius are +// both 1.0, but they can easily be changed with par_shapes_scale. +PARDEF par_shapes_mesh* par_shapes_create_cylinder(int slices, int stacks); + +// Cone is similar to cylinder but the radius diminishes to zero as Z increases. +// Again, height and radius are 1.0, but can be changed with par_shapes_scale. +PARDEF par_shapes_mesh* par_shapes_create_cone(int slices, int stacks); + +// Create a donut that sits on the Z=0 plane with the specified inner radius. +// The outer radius can be controlled with par_shapes_scale. +PARDEF par_shapes_mesh* par_shapes_create_torus(int slices, int stacks, float radius); + +// Create a sphere with texture coordinates and small triangles near the poles. +PARDEF par_shapes_mesh* par_shapes_create_parametric_sphere(int slices, int stacks); + +// Approximate a sphere with a subdivided icosahedron, which produces a nice +// distribution of triangles, but no texture coordinates. Each subdivision +// level scales the number of triangles by four, so use a very low number. +PARDEF par_shapes_mesh* par_shapes_create_subdivided_sphere(int nsubdivisions); + +// More parametric surfaces. +PARDEF par_shapes_mesh* par_shapes_create_klein_bottle(int slices, int stacks); +PARDEF par_shapes_mesh* par_shapes_create_trefoil_knot(int slices, int stacks, + float radius); +PARDEF par_shapes_mesh* par_shapes_create_hemisphere(int slices, int stacks); +PARDEF par_shapes_mesh* par_shapes_create_plane(int slices, int stacks); + +// Create a parametric surface from a callback function that consumes a 2D +// point in [0,1] and produces a 3D point. +typedef void (*par_shapes_fn)(float const*, float*, void*); +PARDEF par_shapes_mesh* par_shapes_create_parametric(par_shapes_fn, int slices, + int stacks, void* userdata); + +// Generate points for a 20-sided polyhedron that fits in the unit sphere. +// Texture coordinates and normals are not generated. +PARDEF par_shapes_mesh* par_shapes_create_icosahedron(); + +// Generate points for a 12-sided polyhedron that fits in the unit sphere. +// Again, texture coordinates and normals are not generated. +PARDEF par_shapes_mesh* par_shapes_create_dodecahedron(); + +// More platonic solids. +PARDEF par_shapes_mesh* par_shapes_create_octahedron(); +PARDEF par_shapes_mesh* par_shapes_create_tetrahedron(); +PARDEF par_shapes_mesh* par_shapes_create_cube(); + +// Generate an orientable disk shape in 3-space. Does not include normals or +// texture coordinates. +PARDEF par_shapes_mesh* par_shapes_create_disk(float radius, int slices, + float const* center, float const* normal); + +// Create an empty shape. Useful for building scenes with merge_and_free. +PARDEF par_shapes_mesh* par_shapes_create_empty(); + +// Generate a rock shape that sits on the Y=0 plane, and sinks into it a bit. +// This includes smooth normals but no texture coordinates. Each subdivision +// level scales the number of triangles by four, so use a very low number. +PARDEF par_shapes_mesh* par_shapes_create_rock(int seed, int nsubdivisions); + +// Create trees or vegetation by executing a recursive turtle graphics program. +// The program is a list of command-argument pairs. See the unit test for +// an example. Texture coordinates and normals are not generated. +PARDEF par_shapes_mesh* par_shapes_create_lsystem(char const* program, int slices, + int maxdepth); + +// Queries --------------------------------------------------------------------- + +// Dump out a text file conforming to the venerable OBJ format. +PARDEF void par_shapes_export(par_shapes_mesh const*, char const* objfile); + +// Take a pointer to 6 floats and set them to min xyz, max xyz. +PARDEF void par_shapes_compute_aabb(par_shapes_mesh const* mesh, float* aabb); + +// Make a deep copy of a mesh. To make a brand new copy, pass null to "target". +// To avoid memory churn, pass an existing mesh to "target". +PARDEF par_shapes_mesh* par_shapes_clone(par_shapes_mesh const* mesh, + par_shapes_mesh* target); + +// Transformations ------------------------------------------------------------- + +PARDEF void par_shapes_merge(par_shapes_mesh* dst, par_shapes_mesh const* src); +PARDEF void par_shapes_translate(par_shapes_mesh*, float x, float y, float z); +PARDEF void par_shapes_rotate(par_shapes_mesh*, float radians, float const* axis); +PARDEF void par_shapes_scale(par_shapes_mesh*, float x, float y, float z); +PARDEF void par_shapes_merge_and_free(par_shapes_mesh* dst, par_shapes_mesh* src); + +// Reverse the winding of a run of faces. Useful when drawing the inside of +// a Cornell Box. Pass 0 for nfaces to reverse every face in the mesh. +PARDEF void par_shapes_invert(par_shapes_mesh*, int startface, int nfaces); + +// Remove all triangles whose area is less than minarea. +PARDEF void par_shapes_remove_degenerate(par_shapes_mesh*, float minarea); + +// Dereference the entire index buffer and replace the point list. +// This creates an inefficient structure, but is useful for drawing facets. +// If create_indices is true, a trivial "0 1 2 3..." index buffer is generated. +PARDEF void par_shapes_unweld(par_shapes_mesh* mesh, bool create_indices); + +// Merge colocated verts, build a new index buffer, and return the +// optimized mesh. Epsilon is the maximum distance to consider when +// welding vertices. The mapping argument can be null, or a pointer to +// npoints integers, which gets filled with the mapping from old vertex +// indices to new indices. +PARDEF par_shapes_mesh* par_shapes_weld(par_shapes_mesh const*, float epsilon, + PAR_SHAPES_T* mapping); + +// Compute smooth normals by averaging adjacent facet normals. +PARDEF void par_shapes_compute_normals(par_shapes_mesh* m); + +// Global Config --------------------------------------------------------------- + +PARDEF void par_shapes_set_epsilon_welded_normals(float epsilon); +PARDEF void par_shapes_set_epsilon_degenerate_sphere(float epsilon); + +// Advanced -------------------------------------------------------------------- + +PARDEF void par_shapes__compute_welded_normals(par_shapes_mesh* m); +PARDEF void par_shapes__connect(par_shapes_mesh* scene, par_shapes_mesh* cylinder, + int slices); + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifndef PAR_MALLOC +#define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define PAR_REALLOC(T, BUF, N, OLD_SZ) ((T*) realloc(BUF, sizeof(T) * (N))) +#define PAR_FREE(BUF) free(BUF) +#endif + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_SHAPES_IMPLEMENTATION +#include +#include +#include +#include +#include +#include +#include + +static float par_shapes__epsilon_welded_normals = 0.001; +static float par_shapes__epsilon_degenerate_sphere = 0.0001; + +static void par_shapes__sphere(float const* uv, float* xyz, void*); +static void par_shapes__hemisphere(float const* uv, float* xyz, void*); +static void par_shapes__plane(float const* uv, float* xyz, void*); +static void par_shapes__klein(float const* uv, float* xyz, void*); +static void par_shapes__cylinder(float const* uv, float* xyz, void*); +static void par_shapes__cone(float const* uv, float* xyz, void*); +static void par_shapes__torus(float const* uv, float* xyz, void*); +static void par_shapes__trefoil(float const* uv, float* xyz, void*); + +struct osn_context; +static int par__simplex_noise(int64_t seed, struct osn_context** ctx); +static void par__simplex_noise_free(struct osn_context* ctx); +static double par__simplex_noise2(struct osn_context* ctx, double x, double y); + +static void par_shapes__copy3(float* result, float const* a) +{ + result[0] = a[0]; + result[1] = a[1]; + result[2] = a[2]; +} + +static float par_shapes__dot3(float const* a, float const* b) +{ + return b[0] * a[0] + b[1] * a[1] + b[2] * a[2]; +} + +static void par_shapes__transform3(float* p, float const* x, float const* y, + float const* z) +{ + float px = par_shapes__dot3(p, x); + float py = par_shapes__dot3(p, y); + float pz = par_shapes__dot3(p, z); + p[0] = px; + p[1] = py; + p[2] = pz; +} + +static void par_shapes__cross3(float* result, float const* a, float const* b) +{ + float x = (a[1] * b[2]) - (a[2] * b[1]); + float y = (a[2] * b[0]) - (a[0] * b[2]); + float z = (a[0] * b[1]) - (a[1] * b[0]); + result[0] = x; + result[1] = y; + result[2] = z; +} + +static void par_shapes__mix3(float* d, float const* a, float const* b, float t) +{ + float x = b[0] * t + a[0] * (1 - t); + float y = b[1] * t + a[1] * (1 - t); + float z = b[2] * t + a[2] * (1 - t); + d[0] = x; + d[1] = y; + d[2] = z; +} + +static void par_shapes__scale3(float* result, float a) +{ + result[0] *= a; + result[1] *= a; + result[2] *= a; +} + +static void par_shapes__normalize3(float* v) +{ + float lsqr = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); + if (lsqr > 0) { + par_shapes__scale3(v, 1.0f / lsqr); + } +} + +static void par_shapes__subtract3(float* result, float const* a) +{ + result[0] -= a[0]; + result[1] -= a[1]; + result[2] -= a[2]; +} + +static void par_shapes__add3(float* result, float const* a) +{ + result[0] += a[0]; + result[1] += a[1]; + result[2] += a[2]; +} + +static float par_shapes__sqrdist3(float const* a, float const* b) +{ + float dx = a[0] - b[0]; + float dy = a[1] - b[1]; + float dz = a[2] - b[2]; + return dx * dx + dy * dy + dz * dz; +} + +PARDEF void par_shapes__compute_welded_normals(par_shapes_mesh* m) +{ + const float epsilon = par_shapes__epsilon_welded_normals; + m->normals = PAR_MALLOC(float, m->npoints * 3); + PAR_SHAPES_T* weldmap = PAR_MALLOC(PAR_SHAPES_T, m->npoints); + par_shapes_mesh* welded = par_shapes_weld(m, epsilon, weldmap); + par_shapes_compute_normals(welded); + float* pdst = m->normals; + for (int i = 0; i < m->npoints; i++, pdst += 3) { + int d = weldmap[i]; + float const* pnormal = welded->normals + d * 3; + pdst[0] = pnormal[0]; + pdst[1] = pnormal[1]; + pdst[2] = pnormal[2]; + } + PAR_FREE(weldmap); + par_shapes_free_mesh(welded); +} + +PARDEF par_shapes_mesh* par_shapes_create_cylinder(int slices, int stacks) +{ + if (slices < 3 || stacks < 1) { + return 0; + } + return par_shapes_create_parametric(par_shapes__cylinder, slices, + stacks, 0); +} + +PARDEF par_shapes_mesh* par_shapes_create_cone(int slices, int stacks) +{ + if (slices < 3 || stacks < 1) { + return 0; + } + return par_shapes_create_parametric(par_shapes__cone, slices, + stacks, 0); +} + +PARDEF par_shapes_mesh* par_shapes_create_parametric_sphere(int slices, int stacks) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + par_shapes_mesh* m = par_shapes_create_parametric(par_shapes__sphere, + slices, stacks, 0); + par_shapes_remove_degenerate(m, par_shapes__epsilon_degenerate_sphere); + return m; +} + +PARDEF par_shapes_mesh* par_shapes_create_hemisphere(int slices, int stacks) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + par_shapes_mesh* m = par_shapes_create_parametric(par_shapes__hemisphere, + slices, stacks, 0); + par_shapes_remove_degenerate(m, par_shapes__epsilon_degenerate_sphere); + return m; +} + +PARDEF par_shapes_mesh* par_shapes_create_torus(int slices, int stacks, float radius) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + assert(radius <= 1.0 && "Use smaller radius to avoid self-intersection."); + assert(radius >= 0.1 && "Use larger radius to avoid self-intersection."); + void* userdata = (void*) &radius; + return par_shapes_create_parametric(par_shapes__torus, slices, + stacks, userdata); +} + +PARDEF par_shapes_mesh* par_shapes_create_klein_bottle(int slices, int stacks) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + par_shapes_mesh* mesh = par_shapes_create_parametric( + par_shapes__klein, slices, stacks, 0); + int face = 0; + for (int stack = 0; stack < stacks; stack++) { + for (int slice = 0; slice < slices; slice++, face += 2) { + if (stack < 27 * stacks / 32) { + par_shapes_invert(mesh, face, 2); + } + } + } + par_shapes__compute_welded_normals(mesh); + return mesh; +} + +PARDEF par_shapes_mesh* par_shapes_create_trefoil_knot(int slices, int stacks, + float radius) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + assert(radius <= 3.0 && "Use smaller radius to avoid self-intersection."); + assert(radius >= 0.5 && "Use larger radius to avoid self-intersection."); + void* userdata = (void*) &radius; + return par_shapes_create_parametric(par_shapes__trefoil, slices, + stacks, userdata); +} + +PARDEF par_shapes_mesh* par_shapes_create_plane(int slices, int stacks) +{ + if (slices < 1 || stacks < 1) { + return 0; + } + return par_shapes_create_parametric(par_shapes__plane, slices, + stacks, 0); +} + +PARDEF par_shapes_mesh* par_shapes_create_parametric(par_shapes_fn fn, + int slices, int stacks, void* userdata) +{ + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + + // Generate verts. + mesh->npoints = (slices + 1) * (stacks + 1); + mesh->points = PAR_CALLOC(float, 3 * mesh->npoints); + float uv[2]; + float xyz[3]; + float* points = mesh->points; + for (int stack = 0; stack < stacks + 1; stack++) { + uv[0] = (float) stack / stacks; + for (int slice = 0; slice < slices + 1; slice++) { + uv[1] = (float) slice / slices; + fn(uv, xyz, userdata); + *points++ = xyz[0]; + *points++ = xyz[1]; + *points++ = xyz[2]; + } + } + + // Generate texture coordinates. + mesh->tcoords = PAR_CALLOC(float, 2 * mesh->npoints); + float* uvs = mesh->tcoords; + for (int stack = 0; stack < stacks + 1; stack++) { + uv[0] = (float) stack / stacks; + for (int slice = 0; slice < slices + 1; slice++) { + uv[1] = (float) slice / slices; + *uvs++ = uv[0]; + *uvs++ = uv[1]; + } + } + + // Generate faces. + mesh->ntriangles = 2 * slices * stacks; + mesh->triangles = PAR_CALLOC(PAR_SHAPES_T, 3 * mesh->ntriangles); + int v = 0; + PAR_SHAPES_T* face = mesh->triangles; + for (int stack = 0; stack < stacks; stack++) { + for (int slice = 0; slice < slices; slice++) { + int next = slice + 1; + *face++ = v + slice + slices + 1; + *face++ = v + next; + *face++ = v + slice; + *face++ = v + slice + slices + 1; + *face++ = v + next + slices + 1; + *face++ = v + next; + } + v += slices + 1; + } + + par_shapes__compute_welded_normals(mesh); + return mesh; +} + +PARDEF void par_shapes_free_mesh(par_shapes_mesh* mesh) +{ + PAR_FREE(mesh->points); + PAR_FREE(mesh->triangles); + PAR_FREE(mesh->normals); + PAR_FREE(mesh->tcoords); + PAR_FREE(mesh); +} + +PARDEF void par_shapes_export(par_shapes_mesh const* mesh, char const* filename) +{ + FILE* objfile = fopen(filename, "wt"); + float const* points = mesh->points; + float const* tcoords = mesh->tcoords; + float const* norms = mesh->normals; + PAR_SHAPES_T const* indices = mesh->triangles; + if (tcoords && norms) { + for (int nvert = 0; nvert < mesh->npoints; nvert++) { + fprintf(objfile, "v %f %f %f\n", points[0], points[1], points[2]); + fprintf(objfile, "vt %f %f\n", tcoords[0], tcoords[1]); + fprintf(objfile, "vn %f %f %f\n", norms[0], norms[1], norms[2]); + points += 3; + norms += 3; + tcoords += 2; + } + for (int nface = 0; nface < mesh->ntriangles; nface++) { + int a = 1 + *indices++; + int b = 1 + *indices++; + int c = 1 + *indices++; + fprintf(objfile, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", + a, a, a, b, b, b, c, c, c); + } + } else if (norms) { + for (int nvert = 0; nvert < mesh->npoints; nvert++) { + fprintf(objfile, "v %f %f %f\n", points[0], points[1], points[2]); + fprintf(objfile, "vn %f %f %f\n", norms[0], norms[1], norms[2]); + points += 3; + norms += 3; + } + for (int nface = 0; nface < mesh->ntriangles; nface++) { + int a = 1 + *indices++; + int b = 1 + *indices++; + int c = 1 + *indices++; + fprintf(objfile, "f %d//%d %d//%d %d//%d\n", a, a, b, b, c, c); + } + } else if (tcoords) { + for (int nvert = 0; nvert < mesh->npoints; nvert++) { + fprintf(objfile, "v %f %f %f\n", points[0], points[1], points[2]); + fprintf(objfile, "vt %f %f\n", tcoords[0], tcoords[1]); + points += 3; + tcoords += 2; + } + for (int nface = 0; nface < mesh->ntriangles; nface++) { + int a = 1 + *indices++; + int b = 1 + *indices++; + int c = 1 + *indices++; + fprintf(objfile, "f %d/%d %d/%d %d/%d\n", a, a, b, b, c, c); + } + } else { + for (int nvert = 0; nvert < mesh->npoints; nvert++) { + fprintf(objfile, "v %f %f %f\n", points[0], points[1], points[2]); + points += 3; + } + for (int nface = 0; nface < mesh->ntriangles; nface++) { + int a = 1 + *indices++; + int b = 1 + *indices++; + int c = 1 + *indices++; + fprintf(objfile, "f %d %d %d\n", a, b, c); + } + } + fclose(objfile); +} + +static void par_shapes__sphere(float const* uv, float* xyz, void* userdata) +{ + float phi = uv[0] * PAR_PI; + float theta = uv[1] * 2 * PAR_PI; + xyz[0] = cosf(theta) * sinf(phi); + xyz[1] = sinf(theta) * sinf(phi); + xyz[2] = cosf(phi); +} + +static void par_shapes__hemisphere(float const* uv, float* xyz, void* userdata) +{ + float phi = uv[0] * PAR_PI; + float theta = uv[1] * PAR_PI; + xyz[0] = cosf(theta) * sinf(phi); + xyz[1] = sinf(theta) * sinf(phi); + xyz[2] = cosf(phi); +} + +static void par_shapes__plane(float const* uv, float* xyz, void* userdata) +{ + xyz[0] = uv[0]; + xyz[1] = uv[1]; + xyz[2] = 0; +} + +static void par_shapes__klein(float const* uv, float* xyz, void* userdata) +{ + float u = uv[0] * PAR_PI; + float v = uv[1] * 2 * PAR_PI; + u = u * 2; + if (u < PAR_PI) { + xyz[0] = 3 * cosf(u) * (1 + sinf(u)) + (2 * (1 - cosf(u) / 2)) * + cosf(u) * cosf(v); + xyz[2] = -8 * sinf(u) - 2 * (1 - cosf(u) / 2) * sinf(u) * cosf(v); + } else { + xyz[0] = 3 * cosf(u) * (1 + sinf(u)) + (2 * (1 - cosf(u) / 2)) * + cosf(v + PAR_PI); + xyz[2] = -8 * sinf(u); + } + xyz[1] = -2 * (1 - cosf(u) / 2) * sinf(v); +} + +static void par_shapes__cylinder(float const* uv, float* xyz, void* userdata) +{ + float theta = uv[1] * 2 * PAR_PI; + xyz[0] = sinf(theta); + xyz[1] = cosf(theta); + xyz[2] = uv[0]; +} + +static void par_shapes__cone(float const* uv, float* xyz, void* userdata) +{ + float r = 1.0f - uv[0]; + float theta = uv[1] * 2 * PAR_PI; + xyz[0] = r * sinf(theta); + xyz[1] = r * cosf(theta); + xyz[2] = uv[0]; +} + +static void par_shapes__torus(float const* uv, float* xyz, void* userdata) +{ + float major = 1; + float minor = *((float*) userdata); + float theta = uv[0] * 2 * PAR_PI; + float phi = uv[1] * 2 * PAR_PI; + float beta = major + minor * cosf(phi); + xyz[0] = cosf(theta) * beta; + xyz[1] = sinf(theta) * beta; + xyz[2] = sinf(phi) * minor; +} + +static void par_shapes__trefoil(float const* uv, float* xyz, void* userdata) +{ + float minor = *((float*) userdata); + const float a = 0.5f; + const float b = 0.3f; + const float c = 0.5f; + const float d = minor * 0.1f; + const float u = (1 - uv[0]) * 4 * PAR_PI; + const float v = uv[1] * 2 * PAR_PI; + const float r = a + b * cos(1.5f * u); + const float x = r * cos(u); + const float y = r * sin(u); + const float z = c * sin(1.5f * u); + float q[3]; + q[0] = + -1.5f * b * sin(1.5f * u) * cos(u) - (a + b * cos(1.5f * u)) * sin(u); + q[1] = + -1.5f * b * sin(1.5f * u) * sin(u) + (a + b * cos(1.5f * u)) * cos(u); + q[2] = 1.5f * c * cos(1.5f * u); + par_shapes__normalize3(q); + float qvn[3] = {q[1], -q[0], 0}; + par_shapes__normalize3(qvn); + float ww[3]; + par_shapes__cross3(ww, q, qvn); + xyz[0] = x + d * (qvn[0] * cos(v) + ww[0] * sin(v)); + xyz[1] = y + d * (qvn[1] * cos(v) + ww[1] * sin(v)); + xyz[2] = z + d * ww[2] * sin(v); +} + +PARDEF void par_shapes_set_epsilon_welded_normals(float epsilon) { + par_shapes__epsilon_welded_normals = epsilon; +} + +PARDEF void par_shapes_set_epsilon_degenerate_sphere(float epsilon) { + par_shapes__epsilon_degenerate_sphere = epsilon; +} + +PARDEF void par_shapes_merge(par_shapes_mesh* dst, par_shapes_mesh const* src) +{ + PAR_SHAPES_T offset = dst->npoints; + int old_dst_npoints = dst->npoints; + int npoints = dst->npoints + src->npoints; + int vecsize = sizeof(float) * 3; + dst->points = PAR_REALLOC(float, dst->points, 3 * npoints, 3 * old_dst_npoints); + memcpy(dst->points + 3 * dst->npoints, src->points, vecsize * src->npoints); + dst->npoints = npoints; + if (src->normals || dst->normals) { + dst->normals = PAR_REALLOC(float, dst->normals, 3 * npoints, 3 * old_dst_npoints); + if (src->normals) { + memcpy(dst->normals + 3 * offset, src->normals, + vecsize * src->npoints); + } + } + if (src->tcoords || dst->tcoords) { + int uvsize = sizeof(float) * 2; + dst->tcoords = PAR_REALLOC(float, dst->tcoords, 2 * npoints, 2 * old_dst_npoints); + if (src->tcoords) { + memcpy(dst->tcoords + 2 * offset, src->tcoords, + uvsize * src->npoints); + } + } + int ntriangles = dst->ntriangles + src->ntriangles; + dst->triangles = PAR_REALLOC(PAR_SHAPES_T, dst->triangles, 3 * ntriangles, 3 * dst->ntriangles); + PAR_SHAPES_T* ptriangles = dst->triangles + 3 * dst->ntriangles; + PAR_SHAPES_T const* striangles = src->triangles; + for (int i = 0; i < src->ntriangles; i++) { + *ptriangles++ = offset + *striangles++; + *ptriangles++ = offset + *striangles++; + *ptriangles++ = offset + *striangles++; + } + dst->ntriangles = ntriangles; +} + +PARDEF par_shapes_mesh* par_shapes_create_disk(float radius, int slices, + float const* center, float const* normal) +{ + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + mesh->npoints = slices + 1; + mesh->points = PAR_MALLOC(float, 3 * mesh->npoints); + float* points = mesh->points; + *points++ = 0; + *points++ = 0; + *points++ = 0; + for (int i = 0; i < slices; i++) { + float theta = i * PAR_PI * 2 / slices; + *points++ = radius * cos(theta); + *points++ = radius * sin(theta); + *points++ = 0; + } + float nnormal[3] = {normal[0], normal[1], normal[2]}; + par_shapes__normalize3(nnormal); + mesh->normals = PAR_MALLOC(float, 3 * mesh->npoints); + float* norms = mesh->normals; + for (int i = 0; i < mesh->npoints; i++) { + *norms++ = nnormal[0]; + *norms++ = nnormal[1]; + *norms++ = nnormal[2]; + } + mesh->ntriangles = slices; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, 3 * mesh->ntriangles); + PAR_SHAPES_T* triangles = mesh->triangles; + for (int i = 0; i < slices; i++) { + *triangles++ = 0; + *triangles++ = 1 + i; + *triangles++ = 1 + (i + 1) % slices; + } + float k[3] = {0, 0, -1}; + float axis[3]; + par_shapes__cross3(axis, nnormal, k); + par_shapes__normalize3(axis); + par_shapes_rotate(mesh, acos(nnormal[2]), axis); + par_shapes_translate(mesh, center[0], center[1], center[2]); + return mesh; +} + +PARDEF par_shapes_mesh* par_shapes_create_empty() +{ + return PAR_CALLOC(par_shapes_mesh, 1); +} + +PARDEF void par_shapes_translate(par_shapes_mesh* m, float x, float y, float z) +{ + float* points = m->points; + for (int i = 0; i < m->npoints; i++) { + *points++ += x; + *points++ += y; + *points++ += z; + } +} + +PARDEF void par_shapes_rotate(par_shapes_mesh* mesh, float radians, float const* axis) +{ + float s = sinf(radians); + float c = cosf(radians); + float x = axis[0]; + float y = axis[1]; + float z = axis[2]; + float xy = x * y; + float yz = y * z; + float zx = z * x; + float oneMinusC = 1.0f - c; + float col0[3] = { + (((x * x) * oneMinusC) + c), + ((xy * oneMinusC) + (z * s)), ((zx * oneMinusC) - (y * s)) + }; + float col1[3] = { + ((xy * oneMinusC) - (z * s)), + (((y * y) * oneMinusC) + c), ((yz * oneMinusC) + (x * s)) + }; + float col2[3] = { + ((zx * oneMinusC) + (y * s)), + ((yz * oneMinusC) - (x * s)), (((z * z) * oneMinusC) + c) + }; + float* p = mesh->points; + for (int i = 0; i < mesh->npoints; i++, p += 3) { + float x = col0[0] * p[0] + col1[0] * p[1] + col2[0] * p[2]; + float y = col0[1] * p[0] + col1[1] * p[1] + col2[1] * p[2]; + float z = col0[2] * p[0] + col1[2] * p[1] + col2[2] * p[2]; + p[0] = x; + p[1] = y; + p[2] = z; + } + float* n = mesh->normals; + if (n) { + for (int i = 0; i < mesh->npoints; i++, n += 3) { + float x = col0[0] * n[0] + col1[0] * n[1] + col2[0] * n[2]; + float y = col0[1] * n[0] + col1[1] * n[1] + col2[1] * n[2]; + float z = col0[2] * n[0] + col1[2] * n[1] + col2[2] * n[2]; + n[0] = x; + n[1] = y; + n[2] = z; + } + } +} + +PARDEF void par_shapes_scale(par_shapes_mesh* m, float x, float y, float z) +{ + float* points = m->points; + for (int i = 0; i < m->npoints; i++) { + *points++ *= x; + *points++ *= y; + *points++ *= z; + } + float* n = m->normals; + if (n && (x != y || x != z || y != z)) { + x = 1.0f / x; + y = 1.0f / y; + z = 1.0f / z; + for (int i = 0; i < m->npoints; i++, n += 3) { + n[0] *= x; + n[1] *= y; + n[2] *= z; + par_shapes__normalize3(n); + } + } +} + +PARDEF void par_shapes_merge_and_free(par_shapes_mesh* dst, par_shapes_mesh* src) +{ + par_shapes_merge(dst, src); + par_shapes_free_mesh(src); +} + +PARDEF void par_shapes_compute_aabb(par_shapes_mesh const* m, float* aabb) +{ + float* points = m->points; + aabb[0] = aabb[3] = points[0]; + aabb[1] = aabb[4] = points[1]; + aabb[2] = aabb[5] = points[2]; + points += 3; + for (int i = 1; i < m->npoints; i++, points += 3) { + aabb[0] = PAR_MIN(points[0], aabb[0]); + aabb[1] = PAR_MIN(points[1], aabb[1]); + aabb[2] = PAR_MIN(points[2], aabb[2]); + aabb[3] = PAR_MAX(points[0], aabb[3]); + aabb[4] = PAR_MAX(points[1], aabb[4]); + aabb[5] = PAR_MAX(points[2], aabb[5]); + } +} + +PARDEF void par_shapes_invert(par_shapes_mesh* m, int face, int nfaces) +{ + nfaces = nfaces ? nfaces : m->ntriangles; + PAR_SHAPES_T* tri = m->triangles + face * 3; + for (int i = 0; i < nfaces; i++) { + PAR_SWAP(PAR_SHAPES_T, tri[0], tri[2]); + tri += 3; + } +} + +PARDEF par_shapes_mesh* par_shapes_create_icosahedron() +{ + static float verts[] = { + 0.000, 0.000, 1.000, + 0.894, 0.000, 0.447, + 0.276, 0.851, 0.447, + -0.724, 0.526, 0.447, + -0.724, -0.526, 0.447, + 0.276, -0.851, 0.447, + 0.724, 0.526, -0.447, + -0.276, 0.851, -0.447, + -0.894, 0.000, -0.447, + -0.276, -0.851, -0.447, + 0.724, -0.526, -0.447, + 0.000, 0.000, -1.000 + }; + static PAR_SHAPES_T faces[] = { + 0,1,2, + 0,2,3, + 0,3,4, + 0,4,5, + 0,5,1, + 7,6,11, + 8,7,11, + 9,8,11, + 10,9,11, + 6,10,11, + 6,2,1, + 7,3,2, + 8,4,3, + 9,5,4, + 10,1,5, + 6,7,2, + 7,8,3, + 8,9,4, + 9,10,5, + 10,6,1 + }; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + mesh->npoints = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->points = PAR_MALLOC(float, sizeof(verts) / 4); + memcpy(mesh->points, verts, sizeof(verts)); + mesh->ntriangles = sizeof(faces) / sizeof(faces[0]) / 3; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, sizeof(faces) / 2); + memcpy(mesh->triangles, faces, sizeof(faces)); + return mesh; +} + +PARDEF par_shapes_mesh* par_shapes_create_dodecahedron() +{ + static float verts[20 * 3] = { + 0.607, 0.000, 0.795, + 0.188, 0.577, 0.795, + -0.491, 0.357, 0.795, + -0.491, -0.357, 0.795, + 0.188, -0.577, 0.795, + 0.982, 0.000, 0.188, + 0.304, 0.934, 0.188, + -0.795, 0.577, 0.188, + -0.795, -0.577, 0.188, + 0.304, -0.934, 0.188, + 0.795, 0.577, -0.188, + -0.304, 0.934, -0.188, + -0.982, 0.000, -0.188, + -0.304, -0.934, -0.188, + 0.795, -0.577, -0.188, + 0.491, 0.357, -0.795, + -0.188, 0.577, -0.795, + -0.607, 0.000, -0.795, + -0.188, -0.577, -0.795, + 0.491, -0.357, -0.795, + }; + static PAR_SHAPES_T pentagons[12 * 5] = { + 0,1,2,3,4, + 5,10,6,1,0, + 6,11,7,2,1, + 7,12,8,3,2, + 8,13,9,4,3, + 9,14,5,0,4, + 15,16,11,6,10, + 16,17,12,7,11, + 17,18,13,8,12, + 18,19,14,9,13, + 19,15,10,5,14, + 19,18,17,16,15 + }; + int npentagons = sizeof(pentagons) / sizeof(pentagons[0]) / 5; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + int ncorners = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->npoints = ncorners; + mesh->points = PAR_MALLOC(float, mesh->npoints * 3); + memcpy(mesh->points, verts, sizeof(verts)); + PAR_SHAPES_T const* pentagon = pentagons; + mesh->ntriangles = npentagons * 3; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* tris = mesh->triangles; + for (int p = 0; p < npentagons; p++, pentagon += 5) { + *tris++ = pentagon[0]; + *tris++ = pentagon[1]; + *tris++ = pentagon[2]; + *tris++ = pentagon[0]; + *tris++ = pentagon[2]; + *tris++ = pentagon[3]; + *tris++ = pentagon[0]; + *tris++ = pentagon[3]; + *tris++ = pentagon[4]; + } + return mesh; +} + +PARDEF par_shapes_mesh* par_shapes_create_octahedron() +{ + static float verts[6 * 3] = { + 0.000, 0.000, 1.000, + 1.000, 0.000, 0.000, + 0.000, 1.000, 0.000, + -1.000, 0.000, 0.000, + 0.000, -1.000, 0.000, + 0.000, 0.000, -1.000 + }; + static PAR_SHAPES_T triangles[8 * 3] = { + 0,1,2, + 0,2,3, + 0,3,4, + 0,4,1, + 2,1,5, + 3,2,5, + 4,3,5, + 1,4,5, + }; + int ntris = sizeof(triangles) / sizeof(triangles[0]) / 3; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + int ncorners = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->npoints = ncorners; + mesh->points = PAR_MALLOC(float, mesh->npoints * 3); + memcpy(mesh->points, verts, sizeof(verts)); + PAR_SHAPES_T const* triangle = triangles; + mesh->ntriangles = ntris; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* tris = mesh->triangles; + for (int p = 0; p < ntris; p++) { + *tris++ = *triangle++; + *tris++ = *triangle++; + *tris++ = *triangle++; + } + return mesh; +} + +PARDEF par_shapes_mesh* par_shapes_create_tetrahedron() +{ + static float verts[4 * 3] = { + 0.000, 1.333, 0, + 0.943, 0, 0, + -0.471, 0, 0.816, + -0.471, 0, -0.816, + }; + static PAR_SHAPES_T triangles[4 * 3] = { + 2,1,0, + 3,2,0, + 1,3,0, + 1,2,3, + }; + int ntris = sizeof(triangles) / sizeof(triangles[0]) / 3; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + int ncorners = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->npoints = ncorners; + mesh->points = PAR_MALLOC(float, mesh->npoints * 3); + memcpy(mesh->points, verts, sizeof(verts)); + PAR_SHAPES_T const* triangle = triangles; + mesh->ntriangles = ntris; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* tris = mesh->triangles; + for (int p = 0; p < ntris; p++) { + *tris++ = *triangle++; + *tris++ = *triangle++; + *tris++ = *triangle++; + } + return mesh; +} + +PARDEF par_shapes_mesh* par_shapes_create_cube() +{ + static float verts[8 * 3] = { + 0, 0, 0, // 0 + 0, 1, 0, // 1 + 1, 1, 0, // 2 + 1, 0, 0, // 3 + 0, 0, 1, // 4 + 0, 1, 1, // 5 + 1, 1, 1, // 6 + 1, 0, 1, // 7 + }; + static PAR_SHAPES_T quads[6 * 4] = { + 7,6,5,4, // front + 0,1,2,3, // back + 6,7,3,2, // right + 5,6,2,1, // top + 4,5,1,0, // left + 7,4,0,3, // bottom + }; + int nquads = sizeof(quads) / sizeof(quads[0]) / 4; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + int ncorners = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->npoints = ncorners; + mesh->points = PAR_MALLOC(float, mesh->npoints * 3); + memcpy(mesh->points, verts, sizeof(verts)); + PAR_SHAPES_T const* quad = quads; + mesh->ntriangles = nquads * 2; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* tris = mesh->triangles; + for (int p = 0; p < nquads; p++, quad += 4) { + *tris++ = quad[0]; + *tris++ = quad[1]; + *tris++ = quad[2]; + *tris++ = quad[2]; + *tris++ = quad[3]; + *tris++ = quad[0]; + } + return mesh; +} + +typedef struct { + char* cmd; + char* arg; +} par_shapes__command; + +typedef struct { + char const* name; + int weight; + int ncommands; + par_shapes__command* commands; +} par_shapes__rule; + +typedef struct { + int pc; + float position[3]; + float scale[3]; + par_shapes_mesh* orientation; + par_shapes__rule* rule; +} par_shapes__stackframe; + +static par_shapes__rule* par_shapes__pick_rule(const char* name, + par_shapes__rule* rules, int nrules) +{ + par_shapes__rule* rule = 0; + int total = 0; + for (int i = 0; i < nrules; i++) { + rule = rules + i; + if (!strcmp(rule->name, name)) { + total += rule->weight; + } + } + float r = (float) rand() / RAND_MAX; + float t = 0; + for (int i = 0; i < nrules; i++) { + rule = rules + i; + if (!strcmp(rule->name, name)) { + t += (float) rule->weight / total; + if (t >= r) { + return rule; + } + } + } + return rule; +} + +static par_shapes_mesh* par_shapes__create_turtle() +{ + const float xaxis[] = {1, 0, 0}; + const float yaxis[] = {0, 1, 0}; + const float zaxis[] = {0, 0, 1}; + par_shapes_mesh* turtle = PAR_CALLOC(par_shapes_mesh, 1); + turtle->npoints = 3; + turtle->points = PAR_CALLOC(float, turtle->npoints * 3); + par_shapes__copy3(turtle->points + 0, xaxis); + par_shapes__copy3(turtle->points + 3, yaxis); + par_shapes__copy3(turtle->points + 6, zaxis); + return turtle; +} + +static par_shapes_mesh* par_shapes__apply_turtle(par_shapes_mesh* mesh, + par_shapes_mesh* turtle, float const* pos, float const* scale) +{ + par_shapes_mesh* m = par_shapes_clone(mesh, 0); + for (int p = 0; p < m->npoints; p++) { + float* pt = m->points + p * 3; + pt[0] *= scale[0]; + pt[1] *= scale[1]; + pt[2] *= scale[2]; + par_shapes__transform3(pt, + turtle->points + 0, turtle->points + 3, turtle->points + 6); + pt[0] += pos[0]; + pt[1] += pos[1]; + pt[2] += pos[2]; + } + return m; +} + +PARDEF void par_shapes__connect(par_shapes_mesh* scene, par_shapes_mesh* cylinder, + int slices) +{ + int stacks = 1; + int npoints = (slices + 1) * (stacks + 1); + assert(scene->npoints >= npoints && "Cannot connect to empty scene."); + + // Create the new point list. + npoints = scene->npoints + (slices + 1); + float* points = PAR_MALLOC(float, npoints * 3); + memcpy(points, scene->points, sizeof(float) * scene->npoints * 3); + float* newpts = points + scene->npoints * 3; + memcpy(newpts, cylinder->points + (slices + 1) * 3, + sizeof(float) * (slices + 1) * 3); + PAR_FREE(scene->points); + scene->points = points; + + // Create the new triangle list. + int ntriangles = scene->ntriangles + 2 * slices * stacks; + PAR_SHAPES_T* triangles = PAR_MALLOC(PAR_SHAPES_T, ntriangles * 3); + memcpy(triangles, scene->triangles, + sizeof(PAR_SHAPES_T) * scene->ntriangles * 3); + int v = scene->npoints - (slices + 1); + PAR_SHAPES_T* face = triangles + scene->ntriangles * 3; + for (int stack = 0; stack < stacks; stack++) { + for (int slice = 0; slice < slices; slice++) { + int next = slice + 1; + *face++ = v + slice + slices + 1; + *face++ = v + next; + *face++ = v + slice; + *face++ = v + slice + slices + 1; + *face++ = v + next + slices + 1; + *face++ = v + next; + } + v += slices + 1; + } + PAR_FREE(scene->triangles); + scene->triangles = triangles; + + scene->npoints = npoints; + scene->ntriangles = ntriangles; +} + +PARDEF par_shapes_mesh* par_shapes_create_lsystem(char const* text, int slices, + int maxdepth) +{ + char* program; + program = PAR_MALLOC(char, strlen(text) + 1); + + // The first pass counts the number of rules and commands. + strcpy(program, text); + char *cmd = strtok(program, " "); + int nrules = 1; + int ncommands = 0; + while (cmd) { + char *arg = strtok(0, " "); + if (!arg) { + puts("lsystem error: unexpected end of program."); + break; + } + if (!strcmp(cmd, "rule")) { + nrules++; + } else { + ncommands++; + } + cmd = strtok(0, " "); + } + + // Allocate space. + par_shapes__rule* rules = PAR_MALLOC(par_shapes__rule, nrules); + par_shapes__command* commands = PAR_MALLOC(par_shapes__command, ncommands); + + // Initialize the entry rule. + par_shapes__rule* current_rule = &rules[0]; + par_shapes__command* current_command = &commands[0]; + current_rule->name = "entry"; + current_rule->weight = 1; + current_rule->ncommands = 0; + current_rule->commands = current_command; + + // The second pass fills in the structures. + strcpy(program, text); + cmd = strtok(program, " "); + while (cmd) { + char *arg = strtok(0, " "); + if (!strcmp(cmd, "rule")) { + current_rule++; + + // Split the argument into a rule name and weight. + char* dot = strchr(arg, '.'); + if (dot) { + current_rule->weight = atoi(dot + 1); + *dot = 0; + } else { + current_rule->weight = 1; + } + + current_rule->name = arg; + current_rule->ncommands = 0; + current_rule->commands = current_command; + } else { + current_rule->ncommands++; + current_command->cmd = cmd; + current_command->arg = arg; + current_command++; + } + cmd = strtok(0, " "); + } + + // For testing purposes, dump out the parsed program. +#ifdef TEST_PARSE + for (int i = 0; i < nrules; i++) { + par_shapes__rule rule = rules[i]; + printf("rule %s.%d\n", rule.name, rule.weight); + for (int c = 0; c < rule.ncommands; c++) { + par_shapes__command cmd = rule.commands[c]; + printf("\t%s %s\n", cmd.cmd, cmd.arg); + } + } +#endif + + // Instantiate the aggregated shape and the template shapes. + par_shapes_mesh* scene = PAR_CALLOC(par_shapes_mesh, 1); + par_shapes_mesh* tube = par_shapes_create_cylinder(slices, 1); + par_shapes_mesh* turtle = par_shapes__create_turtle(); + + // We're not attempting to support texture coordinates and normals + // with L-systems, so remove them from the template shape. + PAR_FREE(tube->normals); + PAR_FREE(tube->tcoords); + tube->normals = 0; + tube->tcoords = 0; + + const float xaxis[] = {1, 0, 0}; + const float yaxis[] = {0, 1, 0}; + const float zaxis[] = {0, 0, 1}; + const float units[] = {1, 1, 1}; + + // Execute the L-system program until the stack size is 0. + par_shapes__stackframe* stack = + PAR_CALLOC(par_shapes__stackframe, maxdepth); + int stackptr = 0; + stack[0].orientation = turtle; + stack[0].rule = &rules[0]; + par_shapes__copy3(stack[0].scale, units); + while (stackptr >= 0) { + par_shapes__stackframe* frame = &stack[stackptr]; + par_shapes__rule* rule = frame->rule; + par_shapes_mesh* turtle = frame->orientation; + float* position = frame->position; + float* scale = frame->scale; + if (frame->pc >= rule->ncommands) { + par_shapes_free_mesh(turtle); + stackptr--; + continue; + } + + par_shapes__command* cmd = rule->commands + (frame->pc++); +#ifdef DUMP_TRACE + printf("%5s %5s %5s:%d %03d\n", cmd->cmd, cmd->arg, rule->name, + frame->pc - 1, stackptr); +#endif + + float value; + if (!strcmp(cmd->cmd, "shape")) { + par_shapes_mesh* m = par_shapes__apply_turtle(tube, turtle, + position, scale); + if (!strcmp(cmd->arg, "connect")) { + par_shapes__connect(scene, m, slices); + } else { + par_shapes_merge(scene, m); + } + par_shapes_free_mesh(m); + } else if (!strcmp(cmd->cmd, "call") && stackptr < maxdepth - 1) { + rule = par_shapes__pick_rule(cmd->arg, rules, nrules); + frame = &stack[++stackptr]; + frame->rule = rule; + frame->orientation = par_shapes_clone(turtle, 0); + frame->pc = 0; + par_shapes__copy3(frame->scale, scale); + par_shapes__copy3(frame->position, position); + continue; + } else { + value = atof(cmd->arg); + if (!strcmp(cmd->cmd, "rx")) { + par_shapes_rotate(turtle, value * PAR_PI / 180.0, xaxis); + } else if (!strcmp(cmd->cmd, "ry")) { + par_shapes_rotate(turtle, value * PAR_PI / 180.0, yaxis); + } else if (!strcmp(cmd->cmd, "rz")) { + par_shapes_rotate(turtle, value * PAR_PI / 180.0, zaxis); + } else if (!strcmp(cmd->cmd, "tx")) { + float vec[3] = {value, 0, 0}; + float t[3] = { + par_shapes__dot3(turtle->points + 0, vec), + par_shapes__dot3(turtle->points + 3, vec), + par_shapes__dot3(turtle->points + 6, vec) + }; + par_shapes__add3(position, t); + } else if (!strcmp(cmd->cmd, "ty")) { + float vec[3] = {0, value, 0}; + float t[3] = { + par_shapes__dot3(turtle->points + 0, vec), + par_shapes__dot3(turtle->points + 3, vec), + par_shapes__dot3(turtle->points + 6, vec) + }; + par_shapes__add3(position, t); + } else if (!strcmp(cmd->cmd, "tz")) { + float vec[3] = {0, 0, value}; + float t[3] = { + par_shapes__dot3(turtle->points + 0, vec), + par_shapes__dot3(turtle->points + 3, vec), + par_shapes__dot3(turtle->points + 6, vec) + }; + par_shapes__add3(position, t); + } else if (!strcmp(cmd->cmd, "sx")) { + scale[0] *= value; + } else if (!strcmp(cmd->cmd, "sy")) { + scale[1] *= value; + } else if (!strcmp(cmd->cmd, "sz")) { + scale[2] *= value; + } else if (!strcmp(cmd->cmd, "sa")) { + scale[0] *= value; + scale[1] *= value; + scale[2] *= value; + } + } + } + PAR_FREE(stack); + PAR_FREE(program); + PAR_FREE(rules); + PAR_FREE(commands); + return scene; +} + +PARDEF void par_shapes_unweld(par_shapes_mesh* mesh, bool create_indices) +{ + int npoints = mesh->ntriangles * 3; + float* points = PAR_MALLOC(float, 3 * npoints); + float* dst = points; + PAR_SHAPES_T const* index = mesh->triangles; + for (int i = 0; i < npoints; i++) { + float const* src = mesh->points + 3 * (*index++); + *dst++ = src[0]; + *dst++ = src[1]; + *dst++ = src[2]; + } + PAR_FREE(mesh->points); + mesh->points = points; + mesh->npoints = npoints; + if (create_indices) { + PAR_SHAPES_T* tris = PAR_MALLOC(PAR_SHAPES_T, 3 * mesh->ntriangles); + PAR_SHAPES_T* index = tris; + for (int i = 0; i < mesh->ntriangles * 3; i++) { + *index++ = i; + } + PAR_FREE(mesh->triangles); + mesh->triangles = tris; + } +} + +PARDEF void par_shapes_compute_normals(par_shapes_mesh* m) +{ + PAR_FREE(m->normals); + m->normals = PAR_CALLOC(float, m->npoints * 3); + PAR_SHAPES_T const* triangle = m->triangles; + float next[3], prev[3], cp[3]; + for (int f = 0; f < m->ntriangles; f++, triangle += 3) { + float const* pa = m->points + 3 * triangle[0]; + float const* pb = m->points + 3 * triangle[1]; + float const* pc = m->points + 3 * triangle[2]; + par_shapes__copy3(next, pb); + par_shapes__subtract3(next, pa); + par_shapes__copy3(prev, pc); + par_shapes__subtract3(prev, pa); + par_shapes__cross3(cp, next, prev); + par_shapes__add3(m->normals + 3 * triangle[0], cp); + par_shapes__copy3(next, pc); + par_shapes__subtract3(next, pb); + par_shapes__copy3(prev, pa); + par_shapes__subtract3(prev, pb); + par_shapes__cross3(cp, next, prev); + par_shapes__add3(m->normals + 3 * triangle[1], cp); + par_shapes__copy3(next, pa); + par_shapes__subtract3(next, pc); + par_shapes__copy3(prev, pb); + par_shapes__subtract3(prev, pc); + par_shapes__cross3(cp, next, prev); + par_shapes__add3(m->normals + 3 * triangle[2], cp); + } + float* normal = m->normals; + for (int p = 0; p < m->npoints; p++, normal += 3) { + par_shapes__normalize3(normal); + } +} + +static void par_shapes__subdivide(par_shapes_mesh* mesh) +{ + assert(mesh->npoints == mesh->ntriangles * 3 && "Must be unwelded."); + int ntriangles = mesh->ntriangles * 4; + int npoints = ntriangles * 3; + float* points = PAR_CALLOC(float, npoints * 3); + float* dpoint = points; + float const* spoint = mesh->points; + for (int t = 0; t < mesh->ntriangles; t++, spoint += 9, dpoint += 3) { + float const* a = spoint; + float const* b = spoint + 3; + float const* c = spoint + 6; + float const* p0 = dpoint; + float const* p1 = dpoint + 3; + float const* p2 = dpoint + 6; + par_shapes__mix3(dpoint, a, b, 0.5); + par_shapes__mix3(dpoint += 3, b, c, 0.5); + par_shapes__mix3(dpoint += 3, a, c, 0.5); + par_shapes__add3(dpoint += 3, a); + par_shapes__add3(dpoint += 3, p0); + par_shapes__add3(dpoint += 3, p2); + par_shapes__add3(dpoint += 3, p0); + par_shapes__add3(dpoint += 3, b); + par_shapes__add3(dpoint += 3, p1); + par_shapes__add3(dpoint += 3, p2); + par_shapes__add3(dpoint += 3, p1); + par_shapes__add3(dpoint += 3, c); + } + PAR_FREE(mesh->points); + mesh->points = points; + mesh->npoints = npoints; + mesh->ntriangles = ntriangles; +} + +PARDEF par_shapes_mesh* par_shapes_create_subdivided_sphere(int nsubd) +{ + par_shapes_mesh* mesh = par_shapes_create_icosahedron(); + par_shapes_unweld(mesh, false); + PAR_FREE(mesh->triangles); + mesh->triangles = 0; + while (nsubd--) { + par_shapes__subdivide(mesh); + } + for (int i = 0; i < mesh->npoints; i++) { + par_shapes__normalize3(mesh->points + i * 3); + } + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, 3 * mesh->ntriangles); + for (int i = 0; i < mesh->ntriangles * 3; i++) { + mesh->triangles[i] = i; + } + par_shapes_mesh* tmp = mesh; + mesh = par_shapes_weld(mesh, 0.01, 0); + par_shapes_free_mesh(tmp); + par_shapes_compute_normals(mesh); + return mesh; +} + +PARDEF par_shapes_mesh* par_shapes_create_rock(int seed, int subd) +{ + par_shapes_mesh* mesh = par_shapes_create_subdivided_sphere(subd); + struct osn_context* ctx; + par__simplex_noise(seed, &ctx); + for (int p = 0; p < mesh->npoints; p++) { + float* pt = mesh->points + p * 3; + float a = 0.25, f = 1.0; + double n = a * par__simplex_noise2(ctx, f * pt[0], f * pt[2]); + a *= 0.5; f *= 2; + n += a * par__simplex_noise2(ctx, f * pt[0], f * pt[2]); + pt[0] *= 1 + 2 * n; + pt[1] *= 1 + n; + pt[2] *= 1 + 2 * n; + if (pt[1] < 0) { + pt[1] = -pow(-pt[1], 0.5) / 2; + } + } + par__simplex_noise_free(ctx); + par_shapes_compute_normals(mesh); + return mesh; +} + +PARDEF par_shapes_mesh* par_shapes_clone(par_shapes_mesh const* mesh, + par_shapes_mesh* clone) +{ + if (!clone) { + clone = PAR_CALLOC(par_shapes_mesh, 1); + } + int old_clone_npoints = clone->npoints; + clone->npoints = mesh->npoints; + clone->points = PAR_REALLOC(float, clone->points, 3 * clone->npoints, 3 * old_clone_npoints); + memcpy(clone->points, mesh->points, sizeof(float) * 3 * clone->npoints); + int old_clone_ntriangles = clone->ntriangles; + clone->ntriangles = mesh->ntriangles; + clone->triangles = PAR_REALLOC(PAR_SHAPES_T, clone->triangles, 3 * + clone->ntriangles, 3 * old_clone_ntriangles); + memcpy(clone->triangles, mesh->triangles, + sizeof(PAR_SHAPES_T) * 3 * clone->ntriangles); + if (mesh->normals) { + clone->normals = PAR_REALLOC(float, clone->normals, 3 * clone->npoints, 3 * old_clone_npoints); + memcpy(clone->normals, mesh->normals, + sizeof(float) * 3 * clone->npoints); + } + if (mesh->tcoords) { + clone->tcoords = PAR_REALLOC(float, clone->tcoords, 2 * clone->npoints, 3 * old_clone_npoints); + memcpy(clone->tcoords, mesh->tcoords, + sizeof(float) * 2 * clone->npoints); + } + return clone; +} + +static struct { + float const* points; + int gridsize; +} par_shapes__sort_context; + +static int par_shapes__cmp1(const void *arg0, const void *arg1) +{ + const int g = par_shapes__sort_context.gridsize; + + // Convert arg0 into a flattened grid index. + PAR_SHAPES_T d0 = *(const PAR_SHAPES_T*) arg0; + float const* p0 = par_shapes__sort_context.points + d0 * 3; + int i0 = (int) p0[0]; + int j0 = (int) p0[1]; + int k0 = (int) p0[2]; + int index0 = i0 + g * j0 + g * g * k0; + + // Convert arg1 into a flattened grid index. + PAR_SHAPES_T d1 = *(const PAR_SHAPES_T*) arg1; + float const* p1 = par_shapes__sort_context.points + d1 * 3; + int i1 = (int) p1[0]; + int j1 = (int) p1[1]; + int k1 = (int) p1[2]; + int index1 = i1 + g * j1 + g * g * k1; + + // Return the ordering. + if (index0 < index1) return -1; + if (index0 > index1) return 1; + return 0; +} + +static void par_shapes__sort_points(par_shapes_mesh* mesh, int gridsize, + PAR_SHAPES_T* sortmap) +{ + // Run qsort over a list of consecutive integers that get deferenced + // within the comparator function; this creates a reorder mapping. + for (int i = 0; i < mesh->npoints; i++) { + sortmap[i] = i; + } + par_shapes__sort_context.gridsize = gridsize; + par_shapes__sort_context.points = mesh->points; + qsort(sortmap, mesh->npoints, sizeof(PAR_SHAPES_T), par_shapes__cmp1); + + // Apply the reorder mapping to the XYZ coordinate data. + float* newpts = PAR_MALLOC(float, mesh->npoints * 3); + PAR_SHAPES_T* invmap = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + float* dstpt = newpts; + for (int i = 0; i < mesh->npoints; i++) { + invmap[sortmap[i]] = i; + float const* srcpt = mesh->points + 3 * sortmap[i]; + *dstpt++ = *srcpt++; + *dstpt++ = *srcpt++; + *dstpt++ = *srcpt++; + } + PAR_FREE(mesh->points); + mesh->points = newpts; + + // Apply the inverse reorder mapping to the triangle indices. + PAR_SHAPES_T* newinds = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* dstind = newinds; + PAR_SHAPES_T const* srcind = mesh->triangles; + for (int i = 0; i < mesh->ntriangles * 3; i++) { + *dstind++ = invmap[*srcind++]; + } + PAR_FREE(mesh->triangles); + mesh->triangles = newinds; + + // Cleanup. + memcpy(sortmap, invmap, sizeof(PAR_SHAPES_T) * mesh->npoints); + PAR_FREE(invmap); +} + +static void par_shapes__weld_points(par_shapes_mesh* mesh, int gridsize, + float epsilon, PAR_SHAPES_T* weldmap) +{ + // Each bin contains a "pointer" (really an index) to its first point. + // We add 1 because 0 is reserved to mean that the bin is empty. + // Since the points are spatially sorted, there's no need to store + // a point count in each bin. + PAR_SHAPES_T* bins = PAR_CALLOC(PAR_SHAPES_T, + gridsize * gridsize * gridsize); + int prev_binindex = -1; + for (int p = 0; p < mesh->npoints; p++) { + float const* pt = mesh->points + p * 3; + int i = (int) pt[0]; + int j = (int) pt[1]; + int k = (int) pt[2]; + int this_binindex = i + gridsize * j + gridsize * gridsize * k; + if (this_binindex != prev_binindex) { + bins[this_binindex] = 1 + p; + } + prev_binindex = this_binindex; + } + + // Examine all bins that intersect the epsilon-sized cube centered at each + // point, and check for colocated points within those bins. + float const* pt = mesh->points; + int nremoved = 0; + for (int p = 0; p < mesh->npoints; p++, pt += 3) { + + // Skip if this point has already been welded. + if (weldmap[p] != p) { + continue; + } + + // Build a list of bins that intersect the epsilon-sized cube. + int nearby[8]; + int nbins = 0; + int minp[3], maxp[3]; + for (int c = 0; c < 3; c++) { + minp[c] = (int) (pt[c] - epsilon); + maxp[c] = (int) (pt[c] + epsilon); + } + for (int i = minp[0]; i <= maxp[0]; i++) { + for (int j = minp[1]; j <= maxp[1]; j++) { + for (int k = minp[2]; k <= maxp[2]; k++) { + int binindex = i + gridsize * j + gridsize * gridsize * k; + PAR_SHAPES_T binvalue = *(bins + binindex); + if (binvalue > 0) { + if (nbins == 8) { + printf("Epsilon value is too large.\n"); + break; + } + nearby[nbins++] = binindex; + } + } + } + } + + // Check for colocated points in each nearby bin. + for (int b = 0; b < nbins; b++) { + int binindex = nearby[b]; + PAR_SHAPES_T binvalue = bins[binindex]; + PAR_SHAPES_T nindex = binvalue - 1; + assert(nindex < mesh->npoints); + while (true) { + + // If this isn't "self" and it's colocated, then weld it! + if (nindex != p && weldmap[nindex] == nindex) { + float const* thatpt = mesh->points + nindex * 3; + float dist2 = par_shapes__sqrdist3(thatpt, pt); + if (dist2 < epsilon) { + weldmap[nindex] = p; + nremoved++; + } + } + + // Advance to the next point if possible. + if (++nindex >= mesh->npoints) { + break; + } + + // If the next point is outside the bin, then we're done. + float const* nextpt = mesh->points + nindex * 3; + int i = (int) nextpt[0]; + int j = (int) nextpt[1]; + int k = (int) nextpt[2]; + int nextbinindex = i + gridsize * j + gridsize * gridsize * k; + if (nextbinindex != binindex) { + break; + } + } + } + } + PAR_FREE(bins); + + // Apply the weldmap to the vertices. + int npoints = mesh->npoints - nremoved; + float* newpts = PAR_MALLOC(float, 3 * npoints); + float* dst = newpts; + PAR_SHAPES_T* condensed_map = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + PAR_SHAPES_T* cmap = condensed_map; + float const* src = mesh->points; + int ci = 0; + for (int p = 0; p < mesh->npoints; p++, src += 3) { + if (weldmap[p] == p) { + *dst++ = src[0]; + *dst++ = src[1]; + *dst++ = src[2]; + *cmap++ = ci++; + } else { + *cmap++ = condensed_map[weldmap[p]]; + } + } + assert(ci == npoints); + PAR_FREE(mesh->points); + memcpy(weldmap, condensed_map, mesh->npoints * sizeof(PAR_SHAPES_T)); + PAR_FREE(condensed_map); + mesh->points = newpts; + mesh->npoints = npoints; + + // Apply the weldmap to the triangle indices and skip the degenerates. + PAR_SHAPES_T const* tsrc = mesh->triangles; + PAR_SHAPES_T* tdst = mesh->triangles; + int ntriangles = 0; + for (int i = 0; i < mesh->ntriangles; i++, tsrc += 3) { + PAR_SHAPES_T a = weldmap[tsrc[0]]; + PAR_SHAPES_T b = weldmap[tsrc[1]]; + PAR_SHAPES_T c = weldmap[tsrc[2]]; + if (a != b && a != c && b != c) { + assert(a < mesh->npoints); + assert(b < mesh->npoints); + assert(c < mesh->npoints); + *tdst++ = a; + *tdst++ = b; + *tdst++ = c; + ntriangles++; + } + } + mesh->ntriangles = ntriangles; +} + +PARDEF par_shapes_mesh* par_shapes_weld(par_shapes_mesh const* mesh, float epsilon, + PAR_SHAPES_T* weldmap) +{ + par_shapes_mesh* clone = par_shapes_clone(mesh, 0); + float aabb[6]; + int gridsize = 20; + float maxcell = gridsize - 1; + par_shapes_compute_aabb(clone, aabb); + float scale[3] = { + aabb[3] == aabb[0] ? 1.0f : maxcell / (aabb[3] - aabb[0]), + aabb[4] == aabb[1] ? 1.0f : maxcell / (aabb[4] - aabb[1]), + aabb[5] == aabb[2] ? 1.0f : maxcell / (aabb[5] - aabb[2]), + }; + par_shapes_translate(clone, -aabb[0], -aabb[1], -aabb[2]); + par_shapes_scale(clone, scale[0], scale[1], scale[2]); + PAR_SHAPES_T* sortmap = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + par_shapes__sort_points(clone, gridsize, sortmap); + bool owner = false; + if (!weldmap) { + owner = true; + weldmap = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + } + for (int i = 0; i < mesh->npoints; i++) { + weldmap[i] = i; + } + par_shapes__weld_points(clone, gridsize, epsilon, weldmap); + if (owner) { + PAR_FREE(weldmap); + } else { + PAR_SHAPES_T* newmap = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + for (int i = 0; i < mesh->npoints; i++) { + newmap[i] = weldmap[sortmap[i]]; + } + memcpy(weldmap, newmap, sizeof(PAR_SHAPES_T) * mesh->npoints); + PAR_FREE(newmap); + } + PAR_FREE(sortmap); + par_shapes_scale(clone, 1.0 / scale[0], 1.0 / scale[1], 1.0 / scale[2]); + par_shapes_translate(clone, aabb[0], aabb[1], aabb[2]); + return clone; +} + +// ----------------------------------------------------------------------------- +// BEGIN OPEN SIMPLEX NOISE +// ----------------------------------------------------------------------------- + +#define STRETCH_CONSTANT_2D (-0.211324865405187) // (1 / sqrt(2 + 1) - 1 ) / 2; +#define SQUISH_CONSTANT_2D (0.366025403784439) // (sqrt(2 + 1) -1) / 2; +#define STRETCH_CONSTANT_3D (-1.0 / 6.0) // (1 / sqrt(3 + 1) - 1) / 3; +#define SQUISH_CONSTANT_3D (1.0 / 3.0) // (sqrt(3+1)-1)/3; +#define STRETCH_CONSTANT_4D (-0.138196601125011) // (1 / sqrt(4 + 1) - 1) / 4; +#define SQUISH_CONSTANT_4D (0.309016994374947) // (sqrt(4 + 1) - 1) / 4; + +#define NORM_CONSTANT_2D (47.0) +#define NORM_CONSTANT_3D (103.0) +#define NORM_CONSTANT_4D (30.0) + +#define DEFAULT_SEED (0LL) + +struct osn_context { + int16_t* perm; + int16_t* permGradIndex3D; +}; + +#define ARRAYSIZE(x) (sizeof((x)) / sizeof((x)[0])) + +/* + * Gradients for 2D. They approximate the directions to the + * vertices of an octagon from the center. + */ +static const int8_t gradients2D[] = { + 5, 2, 2, 5, -5, 2, -2, 5, 5, -2, 2, -5, -5, -2, -2, -5, +}; + +/* + * Gradients for 3D. They approximate the directions to the + * vertices of a rhombicuboctahedron from the center, skewed so + * that the triangular and square facets can be inscribed inside + * circles of the same radius. + */ +static const signed char gradients3D[] = { + -11, 4, 4, -4, 11, 4, -4, 4, 11, 11, 4, 4, 4, 11, 4, 4, 4, 11, -11, -4, 4, + -4, -11, 4, -4, -4, 11, 11, -4, 4, 4, -11, 4, 4, -4, 11, -11, 4, -4, -4, 11, + -4, -4, 4, -11, 11, 4, -4, 4, 11, -4, 4, 4, -11, -11, -4, -4, -4, -11, -4, + -4, -4, -11, 11, -4, -4, 4, -11, -4, 4, -4, -11, +}; + +/* + * Gradients for 4D. They approximate the directions to the + * vertices of a disprismatotesseractihexadecachoron from the center, + * skewed so that the tetrahedral and cubic facets can be inscribed inside + * spheres of the same radius. + */ +static const signed char gradients4D[] = { + 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, -3, 1, 1, 1, -1, 3, 1, 1, + -1, 1, 3, 1, -1, 1, 1, 3, 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, 1, 1, -1, 1, + 3, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3, 3, 1, -1, 1, 1, + 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, + 1, -1, 1, -1, 3, 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, -3, + -1, -1, 1, -1, -3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, 3, 1, 1, -1, 1, 3, + 1, -1, 1, 1, 3, -1, 1, 1, 1, -3, -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3, -1, + -1, 1, 1, -3, 3, -1, 1, -1, 1, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, -3, + -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, 3, 1, -1, -1, 1, 3, + -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, + -1, -1, 1, -1, -3, 3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3, -1, 1, -1, -1, + -3, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, +}; + +static double extrapolate2( + struct osn_context* ctx, int xsb, int ysb, double dx, double dy) +{ + int16_t* perm = ctx->perm; + int index = perm[(perm[xsb & 0xFF] + ysb) & 0xFF] & 0x0E; + return gradients2D[index] * dx + gradients2D[index + 1] * dy; +} + +static inline int fastFloor(double x) +{ + int xi = (int) x; + return x < xi ? xi - 1 : xi; +} + +static int allocate_perm(struct osn_context* ctx, int nperm, int ngrad) +{ + PAR_FREE(ctx->perm); + PAR_FREE(ctx->permGradIndex3D); + ctx->perm = PAR_MALLOC(int16_t, nperm); + if (!ctx->perm) { + return -ENOMEM; + } + ctx->permGradIndex3D = PAR_MALLOC(int16_t, ngrad); + if (!ctx->permGradIndex3D) { + PAR_FREE(ctx->perm); + return -ENOMEM; + } + return 0; +} + +static int par__simplex_noise(int64_t seed, struct osn_context** ctx) +{ + int rc; + int16_t source[256]; + int i; + int16_t* perm; + int16_t* permGradIndex3D; + *ctx = PAR_MALLOC(struct osn_context, 1); + if (!(*ctx)) { + return -ENOMEM; + } + (*ctx)->perm = NULL; + (*ctx)->permGradIndex3D = NULL; + rc = allocate_perm(*ctx, 256, 256); + if (rc) { + PAR_FREE(*ctx); + return rc; + } + perm = (*ctx)->perm; + permGradIndex3D = (*ctx)->permGradIndex3D; + for (i = 0; i < 256; i++) { + source[i] = (int16_t) i; + } + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + for (i = 255; i >= 0; i--) { + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + int r = (int) ((seed + 31) % (i + 1)); + if (r < 0) + r += (i + 1); + perm[i] = source[r]; + permGradIndex3D[i] = + (short) ((perm[i] % (ARRAYSIZE(gradients3D) / 3)) * 3); + source[r] = source[i]; + } + return 0; +} + +static void par__simplex_noise_free(struct osn_context* ctx) +{ + if (!ctx) + return; + if (ctx->perm) { + PAR_FREE(ctx->perm); + ctx->perm = NULL; + } + if (ctx->permGradIndex3D) { + PAR_FREE(ctx->permGradIndex3D); + ctx->permGradIndex3D = NULL; + } + PAR_FREE(ctx); +} + +static double par__simplex_noise2(struct osn_context* ctx, double x, double y) +{ + // Place input coordinates onto grid. + double stretchOffset = (x + y) * STRETCH_CONSTANT_2D; + double xs = x + stretchOffset; + double ys = y + stretchOffset; + + // Floor to get grid coordinates of rhombus (stretched square) super-cell + // origin. + int xsb = fastFloor(xs); + int ysb = fastFloor(ys); + + // Skew out to get actual coordinates of rhombus origin. We'll need these + // later. + double squishOffset = (xsb + ysb) * SQUISH_CONSTANT_2D; + double xb = xsb + squishOffset; + double yb = ysb + squishOffset; + + // Compute grid coordinates relative to rhombus origin. + double xins = xs - xsb; + double yins = ys - ysb; + + // Sum those together to get a value that determines which region we're in. + double inSum = xins + yins; + + // Positions relative to origin point. + double dx0 = x - xb; + double dy0 = y - yb; + + // We'll be defining these inside the next block and using them afterwards. + double dx_ext, dy_ext; + int xsv_ext, ysv_ext; + + double value = 0; + + // Contribution (1,0) + double dx1 = dx0 - 1 - SQUISH_CONSTANT_2D; + double dy1 = dy0 - 0 - SQUISH_CONSTANT_2D; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate2(ctx, xsb + 1, ysb + 0, dx1, dy1); + } + + // Contribution (0,1) + double dx2 = dx0 - 0 - SQUISH_CONSTANT_2D; + double dy2 = dy0 - 1 - SQUISH_CONSTANT_2D; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate2(ctx, xsb + 0, ysb + 1, dx2, dy2); + } + + if (inSum <= 1) { // We're inside the triangle (2-Simplex) at (0,0) + double zins = 1 - inSum; + if (zins > xins || zins > yins) { + if (xins > yins) { + xsv_ext = xsb + 1; + ysv_ext = ysb - 1; + dx_ext = dx0 - 1; + dy_ext = dy0 + 1; + } else { + xsv_ext = xsb - 1; + ysv_ext = ysb + 1; + dx_ext = dx0 + 1; + dy_ext = dy0 - 1; + } + } else { //(1,0) and (0,1) are the closest two vertices. + xsv_ext = xsb + 1; + ysv_ext = ysb + 1; + dx_ext = dx0 - 1 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 - 1 - 2 * SQUISH_CONSTANT_2D; + } + } else { // We're inside the triangle (2-Simplex) at (1,1) + double zins = 2 - inSum; + if (zins < xins || zins < yins) { + if (xins > yins) { + xsv_ext = xsb + 2; + ysv_ext = ysb + 0; + dx_ext = dx0 - 2 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 + 0 - 2 * SQUISH_CONSTANT_2D; + } else { + xsv_ext = xsb + 0; + ysv_ext = ysb + 2; + dx_ext = dx0 + 0 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 - 2 - 2 * SQUISH_CONSTANT_2D; + } + } else { //(1,0) and (0,1) are the closest two vertices. + dx_ext = dx0; + dy_ext = dy0; + xsv_ext = xsb; + ysv_ext = ysb; + } + xsb += 1; + ysb += 1; + dx0 = dx0 - 1 - 2 * SQUISH_CONSTANT_2D; + dy0 = dy0 - 1 - 2 * SQUISH_CONSTANT_2D; + } + + // Contribution (0,0) or (1,1) + double attn0 = 2 - dx0 * dx0 - dy0 * dy0; + if (attn0 > 0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate2(ctx, xsb, ysb, dx0, dy0); + } + + // Extra Vertex + double attn_ext = 2 - dx_ext * dx_ext - dy_ext * dy_ext; + if (attn_ext > 0) { + attn_ext *= attn_ext; + value += attn_ext * attn_ext * + extrapolate2(ctx, xsv_ext, ysv_ext, dx_ext, dy_ext); + } + + return value / NORM_CONSTANT_2D; +} + +PARDEF void par_shapes_remove_degenerate(par_shapes_mesh* mesh, float mintriarea) +{ + int ntriangles = 0; + PAR_SHAPES_T* triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* dst = triangles; + PAR_SHAPES_T const* src = mesh->triangles; + float next[3], prev[3], cp[3]; + float mincplen2 = (mintriarea * 2) * (mintriarea * 2); + for (int f = 0; f < mesh->ntriangles; f++, src += 3) { + float const* pa = mesh->points + 3 * src[0]; + float const* pb = mesh->points + 3 * src[1]; + float const* pc = mesh->points + 3 * src[2]; + par_shapes__copy3(next, pb); + par_shapes__subtract3(next, pa); + par_shapes__copy3(prev, pc); + par_shapes__subtract3(prev, pa); + par_shapes__cross3(cp, next, prev); + float cplen2 = par_shapes__dot3(cp, cp); + if (cplen2 >= mincplen2) { + *dst++ = src[0]; + *dst++ = src[1]; + *dst++ = src[2]; + ntriangles++; + } + } + mesh->ntriangles = ntriangles; + PAR_FREE(mesh->triangles); + mesh->triangles = triangles; +} + +#endif // PAR_SHAPES_IMPLEMENTATION +#endif // PAR_SHAPES_H + +// par_shapes is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// 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. +/*** End of inlined file: par_shapes.h ***/ + + +#pragma endregion + +#pragma region tinyobj loader +RF_INTERNAL RF_THREAD_LOCAL rf_io_callbacks rf__tinyobj_io; +#define RF_SET_TINYOBJ_ALLOCATOR(allocator) rf__tinyobj_allocator = allocator +#define RF_SET_TINYOBJ_IO_CALLBACKS(io) rf__tinyobj_io = io; + +#define TINYOBJ_LOADER_C_IMPLEMENTATION +#define TINYOBJ_MALLOC(size) (RF_ALLOC(rf__global_allocator_for_dependencies, (size))) +#define TINYOBJ_REALLOC(p, oldsz, newsz) (rf_realloc_wrapper(rf__global_allocator_for_dependencies, (p), (oldsz), (newsz))) +#define TINYOBJ_CALLOC(amount, size) (rf_calloc_wrapper(rf__global_allocator_for_dependencies, (amount), (size))) +#define TINYOBJ_FREE(p) (RF_FREE(rf__global_allocator_for_dependencies, (p))) +#define TINYOBJDEF RF_INTERNAL + +/*** Start of inlined file: tinyobjloader.h ***/ +#ifndef TINOBJ_LOADER_C_H_ +#define TINOBJ_LOADER_C_H_ + +/* @todo { Remove stddef dependency. size_t? } */ +#include + +#ifndef TINYOBJDEF +#define TINYOBJDEF extern +#endif + +typedef struct { + char *name; + + float ambient[3]; + float diffuse[3]; + float specular[3]; + float transmittance[3]; + float emission[3]; + float shininess; + float ior; /* index of refraction */ + float dissolve; /* 1 == opaque; 0 == fully transparent */ + /* illumination model (see http://www.fileformat.info/format/material/) */ + int illum; + + int pad0; + + char *ambient_texname; /* map_Ka */ + char *diffuse_texname; /* map_Kd */ + char *specular_texname; /* map_Ks */ + char *specular_highlight_texname; /* map_Ns */ + char *bump_texname; /* map_bump, bump */ + char *displacement_texname; /* disp */ + char *alpha_texname; /* map_d */ +} tinyobj_material_t; + +typedef struct { + char *name; /* group name or object name. */ + unsigned int face_offset; + unsigned int length; +} tinyobj_shape_t; + +typedef struct { int v_idx, vt_idx, vn_idx; } tinyobj_vertex_index_t; + +typedef struct { + unsigned int num_vertices; + unsigned int num_normals; + unsigned int num_texcoords; + unsigned int num_faces; + unsigned int num_face_num_verts; + + int pad0; + + float *vertices; + float *normals; + float *texcoords; + tinyobj_vertex_index_t *faces; + int *face_num_verts; + int *material_ids; +} tinyobj_attrib_t; + +#define TINYOBJ_FLAG_TRIANGULATE (1 << 0) + +#define TINYOBJ_INVALID_INDEX (0x80000000) + +#define TINYOBJ_SUCCESS (0) +#define TINYOBJ_ERROR_EMPTY (-1) +#define TINYOBJ_ERROR_INVALID_PARAMETER (-2) +#define TINYOBJ_ERROR_FILE_OPERATION (-3) + +/* Provide a callback that can read text file without any parsing or modification. + * The obj and mtl parser is going to read all the necessary data: + * tinyobj_parse_obj + * tinyobj_parse_mtl_file + */ +typedef void (*file_reader_callback)(const char *filename, char **buf, size_t *len); + +/* Parse wavefront .obj(.obj string data is expanded to linear char array `buf') + * flags are combination of TINYOBJ_FLAG_*** + * Returns TINYOBJ_SUCCESS if things goes well. + * Returns TINYOBJ_ERR_*** when there is an error. + */ +TINYOBJDEF int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, + size_t *num_shapes, tinyobj_material_t **materials, + size_t *num_materials, const char *filename, file_reader_callback file_reader, + unsigned int flags); +TINYOBJDEF int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, + size_t *num_materials_out, + const char *filename, file_reader_callback file_reader); + +TINYOBJDEF void tinyobj_attrib_init(tinyobj_attrib_t *attrib); +TINYOBJDEF void tinyobj_attrib_free(tinyobj_attrib_t *attrib); +TINYOBJDEF void tinyobj_shapes_free(tinyobj_shape_t *shapes, size_t num_shapes); +TINYOBJDEF void tinyobj_materials_free(tinyobj_material_t *materials, + size_t num_materials); + +#ifdef TINYOBJ_LOADER_C_IMPLEMENTATION +#include +#include +#include +#include + +#if defined(TINYOBJ_MALLOC) && defined(TINYOBJ_REALLOC) && defined(TINYOBJ_CALLOC) && defined(TINYOBJ_FREE) +/* ok */ +#elif !defined(TINYOBJ_MALLOC) && !defined(TINYOBJ_REALLOC) && !defined(TINYOBJ_CALLOC) && !defined(TINYOBJ_FREE) +/* ok */ +#else +#error "Must define all or none of TINYOBJ_MALLOC, TINYOBJ_REALLOC, TINYOBJ_CALLOC and TINYOBJ_FREE." +#endif + +#ifndef TINYOBJ_MALLOC +#include +#define TINYOBJ_MALLOC malloc +#define TINYOBJ_REALLOC realloc +#define TINYOBJ_CALLOC calloc +#define TINYOBJ_FREE free +#endif + +#define TINYOBJ_MAX_FACES_PER_F_LINE (16) + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) ((unsigned int)((x) - '0') < (unsigned int)(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +static void skip_space(const char **token) { + while ((*token)[0] == ' ' || (*token)[0] == '\t') { + (*token)++; + } +} + +static void skip_space_and_cr(const char **token) { + while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') { + (*token)++; + } +} + +static int until_space(const char *token) { + const char *p = token; + while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') { + p++; + } + + return (int)(p - token); +} + +static size_t length_until_newline(const char *token, size_t n) { + size_t len = 0; + + /* Assume token[n-1] = '\0' */ + for (len = 0; len < n - 1; len++) { + if (token[len] == '\n') { + break; + } + if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) { + break; + } + } + + return len; +} + +static size_t length_until_line_feed(const char *token, size_t n) { + size_t len = 0; + + /* Assume token[n-1] = '\0' */ + for (len = 0; len < n; len++) { + if ((token[len] == '\n') || (token[len] == '\r')) { + break; + } + } + + return len; +} + +/* http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work +*/ +static int my_atoi(const char *c) { + int value = 0; + int sign = 1; + if (*c == '+' || *c == '-') { + if (*c == '-') sign = -1; + c++; + } + while (((*c) >= '0') && ((*c) <= '9')) { /* isdigit(*c) */ + value *= 10; + value += (int)(*c - '0'); + c++; + } + return value * sign; +} + +/* Make index zero-base, and also support relative index. */ +static int fixIndex(int idx, size_t n) { + if (idx > 0) return idx - 1; + if (idx == 0) return 0; + return (int)n + idx; /* negative value = relative */ +} + +/* Parse raw triples: i, i/j/k, i//k, i/j */ +static tinyobj_vertex_index_t parseRawTriple(const char **token) { + tinyobj_vertex_index_t vi; + /* 0x80000000 = -2147483648 = invalid */ + vi.v_idx = (int)(0x80000000); + vi.vn_idx = (int)(0x80000000); + vi.vt_idx = (int)(0x80000000); + + vi.v_idx = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + /* i//k */ + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; + } + + /* i/j/k or i/j */ + vi.vt_idx = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + + /* i/j/k */ + (*token)++; /* skip '/' */ + vi.vn_idx = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; +} + +static int parseInt(const char **token) { + int i = 0; + skip_space(token); + i = my_atoi((*token)); + (*token) += until_space((*token)); + return i; +} + +/* + * Tries to parse a floating point number located at s. + * + * s_end should be a location in the string where reading should absolutely + * stop. For example at the end of the string, to prevent buffer overflows. + * + * Parses the following EBNF grammar: + * sign = "+" | "-" ; + * END = ? anything not in digit ? + * digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; + * integer = [sign] , digit , {digit} ; + * decimal = integer , ["." , integer] ; + * float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; + * + * Valid strings are for example: + * -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 + * + * If the parsing is a success, result is set to the parsed value and true + * is returned. + * + * The function is greedy and will parse until any of the following happens: + * - a non-conforming character is encountered. + * - s_end is reached. + * + * The following situations triggers a failure: + * - s >= s_end. + * - parse failure. + */ +static int tryParseDouble(const char *s, const char *s_end, double *result) { + double mantissa = 0.0; + /* This exponent is base 2 rather than 10. + * However the exponent we parse is supposed to be one of ten, + * thus we must take care to convert the exponent/and or the + * mantissa to a * 2^E, where a is the mantissa and E is the + * exponent. + * To get the final double we will use ldexp, it requires the + * exponent to be in base 2. + */ + int exponent = 0; + + /* NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + * TO JUMP OVER DEFINITIONS. + */ + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + /* How many characters were read in a loop. */ + int read = 0; + /* Tells whether a loop terminated due to reaching s_end. */ + int end_not_reached = 0; + + /* + BEGIN PARSING. + */ + + if (s >= s_end) { + return 0; /* fail */ + } + + /* Find out what sign we've got. */ + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + goto fail; + } + + /* Read the integer part. */ + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += (int)(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + /* We must make sure we actually got something. */ + if (read == 0) goto fail; + /* We allow numbers of form "#", "###" etc. */ + if (!end_not_reached) goto assemble; + + /* Read the decimal part. */ + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + /* pow(10.0, -read) */ + double frac_value = 1.0; + int f; + for (f = 0; f < read; f++) { + frac_value *= 0.1; + } + mantissa += (int)(*curr - 0x30) * frac_value; + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + /* Read the exponent part. */ + if (*curr == 'e' || *curr == 'E') { + curr++; + /* Figure out if a sign is present and if it is. */ + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + /* Empty E is not allowed. */ + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + exponent *= 10; + exponent += (int)(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + if (read == 0) goto fail; + } + +assemble : + + { + double a = 1.0; /* = pow(5.0, exponent); */ + double b = 1.0; /* = 2.0^exponent */ + int i; + for (i = 0; i < exponent; i++) { + a = a * 5.0; + } + + for (i = 0; i < exponent; i++) { + b = b * 2.0; + } + + if (exp_sign == '-') { + a = 1.0 / a; + b = 1.0 / b; + } + + *result = + /* (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), + exponent); */ + (sign == '+' ? 1 : -1) * (mantissa * a * b); + } + + return 1; +fail: + return 0; +} + +static float parseFloat(const char **token) { + const char *end; + double val = 0.0; + float f = 0.0f; + skip_space(token); + end = (*token) + until_space((*token)); + val = 0.0; + tryParseDouble((*token), end, &val); + f = (float)(val); + (*token) = end; + return f; +} + +static void parseFloat2(float *x, float *y, const char **token) { + (*x) = parseFloat(token); + (*y) = parseFloat(token); +} + +static void parseFloat3(float *x, float *y, float *z, const char **token) { + (*x) = parseFloat(token); + (*y) = parseFloat(token); + (*z) = parseFloat(token); +} + +static size_t my_strnlen(const char *s, size_t n) { + const char *p = memchr(s, 0, n); + return p ? (size_t)(p - s) : n; +} + +static char *my_strdup(const char *s, size_t max_length) { + char *d; + size_t len; + + if (s == NULL) return NULL; + + /* Do not consider CRLF line ending(#19) */ + len = length_until_line_feed(s, max_length); + /* len = strlen(s); */ + + /* trim line ending and append '\0' */ + d = (char *)TINYOBJ_MALLOC(len + 1); /* + '\0' */ + memcpy(d, s, (size_t)(len)); + d[len] = '\0'; + + return d; +} + +static char *my_strndup(const char *s, size_t len) { + char *d; + size_t slen; + + if (s == NULL) return NULL; + if (len == 0) return NULL; + + slen = my_strnlen(s, len); + d = (char *)TINYOBJ_MALLOC(slen + 1); /* + '\0' */ + if (!d) { + return NULL; + } + memcpy(d, s, slen); + d[slen] = '\0'; + + return d; +} + +char *dynamic_fgets(char **buf, size_t *size, FILE *file) { + char *offset; + char *ret; + size_t old_size; + + if (!(ret = fgets(*buf, (int)*size, file))) { + return ret; + } + + if (NULL != strchr(*buf, '\n')) { + return ret; + } + + do { + old_size = *size; + *size *= 2; + *buf = (char*)TINYOBJ_REALLOC(*buf, old_size, *size); + offset = &((*buf)[old_size - 1]); + + ret = fgets(offset, (int)(old_size + 1), file); + } while(ret && (NULL == strchr(*buf, '\n'))); + + return ret; +} + +static void initMaterial(tinyobj_material_t *material) { + int i; + material->name = NULL; + material->ambient_texname = NULL; + material->diffuse_texname = NULL; + material->specular_texname = NULL; + material->specular_highlight_texname = NULL; + material->bump_texname = NULL; + material->displacement_texname = NULL; + material->alpha_texname = NULL; + for (i = 0; i < 3; i++) { + material->ambient[i] = 0.f; + material->diffuse[i] = 0.f; + material->specular[i] = 0.f; + material->transmittance[i] = 0.f; + material->emission[i] = 0.f; + } + material->illum = 0; + material->dissolve = 1.f; + material->shininess = 1.f; + material->ior = 1.f; +} + +/* Implementation of string to int hashtable */ + +#define HASH_TABLE_ERROR 1 +#define HASH_TABLE_SUCCESS 0 + +#define HASH_TABLE_DEFAULT_SIZE 10 + +typedef struct hash_table_entry_t +{ + unsigned long hash; + int filled; + int pad0; + long value; + + struct hash_table_entry_t* next; +} hash_table_entry_t; + +typedef struct +{ + unsigned long* hashes; + hash_table_entry_t* entries; + size_t capacity; + size_t n; +} hash_table_t; + +static unsigned long hash_djb2(const unsigned char* str) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++)) { + hash = ((hash << 5) + hash) + (unsigned long)(c); + } + + return hash; +} + +static void create_hash_table(size_t start_capacity, hash_table_t* hash_table) +{ + if (start_capacity < 1) + start_capacity = HASH_TABLE_DEFAULT_SIZE; + hash_table->hashes = (unsigned long*) TINYOBJ_MALLOC(start_capacity * sizeof(unsigned long)); + hash_table->entries = (hash_table_entry_t*) TINYOBJ_CALLOC(start_capacity, sizeof(hash_table_entry_t)); + hash_table->capacity = start_capacity; + hash_table->n = 0; +} + +static void destroy_hash_table(hash_table_t* hash_table) +{ + TINYOBJ_FREE(hash_table->entries); + TINYOBJ_FREE(hash_table->hashes); +} + +/* Insert with quadratic probing */ +static int hash_table_insert_value(unsigned long hash, long value, hash_table_t* hash_table) +{ + /* Insert value */ + size_t start_index = hash % hash_table->capacity; + size_t index = start_index; + hash_table_entry_t* start_entry = hash_table->entries + start_index; + size_t i; + hash_table_entry_t* entry; + + for (i = 1; hash_table->entries[index].filled; i++) + { + if (i >= hash_table->capacity) + return HASH_TABLE_ERROR; + index = (start_index + (i * i)) % hash_table->capacity; + } + + entry = hash_table->entries + index; + entry->hash = hash; + entry->filled = 1; + entry->value = value; + + if (index != start_index) { + /* This is a new entry, but not the start entry, hence we need to add a next pointer to our entry */ + entry->next = start_entry->next; + start_entry->next = entry; + } + + return HASH_TABLE_SUCCESS; +} + +static int hash_table_insert(unsigned long hash, long value, hash_table_t* hash_table) +{ + int ret = hash_table_insert_value(hash, value, hash_table); + if (ret == HASH_TABLE_SUCCESS) + { + hash_table->hashes[hash_table->n] = hash; + hash_table->n++; + } + return ret; +} + +static hash_table_entry_t* hash_table_find(unsigned long hash, hash_table_t* hash_table) +{ + hash_table_entry_t* entry = hash_table->entries + (hash % hash_table->capacity); + while (entry) + { + if (entry->hash == hash && entry->filled) + { + return entry; + } + entry = entry->next; + } + return NULL; +} + +static void hash_table_maybe_grow(size_t new_n, hash_table_t* hash_table) +{ + size_t new_capacity; + hash_table_t new_hash_table; + size_t i; + + if (new_n <= hash_table->capacity) { + return; + } + new_capacity = 2 * ((2 * hash_table->capacity) > new_n ? hash_table->capacity : new_n); + /* Create a new hash table. We're not calling create_hash_table because we want to realloc the hash array */ + new_hash_table.hashes = hash_table->hashes = (unsigned long*) TINYOBJ_REALLOC((void*) hash_table->hashes, sizeof(unsigned long) * new_hash_table.capacity, sizeof(unsigned long) * new_capacity); + new_hash_table.entries = (hash_table_entry_t*) TINYOBJ_CALLOC(new_capacity, sizeof(hash_table_entry_t)); + new_hash_table.capacity = new_capacity; + new_hash_table.n = hash_table->n; + + /* Rehash */ + for (i = 0; i < hash_table->capacity; i++) + { + hash_table_entry_t* entry = hash_table_find(hash_table->hashes[i], hash_table); + hash_table_insert_value(hash_table->hashes[i], entry->value, &new_hash_table); + } + + TINYOBJ_FREE(hash_table->entries); + (*hash_table) = new_hash_table; +} + +static int hash_table_exists(const char* name, hash_table_t* hash_table) +{ + return hash_table_find(hash_djb2((const unsigned char*)name), hash_table) != NULL; +} + +static void hash_table_set(const char* name, size_t val, hash_table_t* hash_table) +{ + /* Hash name */ + unsigned long hash = hash_djb2((const unsigned char *)name); + + hash_table_entry_t* entry = hash_table_find(hash, hash_table); + if (entry) + { + entry->value = (long)val; + return; + } + + /* Expand if necessary + * Grow until the element has been added + */ + do + { + hash_table_maybe_grow(hash_table->n + 1, hash_table); + } + while (hash_table_insert(hash, (long)val, hash_table) != HASH_TABLE_SUCCESS); +} + +static long hash_table_get(const char* name, hash_table_t* hash_table) +{ + hash_table_entry_t* ret = hash_table_find(hash_djb2((const unsigned char*)(name)), hash_table); + return ret->value; +} + +static tinyobj_material_t *tinyobj_material_add(tinyobj_material_t *prev, + size_t num_materials, + tinyobj_material_t *new_mat) { + tinyobj_material_t *dst; + dst = (tinyobj_material_t *)TINYOBJ_REALLOC( + prev, sizeof(tinyobj_material_t) * num_materials, sizeof(tinyobj_material_t) * (num_materials + 1)); + + dst[num_materials] = (*new_mat); /* Just copy pointer for char* members */ + return dst; +} + +static int is_line_ending(const char *p, size_t i, size_t end_i) { + if (p[i] == '\0') return 1; + if (p[i] == '\n') return 1; /* this includes \r\n */ + if (p[i] == '\r') { + if (((i + 1) < end_i) && (p[i + 1] != '\n')) { /* detect only \r case */ + return 1; + } + } + return 0; +} + +typedef struct { + size_t pos; + size_t len; +} LineInfo; + +/* Find '\n' and create line data. */ +static int get_line_infos(const char *buf, size_t buf_len, LineInfo **line_infos, size_t *num_lines) +{ + size_t i = 0; + size_t end_idx = buf_len; + size_t prev_pos = 0; + size_t line_no = 0; + size_t last_line_ending = 0; + + /* Count # of lines. */ + for (i = 0; i < end_idx; i++) { + if (is_line_ending(buf, i, end_idx)) { + (*num_lines)++; + last_line_ending = i; + } + } + /* The last char from the input may not be a line + * ending character so add an extra line if there + * are more characters after the last line ending + * that was found. */ + if (end_idx - last_line_ending > 0) { + (*num_lines)++; + } + + if (*num_lines == 0) return TINYOBJ_ERROR_EMPTY; + + *line_infos = (LineInfo *)TINYOBJ_MALLOC(sizeof(LineInfo) * (*num_lines)); + + /* Fill line infos. */ + for (i = 0; i < end_idx; i++) { + if (is_line_ending(buf, i, end_idx)) { + (*line_infos)[line_no].pos = prev_pos; + (*line_infos)[line_no].len = i - prev_pos; + prev_pos = i + 1; + line_no++; + } + } + if (end_idx - last_line_ending > 0) { + (*line_infos)[line_no].pos = prev_pos; + (*line_infos)[line_no].len = end_idx - 1 - last_line_ending; + } + + return 0; +} + +static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, + size_t *num_materials_out, + const char *filename, file_reader_callback file_reader, + hash_table_t* material_table) { + tinyobj_material_t material; + size_t num_materials = 0; + tinyobj_material_t *materials = NULL; + int has_previous_material = 0; + const char *line_end = NULL; + size_t num_lines = 0; + LineInfo *line_infos = NULL; + size_t i = 0; + char *buf = NULL; + size_t len = 0; + + if (materials_out == NULL) { + return TINYOBJ_ERROR_INVALID_PARAMETER; + } + + if (num_materials_out == NULL) { + return TINYOBJ_ERROR_INVALID_PARAMETER; + } + + (*materials_out) = NULL; + (*num_materials_out) = 0; + + file_reader(filename, &buf, &len); + if (len < 1) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (buf == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + + if (get_line_infos(buf, len, &line_infos, &num_lines) != 0) { + return TINYOBJ_ERROR_EMPTY; + } + + /* Create a default material */ + initMaterial(&material); + + for (i = 0; i < num_lines; i++) { + const char *p = &buf[line_infos[i].pos]; + size_t p_len = line_infos[i].len; + + char linebuf[4096]; + const char *token; + assert(p_len < 4095); + + memcpy(linebuf, p, p_len); + linebuf[p_len] = '\0'; + + token = linebuf; + line_end = token + p_len; + + /* Skip leading space. */ + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; /* empty line */ + + if (token[0] == '#') continue; /* comment line */ + + /* new mtl */ + if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { + char namebuf[4096]; + + /* flush previous material. */ + if (has_previous_material) { + materials = tinyobj_material_add(materials, num_materials, &material); + num_materials++; + } else { + has_previous_material = 1; + } + + /* initial temporary material */ + initMaterial(&material); + + /* set new mtl name */ + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + material.name = my_strdup(namebuf, (size_t) (line_end - token)); + + /* Add material to material table */ + if (material_table) + hash_table_set(material.name, num_materials, material_table); + + continue; + } + + /* ambient */ + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + /* diffuse */ + if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + continue; + } + + /* specular */ + if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + /* transmittance */ + if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + /* ior(index of refraction) */ + if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseFloat(&token); + continue; + } + + /* emission */ + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { + float r, g, b; + token += 2; + parseFloat3(&r, &g, &b, &token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + /* shininess */ + if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseFloat(&token); + continue; + } + + /* illum model */ + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + /* dissolve */ + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseFloat(&token); + continue; + } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + /* Invert value of Tr(assume Tr is in range [0, 1]) */ + material.dissolve = 1.0f - parseFloat(&token); + continue; + } + + /* ambient texture */ + if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { + token += 7; + material.ambient_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + + /* diffuse texture */ + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + material.diffuse_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + + /* specular texture */ + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + + /* specular highlight texture */ + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_highlight_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + + /* bump texture */ + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + material.bump_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + + /* alpha texture */ + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + + /* bump texture */ + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + material.bump_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + + /* displacement texture */ + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + material.displacement_texname = my_strdup(token, (size_t) (line_end - token)); + continue; + } + + /* @todo { unknown parameter } */ + } + + if (material.name) { + /* Flush last material element */ + materials = tinyobj_material_add(materials, num_materials, &material); + num_materials++; + } + + (*num_materials_out) = num_materials; + (*materials_out) = materials; + + return TINYOBJ_SUCCESS; +} + +TINYOBJDEF int tinyobj_parse_mtl_file(tinyobj_material_t **materials_out, + size_t *num_materials_out, + const char *filename, file_reader_callback file_reader) { + return tinyobj_parse_and_index_mtl_file(materials_out, num_materials_out, filename, file_reader, NULL); +} + +typedef enum { + COMMAND_EMPTY, + COMMAND_V, + COMMAND_VN, + COMMAND_VT, + COMMAND_F, + COMMAND_G, + COMMAND_O, + COMMAND_USEMTL, + COMMAND_MTLLIB + +} CommandType; + +typedef struct { + float vx, vy, vz; + float nx, ny, nz; + float tx, ty; + + /* @todo { Use dynamic array } */ + tinyobj_vertex_index_t f[TINYOBJ_MAX_FACES_PER_F_LINE]; + size_t num_f; + + int f_num_verts[TINYOBJ_MAX_FACES_PER_F_LINE]; + size_t num_f_num_verts; + + const char *group_name; + unsigned int group_name_len; + int pad0; + + const char *object_name; + unsigned int object_name_len; + int pad1; + + const char *material_name; + unsigned int material_name_len; + int pad2; + + const char *mtllib_name; + unsigned int mtllib_name_len; + + CommandType type; +} Command; + +static int parseLine(Command *command, const char *p, size_t p_len, + int triangulate) { + char linebuf[4096]; + const char *token; + assert(p_len < 4095); + + memcpy(linebuf, p, p_len); + linebuf[p_len] = '\0'; + + token = linebuf; + + command->type = COMMAND_EMPTY; + + /* Skip leading space. */ + skip_space(&token); + + assert(token); + if (token[0] == '\0') { /* empty line */ + return 0; + } + + if (token[0] == '#') { /* comment line */ + return 0; + } + + /* vertex */ + if (token[0] == 'v' && IS_SPACE((token[1]))) { + float x, y, z; + token += 2; + parseFloat3(&x, &y, &z, &token); + command->vx = x; + command->vy = y; + command->vz = z; + command->type = COMMAND_V; + return 1; + } + + /* normal */ + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + float x, y, z; + token += 3; + parseFloat3(&x, &y, &z, &token); + command->nx = x; + command->ny = y; + command->nz = z; + command->type = COMMAND_VN; + return 1; + } + + /* texcoord */ + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + float x, y; + token += 3; + parseFloat2(&x, &y, &token); + command->tx = x; + command->ty = y; + command->type = COMMAND_VT; + return 1; + } + + /* face */ + if (token[0] == 'f' && IS_SPACE((token[1]))) { + size_t num_f = 0; + + tinyobj_vertex_index_t f[TINYOBJ_MAX_FACES_PER_F_LINE]; + token += 2; + skip_space(&token); + + while (!IS_NEW_LINE(token[0])) { + tinyobj_vertex_index_t vi = parseRawTriple(&token); + skip_space_and_cr(&token); + + f[num_f] = vi; + num_f++; + } + + command->type = COMMAND_F; + + if (triangulate) { + size_t k; + size_t n = 0; + + tinyobj_vertex_index_t i0 = f[0]; + tinyobj_vertex_index_t i1; + tinyobj_vertex_index_t i2 = f[1]; + + assert(3 * num_f < TINYOBJ_MAX_FACES_PER_F_LINE); + + for (k = 2; k < num_f; k++) { + i1 = i2; + i2 = f[k]; + command->f[3 * n + 0] = i0; + command->f[3 * n + 1] = i1; + command->f[3 * n + 2] = i2; + + command->f_num_verts[n] = 3; + n++; + } + command->num_f = 3 * n; + command->num_f_num_verts = n; + + } else { + size_t k = 0; + assert(num_f < TINYOBJ_MAX_FACES_PER_F_LINE); + for (k = 0; k < num_f; k++) { + command->f[k] = f[k]; + } + + command->num_f = num_f; + command->f_num_verts[0] = (int)num_f; + command->num_f_num_verts = 1; + } + + return 1; + } + + /* use mtl */ + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + token += 7; + + skip_space(&token); + command->material_name = p + (token - linebuf); + command->material_name_len = (unsigned int)length_until_newline( + token, (p_len - (size_t)(token - linebuf)) + 1); + command->type = COMMAND_USEMTL; + + return 1; + } + + /* load mtl */ + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + /* By specification, `mtllib` should be appear only once in .obj */ + token += 7; + + skip_space(&token); + command->mtllib_name = p + (token - linebuf); + command->mtllib_name_len = (unsigned int)length_until_newline( + token, p_len - (size_t)(token - linebuf)) + + 1; + command->type = COMMAND_MTLLIB; + + return 1; + } + + /* group name */ + if (token[0] == 'g' && IS_SPACE((token[1]))) { + /* @todo { multiple group name. } */ + token += 2; + + command->group_name = p + (token - linebuf); + command->group_name_len = (unsigned int)length_until_newline( + token, p_len - (size_t)(token - linebuf)) + + 1; + command->type = COMMAND_G; + + return 1; + } + + /* object name */ + if (token[0] == 'o' && IS_SPACE((token[1]))) { + /* @todo { multiple object name? } */ + token += 2; + + command->object_name = p + (token - linebuf); + command->object_name_len = (unsigned int)length_until_newline( + token, p_len - (size_t)(token - linebuf)) + + 1; + command->type = COMMAND_O; + + return 1; + } + + return 0; +} + +TINYOBJDEF int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, + size_t *num_shapes, tinyobj_material_t **materials_out, + size_t *num_materials_out, const char *filename, file_reader_callback file_reader, + unsigned int flags) { + LineInfo *line_infos = NULL; + Command *commands = NULL; + size_t num_lines = 0; + + size_t num_v = 0; + size_t num_vn = 0; + size_t num_vt = 0; + size_t num_f = 0; + size_t num_faces = 0; + + int mtllib_line_index = -1; + + tinyobj_material_t *materials = NULL; + size_t num_materials = 0; + + hash_table_t material_table; + + char *buf = NULL; + size_t len = 0; + file_reader(filename, &buf, &len); + + if (len < 1) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (attrib == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (shapes == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (num_shapes == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (buf == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (materials_out == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + if (num_materials_out == NULL) return TINYOBJ_ERROR_INVALID_PARAMETER; + + tinyobj_attrib_init(attrib); + + /* 1. create line data */ + if (get_line_infos(buf, len, &line_infos, &num_lines) != 0) { + return TINYOBJ_ERROR_EMPTY; + } + + commands = (Command *)TINYOBJ_MALLOC(sizeof(Command) * num_lines); + + create_hash_table(HASH_TABLE_DEFAULT_SIZE, &material_table); + + /* 2. parse each line */ + { + size_t i = 0; + for (i = 0; i < num_lines; i++) { + int ret = parseLine(&commands[i], &buf[line_infos[i].pos], + line_infos[i].len, flags & TINYOBJ_FLAG_TRIANGULATE); + if (ret) { + if (commands[i].type == COMMAND_V) { + num_v++; + } else if (commands[i].type == COMMAND_VN) { + num_vn++; + } else if (commands[i].type == COMMAND_VT) { + num_vt++; + } else if (commands[i].type == COMMAND_F) { + num_f += commands[i].num_f; + num_faces += commands[i].num_f_num_verts; + } + + if (commands[i].type == COMMAND_MTLLIB) { + mtllib_line_index = (int)i; + } + } + } + } + + /* line_infos are not used anymore. Release memory. */ + if (line_infos) { + TINYOBJ_FREE(line_infos); + } + + /* Load material(if exits) */ + if (mtllib_line_index >= 0 && commands[mtllib_line_index].mtllib_name && + commands[mtllib_line_index].mtllib_name_len > 0) { + char *filename = my_strndup(commands[mtllib_line_index].mtllib_name, + commands[mtllib_line_index].mtllib_name_len); + + int ret = tinyobj_parse_and_index_mtl_file(&materials, &num_materials, filename, file_reader, &material_table); + + if (ret != TINYOBJ_SUCCESS) { + /* warning. */ + fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", filename, ret); + } + + TINYOBJ_FREE(filename); + + } + + /* Construct attributes */ + + { + size_t v_count = 0; + size_t n_count = 0; + size_t t_count = 0; + size_t f_count = 0; + size_t face_count = 0; + int material_id = -1; /* -1 = default unknown material. */ + size_t i = 0; + + attrib->vertices = (float *)TINYOBJ_MALLOC(sizeof(float) * num_v * 3); + attrib->num_vertices = (unsigned int)num_v; + attrib->normals = (float *)TINYOBJ_MALLOC(sizeof(float) * num_vn * 3); + attrib->num_normals = (unsigned int)num_vn; + attrib->texcoords = (float *)TINYOBJ_MALLOC(sizeof(float) * num_vt * 2); + attrib->num_texcoords = (unsigned int)num_vt; + attrib->faces = (tinyobj_vertex_index_t *)TINYOBJ_MALLOC( + sizeof(tinyobj_vertex_index_t) * num_f); + attrib->num_faces = (unsigned int)num_f; + attrib->face_num_verts = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces); + attrib->material_ids = (int *)TINYOBJ_MALLOC(sizeof(int) * num_faces); + attrib->num_face_num_verts = (unsigned int)num_faces; + + for (i = 0; i < num_lines; i++) { + if (commands[i].type == COMMAND_EMPTY) { + continue; + } else if (commands[i].type == COMMAND_USEMTL) { + /* @todo + if (commands[t][i].material_name && + commands[t][i].material_name_len > 0) { + std::string material_name(commands[t][i].material_name, + commands[t][i].material_name_len); + + if (material_map.find(material_name) != material_map.end()) { + material_id = material_map[material_name]; + } else { + // Assign invalid material ID + material_id = -1; + } + } + */ + if (commands[i].material_name && + commands[i].material_name_len >0) + { + /* Create a null terminated string */ + char* material_name_null_term = (char*) TINYOBJ_MALLOC(commands[i].material_name_len + 1); + memcpy((void*) material_name_null_term, (const void*) commands[i].material_name, commands[i].material_name_len); + material_name_null_term[commands[i].material_name_len] = 0; + + if (hash_table_exists(material_name_null_term, &material_table)) + material_id = (int)hash_table_get(material_name_null_term, &material_table); + else + material_id = -1; + + TINYOBJ_FREE(material_name_null_term); + } + } else if (commands[i].type == COMMAND_V) { + attrib->vertices[3 * v_count + 0] = commands[i].vx; + attrib->vertices[3 * v_count + 1] = commands[i].vy; + attrib->vertices[3 * v_count + 2] = commands[i].vz; + v_count++; + } else if (commands[i].type == COMMAND_VN) { + attrib->normals[3 * n_count + 0] = commands[i].nx; + attrib->normals[3 * n_count + 1] = commands[i].ny; + attrib->normals[3 * n_count + 2] = commands[i].nz; + n_count++; + } else if (commands[i].type == COMMAND_VT) { + attrib->texcoords[2 * t_count + 0] = commands[i].tx; + attrib->texcoords[2 * t_count + 1] = commands[i].ty; + t_count++; + } else if (commands[i].type == COMMAND_F) { + size_t k = 0; + for (k = 0; k < commands[i].num_f; k++) { + tinyobj_vertex_index_t vi = commands[i].f[k]; + int v_idx = fixIndex(vi.v_idx, v_count); + int vn_idx = fixIndex(vi.vn_idx, n_count); + int vt_idx = fixIndex(vi.vt_idx, t_count); + attrib->faces[f_count + k].v_idx = v_idx; + attrib->faces[f_count + k].vn_idx = vn_idx; + attrib->faces[f_count + k].vt_idx = vt_idx; + } + + for (k = 0; k < commands[i].num_f_num_verts; k++) { + attrib->material_ids[face_count + k] = material_id; + attrib->face_num_verts[face_count + k] = commands[i].f_num_verts[k]; + } + + f_count += commands[i].num_f; + face_count += commands[i].num_f_num_verts; + } + } + } + + /* 5. Construct shape information. */ + { + unsigned int face_count = 0; + size_t i = 0; + size_t n = 0; + size_t shape_idx = 0; + + const char *shape_name = NULL; + unsigned int shape_name_len = 0; + const char *prev_shape_name = NULL; + unsigned int prev_shape_name_len = 0; + unsigned int prev_shape_face_offset = 0; + unsigned int prev_face_offset = 0; + tinyobj_shape_t prev_shape = {NULL, 0, 0}; + + /* Find the number of shapes in .obj */ + for (i = 0; i < num_lines; i++) { + if (commands[i].type == COMMAND_O || commands[i].type == COMMAND_G) { + n++; + } + } + + /* Allocate array of shapes with maximum possible size(+1 for unnamed + * group/object). + * Actual # of shapes found in .obj is determined in the later */ + (*shapes) = (tinyobj_shape_t*)TINYOBJ_MALLOC(sizeof(tinyobj_shape_t) * (n + 1)); + + for (i = 0; i < num_lines; i++) { + if (commands[i].type == COMMAND_O || commands[i].type == COMMAND_G) { + if (commands[i].type == COMMAND_O) { + shape_name = commands[i].object_name; + shape_name_len = commands[i].object_name_len; + } else { + shape_name = commands[i].group_name; + shape_name_len = commands[i].group_name_len; + } + + if (face_count == 0) { + /* 'o' or 'g' appears before any 'f' */ + prev_shape_name = shape_name; + prev_shape_name_len = shape_name_len; + prev_shape_face_offset = face_count; + prev_face_offset = face_count; + } else { + if (shape_idx == 0) { + /* 'o' or 'g' after some 'v' lines. */ + (*shapes)[shape_idx].name = my_strndup( + prev_shape_name, prev_shape_name_len); /* may be NULL */ + (*shapes)[shape_idx].face_offset = prev_shape.face_offset; + (*shapes)[shape_idx].length = face_count - prev_face_offset; + shape_idx++; + + prev_face_offset = face_count; + + } else { + if ((face_count - prev_face_offset) > 0) { + (*shapes)[shape_idx].name = + my_strndup(prev_shape_name, prev_shape_name_len); + (*shapes)[shape_idx].face_offset = prev_face_offset; + (*shapes)[shape_idx].length = face_count - prev_face_offset; + shape_idx++; + prev_face_offset = face_count; + } + } + + /* Record shape info for succeeding 'o' or 'g' command. */ + prev_shape_name = shape_name; + prev_shape_name_len = shape_name_len; + prev_shape_face_offset = face_count; + } + } + if (commands[i].type == COMMAND_F) { + face_count++; + } + } + + if ((face_count - prev_face_offset) > 0) { + size_t length = face_count - prev_shape_face_offset; + if (length > 0) { + (*shapes)[shape_idx].name = + my_strndup(prev_shape_name, prev_shape_name_len); + (*shapes)[shape_idx].face_offset = prev_face_offset; + (*shapes)[shape_idx].length = face_count - prev_face_offset; + shape_idx++; + } + } else { + /* Guess no 'v' line occurrence after 'o' or 'g', so discards current + * shape information. */ + } + + (*num_shapes) = shape_idx; + } + + if (commands) { + TINYOBJ_FREE(commands); + } + + destroy_hash_table(&material_table); + + (*materials_out) = materials; + (*num_materials_out) = num_materials; + + return TINYOBJ_SUCCESS; +} + +TINYOBJDEF void tinyobj_attrib_init(tinyobj_attrib_t *attrib) { + attrib->vertices = NULL; + attrib->num_vertices = 0; + attrib->normals = NULL; + attrib->num_normals = 0; + attrib->texcoords = NULL; + attrib->num_texcoords = 0; + attrib->faces = NULL; + attrib->num_faces = 0; + attrib->face_num_verts = NULL; + attrib->num_face_num_verts = 0; + attrib->material_ids = NULL; +} + +TINYOBJDEF void tinyobj_attrib_free(tinyobj_attrib_t *attrib) { + if (attrib->vertices) TINYOBJ_FREE(attrib->vertices); + if (attrib->normals) TINYOBJ_FREE(attrib->normals); + if (attrib->texcoords) TINYOBJ_FREE(attrib->texcoords); + if (attrib->faces) TINYOBJ_FREE(attrib->faces); + if (attrib->face_num_verts) TINYOBJ_FREE(attrib->face_num_verts); + if (attrib->material_ids) TINYOBJ_FREE(attrib->material_ids); +} + +TINYOBJDEF void tinyobj_shapes_free(tinyobj_shape_t *shapes, size_t num_shapes) { + size_t i; + if (shapes == NULL) return; + + for (i = 0; i < num_shapes; i++) { + if (shapes[i].name) TINYOBJ_FREE(shapes[i].name); + } + + TINYOBJ_FREE(shapes); +} + +TINYOBJDEF void tinyobj_materials_free(tinyobj_material_t *materials, + size_t num_materials) { + size_t i; + if (materials == NULL) return; + + for (i = 0; i < num_materials; i++) { + if (materials[i].name) TINYOBJ_FREE(materials[i].name); + if (materials[i].ambient_texname) TINYOBJ_FREE(materials[i].ambient_texname); + if (materials[i].diffuse_texname) TINYOBJ_FREE(materials[i].diffuse_texname); + if (materials[i].specular_texname) TINYOBJ_FREE(materials[i].specular_texname); + if (materials[i].specular_highlight_texname) + TINYOBJ_FREE(materials[i].specular_highlight_texname); + if (materials[i].bump_texname) TINYOBJ_FREE(materials[i].bump_texname); + if (materials[i].displacement_texname) + TINYOBJ_FREE(materials[i].displacement_texname); + if (materials[i].alpha_texname) TINYOBJ_FREE(materials[i].alpha_texname); + } + + TINYOBJ_FREE(materials); +} +#endif /* TINYOBJ_LOADER_C_IMPLEMENTATION */ + +#endif /* TINOBJ_LOADER_C_H_ */ +/*** End of inlined file: tinyobjloader.h ***/ + + +RF_INTERNAL void rf_tinyobj_file_reader_callback(const char* filename, char** buf, size_t* len) +{ + if (!filename || !buf || !len) return; + + *len = RF_FILE_SIZE(rf__tinyobj_io, filename); + + if (*len) + { + if (!RF_READ_FILE(rf__tinyobj_io, filename, *buf, *len)) + { + // On error we set the size of output buffer to 0 + *len = 0; + } + } +} +#pragma endregion + +#pragma region cgltf +#define CGLTF_IMPLEMENTATION +#define CGLTF_MALLOC(size) RF_ALLOC(rf__global_allocator_for_dependencies, size) +#define CGLTF_FREE(ptr) RF_FREE(rf__global_allocator_for_dependencies, ptr) + +/*** Start of inlined file: cgltf.h ***/ +#ifndef CGLTF_H_INCLUDED__ +#define CGLTF_H_INCLUDED__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef size_t cgltf_size; +typedef float cgltf_float; +typedef int cgltf_int; +typedef unsigned int cgltf_uint; +typedef int cgltf_bool; + +typedef enum cgltf_file_type +{ + cgltf_file_type_invalid, + cgltf_file_type_gltf, + cgltf_file_type_glb, +} cgltf_file_type; + +typedef enum cgltf_result +{ + cgltf_result_success, + cgltf_result_data_too_short, + cgltf_result_unknown_format, + cgltf_result_invalid_json, + cgltf_result_invalid_gltf, + cgltf_result_invalid_options, + cgltf_result_file_not_found, + cgltf_result_io_error, + cgltf_result_out_of_memory, + cgltf_result_legacy_gltf, +} cgltf_result; + +typedef struct cgltf_memory_options +{ + void* (*alloc)(void* user, cgltf_size size); + void (*free) (void* user, void* ptr); + void* user_data; +} cgltf_memory_options; + +typedef struct cgltf_file_options +{ + cgltf_result(*read)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data); + void (*release)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data); + void* user_data; +} cgltf_file_options; + +typedef struct cgltf_options +{ + cgltf_file_type type; /* invalid == auto detect */ + cgltf_size json_token_count; /* 0 == auto */ + cgltf_memory_options memory; + cgltf_file_options file; +} cgltf_options; + +typedef enum cgltf_buffer_view_type +{ + cgltf_buffer_view_type_invalid, + cgltf_buffer_view_type_indices, + cgltf_buffer_view_type_vertices, +} cgltf_buffer_view_type; + +typedef enum cgltf_attribute_type +{ + cgltf_attribute_type_invalid, + cgltf_attribute_type_position, + cgltf_attribute_type_normal, + cgltf_attribute_type_tangent, + cgltf_attribute_type_texcoord, + cgltf_attribute_type_color, + cgltf_attribute_type_joints, + cgltf_attribute_type_weights, +} cgltf_attribute_type; + +typedef enum cgltf_component_type +{ + cgltf_component_type_invalid, + cgltf_component_type_r_8, /* BYTE */ + cgltf_component_type_r_8u, /* UNSIGNED_BYTE */ + cgltf_component_type_r_16, /* SHORT */ + cgltf_component_type_r_16u, /* UNSIGNED_SHORT */ + cgltf_component_type_r_32u, /* UNSIGNED_INT */ + cgltf_component_type_r_32f, /* FLOAT */ +} cgltf_component_type; + +typedef enum cgltf_type +{ + cgltf_type_invalid, + cgltf_type_scalar, + cgltf_type_vec2, + cgltf_type_vec3, + cgltf_type_vec4, + cgltf_type_mat2, + cgltf_type_mat3, + cgltf_type_mat4, +} cgltf_type; + +typedef enum cgltf_primitive_type +{ + cgltf_primitive_type_points, + cgltf_primitive_type_lines, + cgltf_primitive_type_line_loop, + cgltf_primitive_type_line_strip, + cgltf_primitive_type_triangles, + cgltf_primitive_type_triangle_strip, + cgltf_primitive_type_triangle_fan, +} cgltf_primitive_type; + +typedef enum cgltf_alpha_mode +{ + cgltf_alpha_mode_opaque, + cgltf_alpha_mode_mask, + cgltf_alpha_mode_blend, +} cgltf_alpha_mode; + +typedef enum cgltf_animation_path_type { + cgltf_animation_path_type_invalid, + cgltf_animation_path_type_translation, + cgltf_animation_path_type_rotation, + cgltf_animation_path_type_scale, + cgltf_animation_path_type_weights, +} cgltf_animation_path_type; + +typedef enum cgltf_interpolation_type { + cgltf_interpolation_type_linear, + cgltf_interpolation_type_step, + cgltf_interpolation_type_cubic_spline, +} cgltf_interpolation_type; + +typedef enum cgltf_camera_type { + cgltf_camera_type_invalid, + cgltf_camera_type_perspective, + cgltf_camera_type_orthographic, +} cgltf_camera_type; + +typedef enum cgltf_light_type { + cgltf_light_type_invalid, + cgltf_light_type_directional, + cgltf_light_type_point, + cgltf_light_type_spot, +} cgltf_light_type; + +typedef struct cgltf_extras { + cgltf_size start_offset; + cgltf_size end_offset; +} cgltf_extras; + +typedef struct cgltf_buffer +{ + cgltf_size size; + char* uri; + void* data; /* loaded by cgltf_load_buffers */ + cgltf_extras extras; +} cgltf_buffer; + +typedef struct cgltf_buffer_view +{ + cgltf_buffer* buffer; + cgltf_size offset; + cgltf_size size; + cgltf_size stride; /* 0 == automatically determined by accessor */ + cgltf_buffer_view_type type; + cgltf_extras extras; +} cgltf_buffer_view; + +typedef struct cgltf_accessor_sparse +{ + cgltf_size count; + cgltf_buffer_view* indices_buffer_view; + cgltf_size indices_byte_offset; + cgltf_component_type indices_component_type; + cgltf_buffer_view* values_buffer_view; + cgltf_size values_byte_offset; + cgltf_extras extras; + cgltf_extras indices_extras; + cgltf_extras values_extras; +} cgltf_accessor_sparse; + +typedef struct cgltf_accessor +{ + cgltf_component_type component_type; + cgltf_bool normalized; + cgltf_type type; + cgltf_size offset; + cgltf_size count; + cgltf_size stride; + cgltf_buffer_view* buffer_view; + cgltf_bool has_min; + cgltf_float min[16]; + cgltf_bool has_max; + cgltf_float max[16]; + cgltf_bool is_sparse; + cgltf_accessor_sparse sparse; + cgltf_extras extras; +} cgltf_accessor; + +typedef struct cgltf_attribute +{ + char* name; + cgltf_attribute_type type; + cgltf_int index; + cgltf_accessor* data; +} cgltf_attribute; + +typedef struct cgltf_image +{ + char* name; + char* uri; + cgltf_buffer_view* buffer_view; + char* mime_type; + cgltf_extras extras; +} cgltf_image; + +typedef struct cgltf_sampler +{ + cgltf_int mag_filter; + cgltf_int min_filter; + cgltf_int wrap_s; + cgltf_int wrap_t; + cgltf_extras extras; +} cgltf_sampler; + +typedef struct cgltf_texture +{ + char* name; + cgltf_image* image; + cgltf_sampler* sampler; + cgltf_extras extras; +} cgltf_texture; + +typedef struct cgltf_texture_transform +{ + cgltf_float offset[2]; + cgltf_float rotation; + cgltf_float scale[2]; + cgltf_int texcoord; +} cgltf_texture_transform; + +typedef struct cgltf_texture_view +{ + cgltf_texture* texture; + cgltf_int texcoord; + cgltf_float scale; /* equivalent to strength for occlusion_texture */ + cgltf_bool has_transform; + cgltf_texture_transform transform; + cgltf_extras extras; +} cgltf_texture_view; + +typedef struct cgltf_pbr_metallic_roughness +{ + cgltf_texture_view base_color_texture; + cgltf_texture_view metallic_roughness_texture; + + cgltf_float base_color_factor[4]; + cgltf_float metallic_factor; + cgltf_float roughness_factor; + + cgltf_extras extras; +} cgltf_pbr_metallic_roughness; + +typedef struct cgltf_pbr_specular_glossiness +{ + cgltf_texture_view diffuse_texture; + cgltf_texture_view specular_glossiness_texture; + + cgltf_float diffuse_factor[4]; + cgltf_float specular_factor[3]; + cgltf_float glossiness_factor; +} cgltf_pbr_specular_glossiness; + +typedef struct cgltf_clearcoat +{ + cgltf_texture_view clearcoat_texture; + cgltf_texture_view clearcoat_roughness_texture; + cgltf_texture_view clearcoat_normal_texture; + + cgltf_float clearcoat_factor; + cgltf_float clearcoat_roughness_factor; +} cgltf_clearcoat; + +typedef struct cgltf_material +{ + char* name; + cgltf_bool has_pbr_metallic_roughness; + cgltf_bool has_pbr_specular_glossiness; + cgltf_bool has_clearcoat; + cgltf_pbr_metallic_roughness pbr_metallic_roughness; + cgltf_pbr_specular_glossiness pbr_specular_glossiness; + cgltf_clearcoat clearcoat; + cgltf_texture_view normal_texture; + cgltf_texture_view occlusion_texture; + cgltf_texture_view emissive_texture; + cgltf_float emissive_factor[3]; + cgltf_alpha_mode alpha_mode; + cgltf_float alpha_cutoff; + cgltf_bool double_sided; + cgltf_bool unlit; + cgltf_extras extras; +} cgltf_material; + +typedef struct cgltf_morph_target { + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_morph_target; + +typedef struct cgltf_draco_mesh_compression { + cgltf_buffer_view* buffer_view; + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_draco_mesh_compression; + +typedef struct cgltf_primitive { + cgltf_primitive_type type; + cgltf_accessor* indices; + cgltf_material* material; + cgltf_attribute* attributes; + cgltf_size attributes_count; + cgltf_morph_target* targets; + cgltf_size targets_count; + cgltf_extras extras; + cgltf_bool has_draco_mesh_compression; + cgltf_draco_mesh_compression draco_mesh_compression; +} cgltf_primitive; + +typedef struct cgltf_mesh { + char* name; + cgltf_primitive* primitives; + cgltf_size primitives_count; + cgltf_float* weights; + cgltf_size weights_count; + char** target_names; + cgltf_size target_names_count; + cgltf_extras extras; +} cgltf_mesh; + +typedef struct cgltf_node cgltf_node; + +typedef struct cgltf_skin { + char* name; + cgltf_node** joints; + cgltf_size joints_count; + cgltf_node* skeleton; + cgltf_accessor* inverse_bind_matrices; + cgltf_extras extras; +} cgltf_skin; + +typedef struct cgltf_camera_perspective { + cgltf_float aspect_ratio; + cgltf_float yfov; + cgltf_float zfar; + cgltf_float znear; + cgltf_extras extras; +} cgltf_camera_perspective; + +typedef struct cgltf_camera_orthographic { + cgltf_float xmag; + cgltf_float ymag; + cgltf_float zfar; + cgltf_float znear; + cgltf_extras extras; +} cgltf_camera_orthographic; + +typedef struct cgltf_camera { + char* name; + cgltf_camera_type type; + union { + cgltf_camera_perspective perspective; + cgltf_camera_orthographic orthographic; + } data; + cgltf_extras extras; +} cgltf_camera; + +typedef struct cgltf_light { + char* name; + cgltf_float color[3]; + cgltf_float intensity; + cgltf_light_type type; + cgltf_float range; + cgltf_float spot_inner_cone_angle; + cgltf_float spot_outer_cone_angle; +} cgltf_light; + +struct cgltf_node { + char* name; + cgltf_node* parent; + cgltf_node** children; + cgltf_size children_count; + cgltf_skin* skin; + cgltf_mesh* mesh; + cgltf_camera* camera; + cgltf_light* light; + cgltf_float* weights; + cgltf_size weights_count; + cgltf_bool has_translation; + cgltf_bool has_rotation; + cgltf_bool has_scale; + cgltf_bool has_matrix; + cgltf_float translation[3]; + cgltf_float rotation[4]; + cgltf_float scale[3]; + cgltf_float matrix[16]; + cgltf_extras extras; +}; + +typedef struct cgltf_scene { + char* name; + cgltf_node** nodes; + cgltf_size nodes_count; + cgltf_extras extras; +} cgltf_scene; + +typedef struct cgltf_animation_sampler { + cgltf_accessor* input; + cgltf_accessor* output; + cgltf_interpolation_type interpolation; + cgltf_extras extras; +} cgltf_animation_sampler; + +typedef struct cgltf_animation_channel { + cgltf_animation_sampler* sampler; + cgltf_node* target_node; + cgltf_animation_path_type target_path; + cgltf_extras extras; +} cgltf_animation_channel; + +typedef struct cgltf_animation { + char* name; + cgltf_animation_sampler* samplers; + cgltf_size samplers_count; + cgltf_animation_channel* channels; + cgltf_size channels_count; + cgltf_extras extras; +} cgltf_animation; + +typedef struct cgltf_asset { + char* copyright; + char* generator; + char* version; + char* min_version; + cgltf_extras extras; +} cgltf_asset; + +typedef struct cgltf_data +{ + cgltf_file_type file_type; + void* file_data; + + cgltf_asset asset; + + cgltf_mesh* meshes; + cgltf_size meshes_count; + + cgltf_material* materials; + cgltf_size materials_count; + + cgltf_accessor* accessors; + cgltf_size accessors_count; + + cgltf_buffer_view* buffer_views; + cgltf_size buffer_views_count; + + cgltf_buffer* buffers; + cgltf_size buffers_count; + + cgltf_image* images; + cgltf_size images_count; + + cgltf_texture* textures; + cgltf_size textures_count; + + cgltf_sampler* samplers; + cgltf_size samplers_count; + + cgltf_skin* skins; + cgltf_size skins_count; + + cgltf_camera* cameras; + cgltf_size cameras_count; + + cgltf_light* lights; + cgltf_size lights_count; + + cgltf_node* nodes; + cgltf_size nodes_count; + + cgltf_scene* scenes; + cgltf_size scenes_count; + + cgltf_scene* scene; + + cgltf_animation* animations; + cgltf_size animations_count; + + cgltf_extras extras; + + char** extensions_used; + cgltf_size extensions_used_count; + + char** extensions_required; + cgltf_size extensions_required_count; + + const char* json; + cgltf_size json_size; + + const void* bin; + cgltf_size bin_size; + + cgltf_memory_options memory; + cgltf_file_options file; +} cgltf_data; + +cgltf_result cgltf_parse( + const cgltf_options* options, + const void* data, + cgltf_size size, + cgltf_data** out_data); + +cgltf_result cgltf_parse_file( + const cgltf_options* options, + const char* path, + cgltf_data** out_data); + +cgltf_result cgltf_load_buffers( + const cgltf_options* options, + cgltf_data* data, + const char* gltf_path); + +cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data); + +cgltf_result cgltf_validate(cgltf_data* data); + +void cgltf_free(cgltf_data* data); + +void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix); +void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix); + +cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size); +cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size); +cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index); + +cgltf_size cgltf_num_components(cgltf_type type); + +cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count); + +cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size); + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef CGLTF_H_INCLUDED__ */ + +/* + * + * Stop now, if you are only interested in the API. + * Below, you find the implementation. + * + */ + +#if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__) +/* This makes MSVC/CLion intellisense work. */ +#define CGLTF_IMPLEMENTATION +#endif + +#ifdef CGLTF_IMPLEMENTATION + +#include /* For uint8_t, uint32_t */ +#include /* For strncpy */ +#include /* For fopen */ +#include /* For UINT_MAX etc */ + +#if !defined(CGLTF_MALLOC) || !defined(CGLTF_FREE) || !defined(CGLTF_ATOI) || !defined(CGLTF_ATOF) +#include /* For malloc, free, atoi, atof */ +#endif + +/* JSMN_PARENT_LINKS is necessary to make parsing large structures linear in input size */ +#define JSMN_PARENT_LINKS + +/* JSMN_STRICT is necessary to reject invalid JSON documents */ +#define JSMN_STRICT + +/* + * -- jsmn.h start -- + * Source: https://github.com/zserge/jsmn + * License: MIT + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; +static void jsmn_init(jsmn_parser *parser); +static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens); +/* + * -- jsmn.h end -- + */ + +static const cgltf_size GlbHeaderSize = 12; +static const cgltf_size GlbChunkHeaderSize = 8; +static const uint32_t GlbVersion = 2; +static const uint32_t GlbMagic = 0x46546C67; +static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; +static const uint32_t GlbMagicBinChunk = 0x004E4942; + +#ifndef CGLTF_MALLOC +#define CGLTF_MALLOC(size) malloc(size) +#endif +#ifndef CGLTF_FREE +#define CGLTF_FREE(ptr) free(ptr) +#endif +#ifndef CGLTF_ATOI +#define CGLTF_ATOI(str) atoi(str) +#endif +#ifndef CGLTF_ATOF +#define CGLTF_ATOF(str) atof(str) +#endif + +static void* cgltf_default_alloc(void* user, cgltf_size size) +{ + (void)user; + return CGLTF_MALLOC(size); +} + +static void cgltf_default_free(void* user, void* ptr) +{ + (void)user; + CGLTF_FREE(ptr); +} + +static void* cgltf_calloc(cgltf_options* options, size_t element_size, cgltf_size count) +{ + if (SIZE_MAX / element_size < count) + { + return NULL; + } + void* result = options->memory.alloc(options->memory.user_data, element_size * count); + if (!result) + { + return NULL; + } + memset(result, 0, element_size * count); + return result; +} + +static cgltf_result cgltf_default_file_read(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data) +{ + (void)file_options; + void* (*memory_alloc)(void*, cgltf_size) = memory_options->alloc ? memory_options->alloc : &cgltf_default_alloc; + void (*memory_free)(void*, void*) = memory_options->free ? memory_options->free : &cgltf_default_free; + + FILE* file = fopen(path, "rb"); + if (!file) + { + return cgltf_result_file_not_found; + } + + cgltf_size file_size = size ? *size : 0; + + if (file_size == 0) + { + fseek(file, 0, SEEK_END); + + long length = ftell(file); + if (length < 0) + { + fclose(file); + return cgltf_result_io_error; + } + + fseek(file, 0, SEEK_SET); + file_size = (cgltf_size)length; + } + + char* file_data = (char*)memory_alloc(memory_options->user_data, file_size); + if (!file_data) + { + fclose(file); + return cgltf_result_out_of_memory; + } + + cgltf_size read_size = fread(file_data, 1, file_size, file); + + fclose(file); + + if (read_size != file_size) + { + memory_free(memory_options->user_data, file_data); + return cgltf_result_io_error; + } + + if (size) + { + *size = file_size; + } + if (data) + { + *data = file_data; + } + + return cgltf_result_success; +} + +static void cgltf_default_file_release(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data) +{ + (void)file_options; + void (*memfree)(void*, void*) = memory_options->free ? memory_options->free : &cgltf_default_free; + memfree(memory_options->user_data, data); +} + +static cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data); + +cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data** out_data) +{ + if (size < GlbHeaderSize) + { + return cgltf_result_data_too_short; + } + + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + cgltf_options fixed_options = *options; + if (fixed_options.memory.alloc == NULL) + { + fixed_options.memory.alloc = &cgltf_default_alloc; + } + if (fixed_options.memory.free == NULL) + { + fixed_options.memory.free = &cgltf_default_free; + } + + uint32_t tmp; + // Magic + memcpy(&tmp, data, 4); + if (tmp != GlbMagic) + { + if (fixed_options.type == cgltf_file_type_invalid) + { + fixed_options.type = cgltf_file_type_gltf; + } + else if (fixed_options.type == cgltf_file_type_glb) + { + return cgltf_result_unknown_format; + } + } + + if (fixed_options.type == cgltf_file_type_gltf) + { + cgltf_result json_result = cgltf_parse_json(&fixed_options, (const uint8_t*)data, size, out_data); + if (json_result != cgltf_result_success) + { + return json_result; + } + + (*out_data)->file_type = cgltf_file_type_gltf; + + return cgltf_result_success; + } + + const uint8_t* ptr = (const uint8_t*)data; + // Version + memcpy(&tmp, ptr + 4, 4); + uint32_t version = tmp; + if (version != GlbVersion) + { + return version < GlbVersion ? cgltf_result_legacy_gltf : cgltf_result_unknown_format; + } + + // Total length + memcpy(&tmp, ptr + 8, 4); + if (tmp > size) + { + return cgltf_result_data_too_short; + } + + const uint8_t* json_chunk = ptr + GlbHeaderSize; + + if (GlbHeaderSize + GlbChunkHeaderSize > size) + { + return cgltf_result_data_too_short; + } + + // JSON chunk: length + uint32_t json_length; + memcpy(&json_length, json_chunk, 4); + if (GlbHeaderSize + GlbChunkHeaderSize + json_length > size) + { + return cgltf_result_data_too_short; + } + + // JSON chunk: magic + memcpy(&tmp, json_chunk + 4, 4); + if (tmp != GlbMagicJsonChunk) + { + return cgltf_result_unknown_format; + } + + json_chunk += GlbChunkHeaderSize; + + const void* bin = 0; + cgltf_size bin_size = 0; + + if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize <= size) + { + // We can read another chunk + const uint8_t* bin_chunk = json_chunk + json_length; + + // Bin chunk: length + uint32_t bin_length; + memcpy(&bin_length, bin_chunk, 4); + if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize + bin_length > size) + { + return cgltf_result_data_too_short; + } + + // Bin chunk: magic + memcpy(&tmp, bin_chunk + 4, 4); + if (tmp != GlbMagicBinChunk) + { + return cgltf_result_unknown_format; + } + + bin_chunk += GlbChunkHeaderSize; + + bin = bin_chunk; + bin_size = bin_length; + } + + cgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data); + if (json_result != cgltf_result_success) + { + return json_result; + } + + (*out_data)->file_type = cgltf_file_type_glb; + (*out_data)->bin = bin; + (*out_data)->bin_size = bin_size; + + return cgltf_result_success; +} + +cgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cgltf_data** out_data) +{ + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + void (*memory_free)(void*, void*) = options->memory.free ? options->memory.free : &cgltf_default_free; + cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; + + void* file_data = NULL; + cgltf_size file_size = 0; + cgltf_result result = file_read(&options->memory, &options->file, path, &file_size, &file_data); + if (result != cgltf_result_success) + { + return result; + } + + result = cgltf_parse(options, file_data, file_size, out_data); + + if (result != cgltf_result_success) + { + memory_free(options->memory.user_data, file_data); + return result; + } + + (*out_data)->file_data = file_data; + + return cgltf_result_success; +} + +static void cgltf_combine_paths(char* path, const char* base, const char* uri) +{ + const char* s0 = strrchr(base, '/'); + const char* s1 = strrchr(base, '\\'); + const char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1; + + if (slash) + { + size_t prefix = slash - base + 1; + + strncpy(path, base, prefix); + strcpy(path + prefix, uri); + } + else + { + strcpy(path, uri); + } +} + +static cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_size size, const char* uri, const char* gltf_path, void** out_data) +{ + void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc ? options->memory.alloc : &cgltf_default_alloc; + cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; + + char* path = (char*)memory_alloc(options->memory.user_data, strlen(uri) + strlen(gltf_path) + 1); + if (!path) + { + return cgltf_result_out_of_memory; + } + + cgltf_combine_paths(path, gltf_path, uri); + + void* file_data = NULL; + cgltf_result result = file_read(&options->memory, &options->file, path, &size, &file_data); + if (result != cgltf_result_success) + { + return result; + } + + *out_data = file_data; + + return cgltf_result_success; +} + +cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data) +{ + void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc ? options->memory.alloc : &cgltf_default_alloc; + void (*memory_free)(void*, void*) = options->memory.free ? options->memory.free : &cgltf_default_free; + + unsigned char* data = (unsigned char*)memory_alloc(options->memory.user_data, size); + if (!data) + { + return cgltf_result_out_of_memory; + } + + unsigned int buffer = 0; + unsigned int buffer_bits = 0; + + for (cgltf_size i = 0; i < size; ++i) + { + while (buffer_bits < 8) + { + char ch = *base64++; + + int index = + (unsigned)(ch - 'A') < 26 ? (ch - 'A') : + (unsigned)(ch - 'a') < 26 ? (ch - 'a') + 26 : + (unsigned)(ch - '0') < 10 ? (ch - '0') + 52 : + ch == '+' ? 62 : + ch == '/' ? 63 : + -1; + + if (index < 0) + { + memory_free(options->memory.user_data, data); + return cgltf_result_io_error; + } + + buffer = (buffer << 6) | index; + buffer_bits += 6; + } + + data[i] = (unsigned char)(buffer >> (buffer_bits - 8)); + buffer_bits -= 8; + } + + *out_data = data; + + return cgltf_result_success; +} + +cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path) +{ + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + if (data->buffers_count && data->buffers[0].data == NULL && data->buffers[0].uri == NULL && data->bin) + { + if (data->bin_size < data->buffers[0].size) + { + return cgltf_result_data_too_short; + } + + data->buffers[0].data = (void*)data->bin; + } + + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + if (data->buffers[i].data) + { + continue; + } + + const char* uri = data->buffers[i].uri; + + if (uri == NULL) + { + continue; + } + + if (strncmp(uri, "data:", 5) == 0) + { + const char* comma = strchr(uri, ','); + + if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) + { + cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data); + + if (res != cgltf_result_success) + { + return res; + } + } + else + { + return cgltf_result_unknown_format; + } + } + else if (strstr(uri, "://") == NULL && gltf_path) + { + cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data); + + if (res != cgltf_result_success) + { + return res; + } + } + else + { + return cgltf_result_unknown_format; + } + } + + return cgltf_result_success; +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type); + +static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_size offset, cgltf_component_type component_type, cgltf_size count) +{ + char* data = (char*)buffer_view->buffer->data + offset + buffer_view->offset; + cgltf_size bound = 0; + + switch (component_type) + { + case cgltf_component_type_r_8u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned char*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + case cgltf_component_type_r_16u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned short*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + case cgltf_component_type_r_32u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned int*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + default: + ; + } + + return bound; +} + +cgltf_result cgltf_validate(cgltf_data* data) +{ + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + cgltf_accessor* accessor = &data->accessors[i]; + + cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); + + if (accessor->buffer_view) + { + cgltf_size req_size = accessor->offset + accessor->stride * (accessor->count - 1) + element_size; + + if (accessor->buffer_view->size < req_size) + { + return cgltf_result_data_too_short; + } + } + + if (accessor->is_sparse) + { + cgltf_accessor_sparse* sparse = &accessor->sparse; + + cgltf_size indices_component_size = cgltf_calc_size(cgltf_type_scalar, sparse->indices_component_type); + cgltf_size indices_req_size = sparse->indices_byte_offset + indices_component_size * sparse->count; + cgltf_size values_req_size = sparse->values_byte_offset + element_size * sparse->count; + + if (sparse->indices_buffer_view->size < indices_req_size || + sparse->values_buffer_view->size < values_req_size) + { + return cgltf_result_data_too_short; + } + + if (sparse->indices_component_type != cgltf_component_type_r_8u && + sparse->indices_component_type != cgltf_component_type_r_16u && + sparse->indices_component_type != cgltf_component_type_r_32u) + { + return cgltf_result_invalid_gltf; + } + + if (sparse->indices_buffer_view->buffer->data) + { + cgltf_size index_bound = cgltf_calc_index_bound(sparse->indices_buffer_view, sparse->indices_byte_offset, sparse->indices_component_type, sparse->count); + + if (index_bound >= accessor->count) + { + return cgltf_result_data_too_short; + } + } + } + } + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + cgltf_size req_size = data->buffer_views[i].offset + data->buffer_views[i].size; + + if (data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size) + { + return cgltf_result_data_too_short; + } + } + + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + if (data->meshes[i].weights) + { + if (data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count) + { + return cgltf_result_invalid_gltf; + } + } + + if (data->meshes[i].target_names) + { + if (data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count) + { + return cgltf_result_invalid_gltf; + } + } + + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + if (data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count) + { + return cgltf_result_invalid_gltf; + } + + if (data->meshes[i].primitives[j].attributes_count) + { + cgltf_accessor* first = data->meshes[i].primitives[j].attributes[0].data; + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + if (data->meshes[i].primitives[j].attributes[k].data->count != first->count) + { + return cgltf_result_invalid_gltf; + } + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + if (data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count) + { + return cgltf_result_invalid_gltf; + } + } + } + + cgltf_accessor* indices = data->meshes[i].primitives[j].indices; + + if (indices && + indices->component_type != cgltf_component_type_r_8u && + indices->component_type != cgltf_component_type_r_16u && + indices->component_type != cgltf_component_type_r_32u) + { + return cgltf_result_invalid_gltf; + } + + if (indices && indices->buffer_view && indices->buffer_view->buffer->data) + { + cgltf_size index_bound = cgltf_calc_index_bound(indices->buffer_view, indices->offset, indices->component_type, indices->count); + + if (index_bound >= first->count) + { + return cgltf_result_data_too_short; + } + } + } + } + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + if (data->nodes[i].weights && data->nodes[i].mesh) + { + if (data->nodes[i].mesh->primitives_count && data->nodes[i].mesh->primitives[0].targets_count != data->nodes[i].weights_count) + { + return cgltf_result_invalid_gltf; + } + } + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + cgltf_node* p1 = data->nodes[i].parent; + cgltf_node* p2 = p1 ? p1->parent : NULL; + + while (p1 && p2) + { + if (p1 == p2) + { + return cgltf_result_invalid_gltf; + } + + p1 = p1->parent; + p2 = p2->parent ? p2->parent->parent : NULL; + } + } + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) + { + if (data->scenes[i].nodes[j]->parent) + { + return cgltf_result_invalid_gltf; + } + } + } + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) + { + cgltf_animation_channel* channel = &data->animations[i].channels[j]; + + if (!channel->target_node) + { + continue; + } + + cgltf_size components = 1; + + if (channel->target_path == cgltf_animation_path_type_weights) + { + if (!channel->target_node->mesh || !channel->target_node->mesh->primitives_count) + { + return cgltf_result_invalid_gltf; + } + + components = channel->target_node->mesh->primitives[0].targets_count; + } + + cgltf_size values = channel->sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1; + + if (channel->sampler->input->count * components * values != channel->sampler->output->count) + { + return cgltf_result_data_too_short; + } + } + } + + return cgltf_result_success; +} + +cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size) +{ + cgltf_size json_size = extras->end_offset - extras->start_offset; + + if (!dest) + { + if (dest_size) + { + *dest_size = json_size + 1; + return cgltf_result_success; + } + return cgltf_result_invalid_options; + } + + if (*dest_size + 1 < json_size) + { + strncpy(dest, data->json + extras->start_offset, *dest_size - 1); + dest[*dest_size - 1] = 0; + } + else + { + strncpy(dest, data->json + extras->start_offset, json_size); + dest[json_size] = 0; + } + + return cgltf_result_success; +} + +void cgltf_free(cgltf_data* data) +{ + if (!data) + { + return; + } + + void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data) = data->file.release ? data->file.release : cgltf_default_file_release; + + data->memory.free(data->memory.user_data, data->asset.copyright); + data->memory.free(data->memory.user_data, data->asset.generator); + data->memory.free(data->memory.user_data, data->asset.version); + data->memory.free(data->memory.user_data, data->asset.min_version); + + data->memory.free(data->memory.user_data, data->accessors); + data->memory.free(data->memory.user_data, data->buffer_views); + + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + if (data->buffers[i].data != data->bin) + { + file_release(&data->memory, &data->file, data->buffers[i].data); + } + + data->memory.free(data->memory.user_data, data->buffers[i].uri); + } + + data->memory.free(data->memory.user_data, data->buffers); + + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + data->memory.free(data->memory.user_data, data->meshes[i].name); + + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].attributes[k].name); + } + + data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].attributes); + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes[m].name); + } + + data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes); + } + + data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].targets); + } + + data->memory.free(data->memory.user_data, data->meshes[i].primitives); + data->memory.free(data->memory.user_data, data->meshes[i].weights); + + for (cgltf_size j = 0; j < data->meshes[i].target_names_count; ++j) + { + data->memory.free(data->memory.user_data, data->meshes[i].target_names[j]); + } + + data->memory.free(data->memory.user_data, data->meshes[i].target_names); + } + + data->memory.free(data->memory.user_data, data->meshes); + + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + data->memory.free(data->memory.user_data, data->materials[i].name); + } + + data->memory.free(data->memory.user_data, data->materials); + + for (cgltf_size i = 0; i < data->images_count; ++i) + { + data->memory.free(data->memory.user_data, data->images[i].name); + data->memory.free(data->memory.user_data, data->images[i].uri); + data->memory.free(data->memory.user_data, data->images[i].mime_type); + } + + data->memory.free(data->memory.user_data, data->images); + + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + data->memory.free(data->memory.user_data, data->textures[i].name); + } + + data->memory.free(data->memory.user_data, data->textures); + + data->memory.free(data->memory.user_data, data->samplers); + + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + data->memory.free(data->memory.user_data, data->skins[i].name); + data->memory.free(data->memory.user_data, data->skins[i].joints); + } + + data->memory.free(data->memory.user_data, data->skins); + + for (cgltf_size i = 0; i < data->cameras_count; ++i) + { + data->memory.free(data->memory.user_data, data->cameras[i].name); + } + + data->memory.free(data->memory.user_data, data->cameras); + + for (cgltf_size i = 0; i < data->lights_count; ++i) + { + data->memory.free(data->memory.user_data, data->lights[i].name); + } + + data->memory.free(data->memory.user_data, data->lights); + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + data->memory.free(data->memory.user_data, data->nodes[i].name); + data->memory.free(data->memory.user_data, data->nodes[i].children); + data->memory.free(data->memory.user_data, data->nodes[i].weights); + } + + data->memory.free(data->memory.user_data, data->nodes); + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + data->memory.free(data->memory.user_data, data->scenes[i].name); + data->memory.free(data->memory.user_data, data->scenes[i].nodes); + } + + data->memory.free(data->memory.user_data, data->scenes); + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + data->memory.free(data->memory.user_data, data->animations[i].name); + data->memory.free(data->memory.user_data, data->animations[i].samplers); + data->memory.free(data->memory.user_data, data->animations[i].channels); + } + + data->memory.free(data->memory.user_data, data->animations); + + for (cgltf_size i = 0; i < data->extensions_used_count; ++i) + { + data->memory.free(data->memory.user_data, data->extensions_used[i]); + } + + data->memory.free(data->memory.user_data, data->extensions_used); + + for (cgltf_size i = 0; i < data->extensions_required_count; ++i) + { + data->memory.free(data->memory.user_data, data->extensions_required[i]); + } + + data->memory.free(data->memory.user_data, data->extensions_required); + + file_release(&data->memory, &data->file, data->file_data); + + data->memory.free(data->memory.user_data, data); +} + +void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix) +{ + cgltf_float* lm = out_matrix; + + if (node->has_matrix) + { + memcpy(lm, node->matrix, sizeof(float) * 16); + } + else + { + float tx = node->translation[0]; + float ty = node->translation[1]; + float tz = node->translation[2]; + + float qx = node->rotation[0]; + float qy = node->rotation[1]; + float qz = node->rotation[2]; + float qw = node->rotation[3]; + + float sx = node->scale[0]; + float sy = node->scale[1]; + float sz = node->scale[2]; + + lm[0] = (1 - 2 * qy*qy - 2 * qz*qz) * sx; + lm[1] = (2 * qx*qy + 2 * qz*qw) * sx; + lm[2] = (2 * qx*qz - 2 * qy*qw) * sx; + lm[3] = 0.f; + + lm[4] = (2 * qx*qy - 2 * qz*qw) * sy; + lm[5] = (1 - 2 * qx*qx - 2 * qz*qz) * sy; + lm[6] = (2 * qy*qz + 2 * qx*qw) * sy; + lm[7] = 0.f; + + lm[8] = (2 * qx*qz + 2 * qy*qw) * sz; + lm[9] = (2 * qy*qz - 2 * qx*qw) * sz; + lm[10] = (1 - 2 * qx*qx - 2 * qy*qy) * sz; + lm[11] = 0.f; + + lm[12] = tx; + lm[13] = ty; + lm[14] = tz; + lm[15] = 1.f; + } +} + +void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix) +{ + cgltf_float* lm = out_matrix; + cgltf_node_transform_local(node, lm); + + const cgltf_node* parent = node->parent; + + while (parent) + { + float pm[16]; + cgltf_node_transform_local(parent, pm); + + for (int i = 0; i < 4; ++i) + { + float l0 = lm[i * 4 + 0]; + float l1 = lm[i * 4 + 1]; + float l2 = lm[i * 4 + 2]; + + float r0 = l0 * pm[0] + l1 * pm[4] + l2 * pm[8]; + float r1 = l0 * pm[1] + l1 * pm[5] + l2 * pm[9]; + float r2 = l0 * pm[2] + l1 * pm[6] + l2 * pm[10]; + + lm[i * 4 + 0] = r0; + lm[i * 4 + 1] = r1; + lm[i * 4 + 2] = r2; + } + + lm[12] += pm[12]; + lm[13] += pm[13]; + lm[14] += pm[14]; + + parent = parent->parent; + } +} + +static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type) +{ + switch (component_type) + { + case cgltf_component_type_r_16: + return *((const int16_t*) in); + case cgltf_component_type_r_16u: + return *((const uint16_t*) in); + case cgltf_component_type_r_32u: + return *((const uint32_t*) in); + case cgltf_component_type_r_32f: + return (cgltf_size)*((const float*) in); + case cgltf_component_type_r_8: + return *((const int8_t*) in); + case cgltf_component_type_r_8u: + return *((const uint8_t*) in); + default: + return 0; + } +} + +static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, cgltf_bool normalized) +{ + if (component_type == cgltf_component_type_r_32f) + { + return *((const float*) in); + } + + if (normalized) + { + switch (component_type) + { + // note: glTF spec doesn't currently define normalized conversions for 32-bit integers + case cgltf_component_type_r_16: + return *((const int16_t*) in) / (cgltf_float)32767; + case cgltf_component_type_r_16u: + return *((const uint16_t*) in) / (cgltf_float)65535; + case cgltf_component_type_r_8: + return *((const int8_t*) in) / (cgltf_float)127; + case cgltf_component_type_r_8u: + return *((const uint8_t*) in) / (cgltf_float)255; + default: + return 0; + } + } + + return (cgltf_float)cgltf_component_read_index(in, component_type); +} + +static cgltf_size cgltf_component_size(cgltf_component_type component_type); + +static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_bool normalized, cgltf_float* out, cgltf_size element_size) +{ + cgltf_size num_components = cgltf_num_components(type); + + if (element_size < num_components) { + return 0; + } + + // There are three special cases for component extraction, see #data-alignment in the 2.0 spec. + + cgltf_size component_size = cgltf_component_size(component_type); + + if (type == cgltf_type_mat2 && component_size == 1) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 1, component_type, normalized); + out[2] = cgltf_component_read_float(element + 4, component_type, normalized); + out[3] = cgltf_component_read_float(element + 5, component_type, normalized); + return 1; + } + + if (type == cgltf_type_mat3 && component_size == 1) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 1, component_type, normalized); + out[2] = cgltf_component_read_float(element + 2, component_type, normalized); + out[3] = cgltf_component_read_float(element + 4, component_type, normalized); + out[4] = cgltf_component_read_float(element + 5, component_type, normalized); + out[5] = cgltf_component_read_float(element + 6, component_type, normalized); + out[6] = cgltf_component_read_float(element + 8, component_type, normalized); + out[7] = cgltf_component_read_float(element + 9, component_type, normalized); + out[8] = cgltf_component_read_float(element + 10, component_type, normalized); + return 1; + } + + if (type == cgltf_type_mat3 && component_size == 2) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 2, component_type, normalized); + out[2] = cgltf_component_read_float(element + 4, component_type, normalized); + out[3] = cgltf_component_read_float(element + 8, component_type, normalized); + out[4] = cgltf_component_read_float(element + 10, component_type, normalized); + out[5] = cgltf_component_read_float(element + 12, component_type, normalized); + out[6] = cgltf_component_read_float(element + 16, component_type, normalized); + out[7] = cgltf_component_read_float(element + 18, component_type, normalized); + out[8] = cgltf_component_read_float(element + 20, component_type, normalized); + return 1; + } + + for (cgltf_size i = 0; i < num_components; ++i) + { + out[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized); + } + return 1; +} + +cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size) +{ + if (accessor->is_sparse) + { + return 0; + } + if (accessor->buffer_view == NULL) + { + memset(out, 0, element_size * sizeof(cgltf_float)); + return 1; + } + if (accessor->buffer_view->buffer->data == NULL) + { + return 0; + } + cgltf_size offset = accessor->offset + accessor->buffer_view->offset; + const uint8_t* element = (const uint8_t*) accessor->buffer_view->buffer->data; + element += offset + accessor->stride * index; + return cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size); +} + +cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count) +{ + cgltf_size floats_per_element = cgltf_num_components(accessor->type); + cgltf_size available_floats = accessor->count * floats_per_element; + if (out == NULL) + { + return available_floats; + } + + float_count = available_floats < float_count ? available_floats : float_count; + cgltf_size element_count = float_count / floats_per_element; + + // First pass: convert each element in the base accessor. + cgltf_float* dest = out; + cgltf_accessor dense = *accessor; + dense.is_sparse = 0; + for (cgltf_size index = 0; index < element_count; index++, dest += floats_per_element) + { + if (!cgltf_accessor_read_float(&dense, index, dest, floats_per_element)) + { + return 0; + } + } + + // Second pass: write out each element in the sparse accessor. + if (accessor->is_sparse) + { + const cgltf_accessor_sparse* sparse = &dense.sparse; + + if (sparse->indices_buffer_view->buffer->data == NULL || sparse->values_buffer_view->buffer->data == NULL) + { + return 0; + } + + const uint8_t* index_data = (const uint8_t*) sparse->indices_buffer_view->buffer->data; + index_data += sparse->indices_byte_offset + sparse->indices_buffer_view->offset; + cgltf_size index_stride = cgltf_component_size(sparse->indices_component_type); + const uint8_t* reader_head = (const uint8_t*) sparse->values_buffer_view->buffer->data; + reader_head += sparse->values_byte_offset + sparse->values_buffer_view->offset; + for (cgltf_size reader_index = 0; reader_index < sparse->count; reader_index++, index_data += index_stride) + { + size_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type); + float* writer_head = out + writer_index * floats_per_element; + + if (!cgltf_element_read_float(reader_head, dense.type, dense.component_type, dense.normalized, writer_head, floats_per_element)) + { + return 0; + } + + reader_head += dense.stride; + } + } + + return element_count * floats_per_element; +} + +static cgltf_uint cgltf_component_read_uint(const void* in, cgltf_component_type component_type) +{ + switch (component_type) + { + case cgltf_component_type_r_8: + return *((const int8_t*) in); + + case cgltf_component_type_r_8u: + return *((const uint8_t*) in); + + case cgltf_component_type_r_16: + return *((const int16_t*) in); + + case cgltf_component_type_r_16u: + return *((const uint16_t*) in); + + case cgltf_component_type_r_32u: + return *((const uint32_t*) in); + + default: + return 0; + } +} + +static cgltf_bool cgltf_element_read_uint(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_uint* out, cgltf_size element_size) +{ + cgltf_size num_components = cgltf_num_components(type); + + if (element_size < num_components) + { + return 0; + } + + // Reading integer matrices is not a valid use case + if (type == cgltf_type_mat2 || type == cgltf_type_mat3 || type == cgltf_type_mat4) + { + return 0; + } + + cgltf_size component_size = cgltf_component_size(component_type); + + for (cgltf_size i = 0; i < num_components; ++i) + { + out[i] = cgltf_component_read_uint(element + component_size * i, component_type); + } + return 1; +} + +cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size) +{ + if (accessor->is_sparse) + { + return 0; + } + if (accessor->buffer_view == NULL) + { + memset(out, 0, element_size * sizeof( cgltf_uint )); + return 1; + } + if (accessor->buffer_view->buffer->data == NULL) + { + return 0; + } + cgltf_size offset = accessor->offset + accessor->buffer_view->offset; + const uint8_t* element = (const uint8_t*) accessor->buffer_view->buffer->data; + element += offset + accessor->stride * index; + return cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size); +} + +cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index) +{ + if (accessor->is_sparse) + { + return 0; // This is an error case, but we can't communicate the error with existing interface. + } + if (accessor->buffer_view == NULL) + { + return 0; + } + if (accessor->buffer_view->buffer->data == NULL) + { + return 0; // This is an error case, but we can't communicate the error with existing interface. + } + + cgltf_size offset = accessor->offset + accessor->buffer_view->offset; + const uint8_t* element = (const uint8_t*) accessor->buffer_view->buffer->data; + element += offset + accessor->stride * index; + return cgltf_component_read_index(element, accessor->component_type); +} + +#define CGLTF_ERROR_JSON -1 +#define CGLTF_ERROR_NOMEM -2 +#define CGLTF_ERROR_LEGACY -3 + +#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; } +#define CGLTF_CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return CGLTF_ERROR_JSON; } /* checking size for 0 verifies that a value follows the key */ + +#define CGLTF_PTRINDEX(type, idx) (type*)((cgltf_size)idx + 1) +#define CGLTF_PTRFIXUP(var, data, size) if (var) { if ((cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; } +#define CGLTF_PTRFIXUP_REQ(var, data, size) if (!var || (cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; + +static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING); + size_t const str_len = strlen(str); + size_t const name_length = tok->end - tok->start; + return (str_len == name_length) ? strncmp((const char*)json_chunk + tok->start, str, str_len) : 128; +} + +static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : (int)(sizeof(tmp) - 1); + strncpy(tmp, (const char*)json_chunk + tok->start, size); + tmp[size] = 0; + return CGLTF_ATOI(tmp); +} + +static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : (int)(sizeof(tmp) - 1); + strncpy(tmp, (const char*)json_chunk + tok->start, size); + tmp[size] = 0; + return (cgltf_float)CGLTF_ATOF(tmp); +} + +static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + int size = tok->end - tok->start; + return size == 4 && memcmp(json_chunk + tok->start, "true", 4) == 0; +} + +static int cgltf_skip_json(jsmntok_t const* tokens, int i) +{ + int end = i + 1; + + while (i < end) + { + switch (tokens[i].type) + { + case JSMN_OBJECT: + end += tokens[i].size * 2; + break; + + case JSMN_ARRAY: + end += tokens[i].size; + break; + + case JSMN_PRIMITIVE: + case JSMN_STRING: + break; + + default: + return -1; + } + + i++; + } + + return i; +} + +static void cgltf_fill_float_array(float* out_array, int size, float value) +{ + for (int j = 0; j < size; ++j) + { + out_array[j] = value; + } +} + +static int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, float* out_array, int size) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + if (tokens[i].size != size) + { + return CGLTF_ERROR_JSON; + } + ++i; + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_array[j] = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + return i; +} + +static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char** out_string) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); + if (*out_string) + { + return CGLTF_ERROR_JSON; + } + int size = tokens[i].end - tokens[i].start; + char* result = (char*)options->memory.alloc(options->memory.user_data, size + 1); + if (!result) + { + return CGLTF_ERROR_NOMEM; + } + strncpy(result, (const char*)json_chunk + tokens[i].start, size); + result[size] = 0; + *out_string = result; + return i + 1; +} + +static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size) +{ + (void)json_chunk; + if (tokens[i].type != JSMN_ARRAY) + { + return tokens[i].type == JSMN_OBJECT ? CGLTF_ERROR_LEGACY : CGLTF_ERROR_JSON; + } + if (*out_array) + { + return CGLTF_ERROR_JSON; + } + int size = tokens[i].size; + void* result = cgltf_calloc(options, element_size, size); + if (!result) + { + return CGLTF_ERROR_NOMEM; + } + *out_array = result; + *out_size = size; + return i + 1; +} + +static int cgltf_parse_json_string_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char*** out_array, cgltf_size* out_size) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < *out_size; ++j) + { + i = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array)); + if (i < 0) + { + return i; + } + } + return i; +} + +static void cgltf_parse_attribute_type(const char* name, cgltf_attribute_type* out_type, int* out_index) +{ + const char* us = strchr(name, '_'); + size_t len = us ? (size_t)(us - name) : strlen(name); + + if (len == 8 && strncmp(name, "POSITION", 8) == 0) + { + *out_type = cgltf_attribute_type_position; + } + else if (len == 6 && strncmp(name, "NORMAL", 6) == 0) + { + *out_type = cgltf_attribute_type_normal; + } + else if (len == 7 && strncmp(name, "TANGENT", 7) == 0) + { + *out_type = cgltf_attribute_type_tangent; + } + else if (len == 8 && strncmp(name, "TEXCOORD", 8) == 0) + { + *out_type = cgltf_attribute_type_texcoord; + } + else if (len == 5 && strncmp(name, "COLOR", 5) == 0) + { + *out_type = cgltf_attribute_type_color; + } + else if (len == 6 && strncmp(name, "JOINTS", 6) == 0) + { + *out_type = cgltf_attribute_type_joints; + } + else if (len == 7 && strncmp(name, "WEIGHTS", 7) == 0) + { + *out_type = cgltf_attribute_type_weights; + } + else + { + *out_type = cgltf_attribute_type_invalid; + } + + if (us && *out_type != cgltf_attribute_type_invalid) + { + *out_index = CGLTF_ATOI(us + 1); + } +} + +static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_attribute** out_attributes, cgltf_size* out_attributes_count) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + if (*out_attributes) + { + return CGLTF_ERROR_JSON; + } + + *out_attributes_count = tokens[i].size; + *out_attributes = (cgltf_attribute*)cgltf_calloc(options, sizeof(cgltf_attribute), *out_attributes_count); + ++i; + + if (!*out_attributes) + { + return CGLTF_ERROR_NOMEM; + } + + for (cgltf_size j = 0; j < *out_attributes_count; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + i = cgltf_parse_json_string(options, tokens, i, json_chunk, &(*out_attributes)[j].name); + if (i < 0) + { + return CGLTF_ERROR_JSON; + } + + cgltf_parse_attribute_type((*out_attributes)[j].name, &(*out_attributes)[j].type, &(*out_attributes)[j].index); + + (*out_attributes)[j].data = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + + return i; +} + +static int cgltf_parse_json_extras(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras) +{ + (void)json_chunk; + out_extras->start_offset = tokens[i].start; + out_extras->end_offset = tokens[i].end; + i = cgltf_skip_json(tokens, i); + return i; +} + +static int cgltf_parse_json_draco_mesh_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_draco_mesh_compression* out_draco_mesh_compression) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0) + { + i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_draco_mesh_compression->attributes, &out_draco_mesh_compression->attributes_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0) + { + ++i; + out_draco_mesh_compression->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + + return i; +} + +static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_prim->type = cgltf_primitive_type_triangles; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) + { + ++i; + out_prim->type + = (cgltf_primitive_type) + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) + { + ++i; + out_prim->indices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "material") == 0) + { + ++i; + out_prim->material = CGLTF_PTRINDEX(cgltf_material, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "attributes") == 0) + { + i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_prim->attributes, &out_prim->attributes_count); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "targets") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_morph_target), (void**)&out_prim->targets, &out_prim->targets_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_prim->targets_count; ++k) + { + i = cgltf_parse_json_attribute_list(options, tokens, i, json_chunk, &out_prim->targets[k].attributes, &out_prim->targets[k].attributes_count); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_prim->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_draco_mesh_compression") == 0) + { + out_prim->has_draco_mesh_compression = 1; + i = cgltf_parse_json_draco_mesh_compression(options, tokens, i + 1, json_chunk, &out_prim->draco_mesh_compression); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh* out_mesh) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_mesh->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "primitives") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_primitive), (void**)&out_mesh->primitives, &out_mesh->primitives_count); + if (i < 0) + { + return i; + } + + for (cgltf_size prim_index = 0; prim_index < out_mesh->primitives_count; ++prim_index) + { + i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, &out_mesh->primitives[prim_index]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_mesh->weights, &out_mesh->weights_count); + if (i < 0) + { + return i; + } + + i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_mesh->weights, (int)out_mesh->weights_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + ++i; + + out_mesh->extras.start_offset = tokens[i].start; + out_mesh->extras.end_offset = tokens[i].end; + + if (tokens[i].type == JSMN_OBJECT) + { + int extras_size = tokens[i].size; + ++i; + + for (int k = 0; k < extras_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "targetNames") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i); + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_mesh), (void**)&out_data->meshes, &out_data->meshes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->meshes_count; ++j) + { + i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, &out_data->meshes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + int type = cgltf_json_to_int(tok, json_chunk); + + switch (type) + { + case 5120: + return cgltf_component_type_r_8; + case 5121: + return cgltf_component_type_r_8u; + case 5122: + return cgltf_component_type_r_16; + case 5123: + return cgltf_component_type_r_16u; + case 5125: + return cgltf_component_type_r_32u; + case 5126: + return cgltf_component_type_r_32f; + default: + return cgltf_component_type_invalid; + } +} + +static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_sparse->count = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int indices_size = tokens[i].size; + ++i; + + for (int k = 0; k < indices_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_sparse->indices_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_sparse->indices_byte_offset = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + out_sparse->indices_component_type = cgltf_json_to_component_type(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->indices_extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "values") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int values_size = tokens[i].size; + ++i; + + for (int k = 0; k < values_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_sparse->values_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_sparse->values_byte_offset = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->values_extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_accessor(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_accessor->offset = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + out_accessor->component_type = cgltf_json_to_component_type(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "normalized") == 0) + { + ++i; + out_accessor->normalized = cgltf_json_to_bool(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_accessor->count = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "SCALAR") == 0) + { + out_accessor->type = cgltf_type_scalar; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC2") == 0) + { + out_accessor->type = cgltf_type_vec2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC3") == 0) + { + out_accessor->type = cgltf_type_vec3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC4") == 0) + { + out_accessor->type = cgltf_type_vec4; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT2") == 0) + { + out_accessor->type = cgltf_type_mat2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT3") == 0) + { + out_accessor->type = cgltf_type_mat3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT4") == 0) + { + out_accessor->type = cgltf_type_mat4; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "min") == 0) + { + ++i; + out_accessor->has_min = 1; + // note: we can't parse the precise number of elements since type may not have been computed yet + int min_size = tokens[i].size > 16 ? 16 : tokens[i].size; + i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->min, min_size); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "max") == 0) + { + ++i; + out_accessor->has_max = 1; + // note: we can't parse the precise number of elements since type may not have been computed yet + int max_size = tokens[i].size > 16 ? 16 : tokens[i].size; + i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->max, max_size); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "sparse") == 0) + { + out_accessor->is_sparse = 1; + i = cgltf_parse_json_accessor_sparse(tokens, i + 1, json_chunk, &out_accessor->sparse); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_accessor->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_transform* out_texture_transform) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "offset") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->offset, 2); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0) + { + ++i; + out_texture_transform->rotation = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->scale, 2); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out_texture_transform->texcoord = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture_view(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_texture_view->scale = 1.0f; + cgltf_fill_float_array(out_texture_view->transform.scale, 2, 1.0f); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "index") == 0) + { + ++i; + out_texture_view->texture = CGLTF_PTRINDEX(cgltf_texture, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out_texture_view->texcoord = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + ++i; + out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "strength") == 0) + { + ++i; + out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture_view->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_texture_transform") == 0) + { + out_texture_view->has_transform = 1; + i = cgltf_parse_json_texture_transform(tokens, i + 1, json_chunk, &out_texture_view->transform); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_pbr_metallic_roughness(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_metallic_roughness* out_pbr) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "metallicFactor") == 0) + { + ++i; + out_pbr->metallic_factor = + cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "roughnessFactor") == 0) + { + ++i; + out_pbr->roughness_factor = + cgltf_json_to_float(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->base_color_factor, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_pbr->base_color_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "metallicRoughnessTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_pbr->metallic_roughness_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_pbr->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_pbr_specular_glossiness(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_specular_glossiness* out_pbr) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->diffuse_factor, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->specular_factor, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "glossinessFactor") == 0) + { + ++i; + out_pbr->glossiness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_pbr->diffuse_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularGlossinessTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_clearcoat(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_clearcoat* out_clearcoat) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatFactor") == 0) + { + ++i; + out_clearcoat->clearcoat_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessFactor") == 0) + { + ++i; + out_clearcoat->clearcoat_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_roughness_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatNormalTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_normal_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_image* out_image) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->uri); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_image->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->mime_type); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_image->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_sampler->wrap_s = 10497; + out_sampler->wrap_t = 10497; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) + { + ++i; + out_sampler->mag_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0) + { + ++i; + out_sampler->min_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0) + { + ++i; + out_sampler->wrap_s + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0) + { + ++i; + out_sampler->wrap_t + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture* out_texture) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_texture->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0) + { + ++i; + out_texture->sampler = CGLTF_PTRINDEX(cgltf_sampler, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) + { + ++i; + out_texture->image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material* out_material) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + cgltf_fill_float_array(out_material->pbr_metallic_roughness.base_color_factor, 4, 1.0f); + out_material->pbr_metallic_roughness.metallic_factor = 1.0f; + out_material->pbr_metallic_roughness.roughness_factor = 1.0f; + + cgltf_fill_float_array(out_material->pbr_specular_glossiness.diffuse_factor, 4, 1.0f); + cgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f); + out_material->pbr_specular_glossiness.glossiness_factor = 1.0f; + + out_material->alpha_cutoff = 0.5f; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_material->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "pbrMetallicRoughness") == 0) + { + out_material->has_pbr_metallic_roughness = 1; + i = cgltf_parse_json_pbr_metallic_roughness(tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_material->emissive_factor, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_material->normal_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_material->occlusion_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_material->emissive_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaMode") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "OPAQUE") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_opaque; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "MASK") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_mask; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "BLEND") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_blend; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaCutoff") == 0) + { + ++i; + out_material->alpha_cutoff = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0) + { + ++i; + out_material->double_sided = + cgltf_json_to_bool(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_material->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_pbrSpecularGlossiness") == 0) + { + out_material->has_pbr_specular_glossiness = 1; + i = cgltf_parse_json_pbr_specular_glossiness(tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_unlit") == 0) + { + out_material->unlit = 1; + i = cgltf_skip_json(tokens, i+1); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_clearcoat") == 0) + { + out_material->has_clearcoat = 1; + i = cgltf_parse_json_clearcoat(tokens, i + 1, json_chunk, &out_material->clearcoat); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_accessor), (void**)&out_data->accessors, &out_data->accessors_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->accessors_count; ++j) + { + i = cgltf_parse_json_accessor(tokens, i, json_chunk, &out_data->accessors[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material), (void**)&out_data->materials, &out_data->materials_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->materials_count; ++j) + { + i = cgltf_parse_json_material(options, tokens, i, json_chunk, &out_data->materials[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_images(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_image), (void**)&out_data->images, &out_data->images_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->images_count; ++j) + { + i = cgltf_parse_json_image(options, tokens, i, json_chunk, &out_data->images[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_texture), (void**)&out_data->textures, &out_data->textures_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->textures_count; ++j) + { + i = cgltf_parse_json_texture(options, tokens, i, json_chunk, &out_data->textures[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_sampler), (void**)&out_data->samplers, &out_data->samplers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->samplers_count; ++j) + { + i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, &out_data->samplers[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_buffer_view(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer_view* out_buffer_view) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) + { + ++i; + out_buffer_view->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_buffer_view->offset = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_buffer_view->size = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) + { + ++i; + out_buffer_view->stride = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) + { + ++i; + int type = cgltf_json_to_int(tokens+i, json_chunk); + switch (type) + { + case 34962: + type = cgltf_buffer_view_type_vertices; + break; + case 34963: + type = cgltf_buffer_view_type_indices; + break; + default: + type = cgltf_buffer_view_type_invalid; + break; + } + out_buffer_view->type = (cgltf_buffer_view_type)type; + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer_view->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer_view), (void**)&out_data->buffer_views, &out_data->buffer_views_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->buffer_views_count; ++j) + { + i = cgltf_parse_json_buffer_view(tokens, i, json_chunk, &out_data->buffer_views[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer* out_buffer) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_buffer->size = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "uri") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->uri); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer), (void**)&out_data->buffers, &out_data->buffers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->buffers_count; ++j) + { + i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, &out_data->buffers[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_skin* out_skin) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_skin->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "joints") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_skin->joints, &out_skin->joints_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_skin->joints_count; ++k) + { + out_skin->joints[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "skeleton") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_skin->skeleton = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "inverseBindMatrices") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_skin->inverse_bind_matrices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_skin->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_skins(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_skin), (void**)&out_data->skins, &out_data->skins_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->skins_count; ++j) + { + i = cgltf_parse_json_skin(options, tokens, i, json_chunk, &out_data->skins[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_camera* out_camera) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_camera->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "perspective") == 0) + { + out_camera->type = cgltf_camera_type_perspective; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "orthographic") == 0) + { + out_camera->type = cgltf_camera_type_orthographic; + } + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "perspective") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + out_camera->type = cgltf_camera_type_perspective; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "aspectRatio") == 0) + { + ++i; + out_camera->data.perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "yfov") == 0) + { + ++i; + out_camera->data.perspective.yfov = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) + { + ++i; + out_camera->data.perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) + { + ++i; + out_camera->data.perspective.znear = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->data.perspective.extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "orthographic") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + out_camera->type = cgltf_camera_type_orthographic; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "xmag") == 0) + { + ++i; + out_camera->data.orthographic.xmag = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "ymag") == 0) + { + ++i; + out_camera->data.orthographic.ymag = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) + { + ++i; + out_camera->data.orthographic.zfar = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) + { + ++i; + out_camera->data.orthographic.znear = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->data.orthographic.extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_cameras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_camera), (void**)&out_data->cameras, &out_data->cameras_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->cameras_count; ++j) + { + i = cgltf_parse_json_camera(options, tokens, i, json_chunk, &out_data->cameras[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_light* out_light) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_light->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "color") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_light->color, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "intensity") == 0) + { + ++i; + out_light->intensity = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "directional") == 0) + { + out_light->type = cgltf_light_type_directional; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "point") == 0) + { + out_light->type = cgltf_light_type_point; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "spot") == 0) + { + out_light->type = cgltf_light_type_spot; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "range") == 0) + { + ++i; + out_light->range = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "spot") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "innerConeAngle") == 0) + { + ++i; + out_light->spot_inner_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "outerConeAngle") == 0) + { + ++i; + out_light->spot_outer_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_lights(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_light), (void**)&out_data->lights, &out_data->lights_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->lights_count; ++j) + { + i = cgltf_parse_json_light(options, tokens, i, json_chunk, &out_data->lights[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_node* out_node) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_node->rotation[3] = 1.0f; + out_node->scale[0] = 1.0f; + out_node->scale[1] = 1.0f; + out_node->scale[2] = 1.0f; + out_node->matrix[0] = 1.0f; + out_node->matrix[5] = 1.0f; + out_node->matrix[10] = 1.0f; + out_node->matrix[15] = 1.0f; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_node->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "children") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_node->children, &out_node->children_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_node->children_count; ++k) + { + out_node->children[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "mesh") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->mesh = CGLTF_PTRINDEX(cgltf_mesh, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "skin") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->skin = CGLTF_PTRINDEX(cgltf_skin, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "camera") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->camera = CGLTF_PTRINDEX(cgltf_camera, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) + { + out_node->has_translation = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->translation, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) + { + out_node->has_rotation = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->rotation, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) + { + out_node->has_scale = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->scale, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "matrix") == 0) + { + out_node->has_matrix = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->matrix, 16); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_node->weights, &out_node->weights_count); + if (i < 0) + { + return i; + } + + i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_node->weights, (int)out_node->weights_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_node->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int m = 0; m < data_size; ++m) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "light") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->light = CGLTF_PTRINDEX(cgltf_light, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_nodes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_node), (void**)&out_data->nodes, &out_data->nodes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->nodes_count; ++j) + { + i = cgltf_parse_json_node(options, tokens, i, json_chunk, &out_data->nodes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_scene* out_scene) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_scene->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "nodes") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_scene->nodes, &out_scene->nodes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_scene->nodes_count; ++k) + { + out_scene->nodes[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_scene->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_scene), (void**)&out_data->scenes, &out_data->scenes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->scenes_count; ++j) + { + i = cgltf_parse_json_scene(options, tokens, i, json_chunk, &out_data->scenes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_sampler* out_sampler) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "input") == 0) + { + ++i; + out_sampler->input = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "output") == 0) + { + ++i; + out_sampler->output = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "interpolation") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "LINEAR") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_linear; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "STEP") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_step; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "CUBICSPLINE") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_cubic_spline; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_channel* out_channel) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "sampler") == 0) + { + ++i; + out_channel->sampler = CGLTF_PTRINDEX(cgltf_animation_sampler, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int target_size = tokens[i].size; + ++i; + + for (int k = 0; k < target_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "node") == 0) + { + ++i; + out_channel->target_node = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "path") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) + { + out_channel->target_path = cgltf_animation_path_type_translation; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) + { + out_channel->target_path = cgltf_animation_path_type_rotation; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) + { + out_channel->target_path = cgltf_animation_path_type_scale; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "weights") == 0) + { + out_channel->target_path = cgltf_animation_path_type_weights; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_channel->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation* out_animation) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_animation->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "samplers") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_sampler), (void**)&out_animation->samplers, &out_animation->samplers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_animation->samplers_count; ++k) + { + i = cgltf_parse_json_animation_sampler(options, tokens, i, json_chunk, &out_animation->samplers[k]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "channels") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_channel), (void**)&out_animation->channels, &out_animation->channels_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_animation->channels_count; ++k) + { + i = cgltf_parse_json_animation_channel(options, tokens, i, json_chunk, &out_animation->channels[k]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_animation->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_animation), (void**)&out_data->animations, &out_data->animations_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->animations_count; ++j) + { + i = cgltf_parse_json_animation(options, tokens, i, json_chunk, &out_data->animations[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_asset* out_asset) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "copyright") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->copyright); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "generator") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->generator); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "version") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->version); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "minVersion") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->min_version); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_asset->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + if (out_asset->version && CGLTF_ATOF(out_asset->version) < 2) + { + return CGLTF_ERROR_LEGACY; + } + + return i; +} + +cgltf_size cgltf_num_components(cgltf_type type) { + switch (type) + { + case cgltf_type_vec2: + return 2; + case cgltf_type_vec3: + return 3; + case cgltf_type_vec4: + return 4; + case cgltf_type_mat2: + return 4; + case cgltf_type_mat3: + return 9; + case cgltf_type_mat4: + return 16; + case cgltf_type_invalid: + case cgltf_type_scalar: + default: + return 1; + } +} + +static cgltf_size cgltf_component_size(cgltf_component_type component_type) { + switch (component_type) + { + case cgltf_component_type_r_8: + case cgltf_component_type_r_8u: + return 1; + case cgltf_component_type_r_16: + case cgltf_component_type_r_16u: + return 2; + case cgltf_component_type_r_32u: + case cgltf_component_type_r_32f: + return 4; + case cgltf_component_type_invalid: + default: + return 0; + } +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type) +{ + cgltf_size component_size = cgltf_component_size(component_type); + if (type == cgltf_type_mat2 && component_size == 1) + { + return 8 * component_size; + } + else if (type == cgltf_type_mat3 && (component_size == 1 || component_size == 2)) + { + return 12 * component_size; + } + return component_size * cgltf_num_components(type); +} + +static int cgltf_fixup_pointers(cgltf_data* out_data); + +static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "asset") == 0) + { + i = cgltf_parse_json_asset(options, tokens, i + 1, json_chunk, &out_data->asset); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "meshes") == 0) + { + i = cgltf_parse_json_meshes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "accessors") == 0) + { + i = cgltf_parse_json_accessors(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferViews") == 0) + { + i = cgltf_parse_json_buffer_views(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "buffers") == 0) + { + i = cgltf_parse_json_buffers(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "materials") == 0) + { + i = cgltf_parse_json_materials(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "images") == 0) + { + i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "textures") == 0) + { + i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "samplers") == 0) + { + i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "skins") == 0) + { + i = cgltf_parse_json_skins(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "cameras") == 0) + { + i = cgltf_parse_json_cameras(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "nodes") == 0) + { + i = cgltf_parse_json_nodes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scenes") == 0) + { + i = cgltf_parse_json_scenes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scene") == 0) + { + ++i; + out_data->scene = CGLTF_PTRINDEX(cgltf_scene, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "animations") == 0) + { + i = cgltf_parse_json_animations(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_data->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int m = 0; m < data_size; ++m) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "lights") == 0) + { + i = cgltf_parse_json_lights(options, tokens, i + 1, json_chunk, out_data); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsUsed") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_used, &out_data->extensions_used_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsRequired") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_required, &out_data->extensions_required_count); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data) +{ + jsmn_parser parser = { 0, 0, 0 }; + + if (options->json_token_count == 0) + { + int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0); + + if (token_count <= 0) + { + return cgltf_result_invalid_json; + } + + options->json_token_count = token_count; + } + + jsmntok_t* tokens = (jsmntok_t*)options->memory.alloc(options->memory.user_data, sizeof(jsmntok_t) * (options->json_token_count + 1)); + + if (!tokens) + { + return cgltf_result_out_of_memory; + } + + jsmn_init(&parser); + + int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count); + + if (token_count <= 0) + { + options->memory.free(options->memory.user_data, tokens); + return cgltf_result_invalid_json; + } + + // this makes sure that we always have an UNDEFINED token at the end of the stream + // for invalid JSON inputs this makes sure we don't perform out of bound reads of token data + tokens[token_count].type = JSMN_UNDEFINED; + + cgltf_data* data = (cgltf_data*)options->memory.alloc(options->memory.user_data, sizeof(cgltf_data)); + + if (!data) + { + options->memory.free(options->memory.user_data, tokens); + return cgltf_result_out_of_memory; + } + + memset(data, 0, sizeof(cgltf_data)); + data->memory = options->memory; + data->file = options->file; + + int i = cgltf_parse_json_root(options, tokens, 0, json_chunk, data); + + options->memory.free(options->memory.user_data, tokens); + + if (i < 0) + { + cgltf_free(data); + + switch (i) + { + case CGLTF_ERROR_NOMEM: return cgltf_result_out_of_memory; + case CGLTF_ERROR_LEGACY: return cgltf_result_legacy_gltf; + default: return cgltf_result_invalid_gltf; + } + } + + if (cgltf_fixup_pointers(data) < 0) + { + cgltf_free(data); + return cgltf_result_invalid_gltf; + } + + data->json = (const char*)json_chunk; + data->json_size = size; + + *out_data = data; + + return cgltf_result_success; +} + +static int cgltf_fixup_pointers(cgltf_data* data) +{ + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + CGLTF_PTRFIXUP(data->meshes[i].primitives[j].indices, data->accessors, data->accessors_count); + CGLTF_PTRFIXUP(data->meshes[i].primitives[j].material, data->materials, data->materials_count); + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].attributes[k].data, data->accessors, data->accessors_count); + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count); + } + } + + if (data->meshes[i].primitives[j].has_draco_mesh_compression) { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.buffer_view, data->buffer_views, data->buffer_views_count); + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++m) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.attributes[m].data, data->accessors, data->accessors_count); + } + } + } + } + + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + CGLTF_PTRFIXUP(data->accessors[i].buffer_view, data->buffer_views, data->buffer_views_count); + + if (data->accessors[i].is_sparse) + { + CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.indices_buffer_view, data->buffer_views, data->buffer_views_count); + CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.values_buffer_view, data->buffer_views, data->buffer_views_count); + } + + if (data->accessors[i].buffer_view) + { + data->accessors[i].stride = data->accessors[i].buffer_view->stride; + } + + if (data->accessors[i].stride == 0) + { + data->accessors[i].stride = cgltf_calc_size(data->accessors[i].type, data->accessors[i].component_type); + } + } + + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + CGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count); + CGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count); + } + + for (cgltf_size i = 0; i < data->images_count; ++i) + { + CGLTF_PTRFIXUP(data->images[i].buffer_view, data->buffer_views, data->buffer_views_count); + } + + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + CGLTF_PTRFIXUP(data->materials[i].normal_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].emissive_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].occlusion_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.base_color_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.diffuse_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_roughness_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_normal_texture.texture, data->textures, data->textures_count); + } + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + CGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count); + } + + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + for (cgltf_size j = 0; j < data->skins[i].joints_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->skins[i].joints[j], data->nodes, data->nodes_count); + } + + CGLTF_PTRFIXUP(data->skins[i].skeleton, data->nodes, data->nodes_count); + CGLTF_PTRFIXUP(data->skins[i].inverse_bind_matrices, data->accessors, data->accessors_count); + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + for (cgltf_size j = 0; j < data->nodes[i].children_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->nodes[i].children[j], data->nodes, data->nodes_count); + + if (data->nodes[i].children[j]->parent) + { + return CGLTF_ERROR_JSON; + } + + data->nodes[i].children[j]->parent = &data->nodes[i]; + } + + CGLTF_PTRFIXUP(data->nodes[i].mesh, data->meshes, data->meshes_count); + CGLTF_PTRFIXUP(data->nodes[i].skin, data->skins, data->skins_count); + CGLTF_PTRFIXUP(data->nodes[i].camera, data->cameras, data->cameras_count); + CGLTF_PTRFIXUP(data->nodes[i].light, data->lights, data->lights_count); + } + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->scenes[i].nodes[j], data->nodes, data->nodes_count); + + if (data->scenes[i].nodes[j]->parent) + { + return CGLTF_ERROR_JSON; + } + } + } + + CGLTF_PTRFIXUP(data->scene, data->scenes, data->scenes_count); + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].input, data->accessors, data->accessors_count); + CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].output, data->accessors, data->accessors_count); + } + + for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->animations[i].channels[j].sampler, data->animations[i].samplers, data->animations[i].samplers_count); + CGLTF_PTRFIXUP(data->animations[i].channels[j].target_node, data->nodes, data->nodes_count); + } + } + + return 0; +} + +/* + * -- jsmn.c start -- + * Source: https://github.com/zserge/jsmn + * License: MIT + * + * Copyright (c) 2010 Serge A. Zaitsev + + * 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. + */ + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + + found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, size_t num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if(token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +static void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} +/* + * -- jsmn.c end -- + */ + +#endif /* #ifdef CGLTF_IMPLEMENTATION */ + +/* cgltf is distributed under MIT license: + * + * Copyright (c) 2018 Johannes Kuhlmann + + * 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. + */ +/*** End of inlined file: cgltf.h ***/ + + +RF_INTERNAL cgltf_result rf_cgltf_io_read(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data) +{ + ((void) memory_options); + ((void) file_options); + + cgltf_result result = cgltf_result_file_not_found; + rf_io_callbacks* io = (rf_io_callbacks*) file_options->user_data; + + int file_size = RF_FILE_SIZE(*io, path); + + if (file_size > 0) + { + void* dst = CGLTF_MALLOC(file_size); + + if (dst == NULL) + { + if (RF_READ_FILE(*io, path, data, file_size) && data && size) + { + *data = dst; + *size = file_size; + result = cgltf_result_success; + } + else + { + CGLTF_FREE(dst); + result = cgltf_result_io_error; + } + } + else + { + result = cgltf_result_out_of_memory; + } + } + + return result; +} + +RF_INTERNAL void rf_cgltf_io_release(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data) +{ + ((void) memory_options); + ((void) file_options); + + CGLTF_FREE(data); +} +#pragma endregion + +#pragma endregion + +RF_INTERNAL rf_model rf_load_meshes_and_materials_for_model(rf_model model, rf_allocator allocator, rf_allocator temp_allocator) +{ + // Make sure model transform is set to identity matrix! + model.transform = rf_mat_identity(); + + if (model.mesh_count == 0) + { + RF_LOG(RF_LOG_TYPE_WARNING, "No meshes can be loaded, default to cube mesh."); + + model.mesh_count = 1; + model.meshes = (rf_mesh *) RF_ALLOC(allocator, sizeof(rf_mesh)); + memset(model.meshes, 0, sizeof(rf_mesh)); + model.meshes[0] = rf_gen_mesh_cube(1.0f, 1.0f, 1.0f, allocator, temp_allocator); + } + else + { + // Upload vertex data to GPU (static mesh) + for (rf_int i = 0; i < model.mesh_count; i++) + rf_gfx_load_mesh(&model.meshes[i], false); + } + + if (model.material_count == 0) + { + RF_LOG(RF_LOG_TYPE_WARNING, "No materials can be loaded, default to white material."); + + model.material_count = 1; + model.materials = (rf_material *) RF_ALLOC(allocator, sizeof(rf_material)); + memset(model.materials, 0, sizeof(rf_material)); + model.materials[0] = rf_load_default_material(allocator); + + if (model.mesh_material == NULL) + { + model.mesh_material = (int *) RF_ALLOC(allocator, model.mesh_count * sizeof(int)); + memset(model.mesh_material, 0, model.mesh_count * sizeof(int)); + } + } + + return model; +} + +// Compute mesh bounding box limits. Note: min_vertex and max_vertex should be transformed by model transform matrix +RF_API rf_bounding_box rf_mesh_bounding_box(rf_mesh mesh) +{ + // Get min and max vertex to construct bounds (AABB) + rf_vec3 min_vertex = {0}; + rf_vec3 max_vertex = {0}; + + if (mesh.vertices != NULL) + { + min_vertex = (rf_vec3){mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + max_vertex = (rf_vec3){mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] }; + + for (rf_int i = 1; i < mesh.vertex_count; i++) + { + min_vertex = rf_vec3_min(min_vertex, (rf_vec3) {mesh.vertices[i * 3], mesh.vertices[i * 3 + 1], + mesh.vertices[i * 3 + 2]}); + max_vertex = rf_vec3_max(max_vertex, (rf_vec3) {mesh.vertices[i * 3], mesh.vertices[i * 3 + 1], + mesh.vertices[i * 3 + 2]}); + } + } + + // Create the bounding box + rf_bounding_box box = {0}; + box.min = min_vertex; + box.max = max_vertex; + + return box; +} + +// Compute mesh tangents +// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates +// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html +RF_API void rf_mesh_compute_tangents(rf_mesh* mesh, rf_allocator allocator, rf_allocator temp_allocator) +{ + if (mesh->tangents == NULL) mesh->tangents = (float*) RF_ALLOC(allocator, mesh->vertex_count * 4 * sizeof(float)); + else RF_LOG(RF_LOG_TYPE_WARNING, "rf_mesh tangents already exist"); + + rf_vec3* tan1 = (rf_vec3*) RF_ALLOC(temp_allocator, mesh->vertex_count * sizeof(rf_vec3)); + rf_vec3* tan2 = (rf_vec3*) RF_ALLOC(temp_allocator, mesh->vertex_count * sizeof(rf_vec3)); + + for (rf_int i = 0; i < mesh->vertex_count; i += 3) + { + // Get triangle vertices + rf_vec3 v1 = { mesh->vertices[(i + 0) * 3 + 0], mesh->vertices[(i + 0) * 3 + 1], mesh->vertices[(i + 0) * 3 + 2] }; + rf_vec3 v2 = { mesh->vertices[(i + 1) * 3 + 0], mesh->vertices[(i + 1) * 3 + 1], mesh->vertices[(i + 1) * 3 + 2] }; + rf_vec3 v3 = { mesh->vertices[(i + 2) * 3 + 0], mesh->vertices[(i + 2) * 3 + 1], mesh->vertices[(i + 2) * 3 + 2] }; + + // Get triangle texcoords + rf_vec2 uv1 = { mesh->texcoords[(i + 0) * 2 + 0], mesh->texcoords[(i + 0) * 2 + 1] }; + rf_vec2 uv2 = { mesh->texcoords[(i + 1) * 2 + 0], mesh->texcoords[(i + 1) * 2 + 1] }; + rf_vec2 uv3 = { mesh->texcoords[(i + 2) * 2 + 0], mesh->texcoords[(i + 2) * 2 + 1] }; + + float x1 = v2.x - v1.x; + float y1 = v2.y - v1.y; + float z1 = v2.z - v1.z; + float x2 = v3.x - v1.x; + float y2 = v3.y - v1.y; + float z2 = v3.z - v1.z; + + float s1 = uv2.x - uv1.x; + float t1 = uv2.y - uv1.y; + float s2 = uv3.x - uv1.x; + float t2 = uv3.y - uv1.y; + + float div = s1 * t2 - s2 * t1; + float r = (div == 0.0f) ? (0.0f) : (1.0f / div); + + rf_vec3 sdir = {(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r }; + rf_vec3 tdir = {(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r }; + + tan1[i + 0] = sdir; + tan1[i + 1] = sdir; + tan1[i + 2] = sdir; + + tan2[i + 0] = tdir; + tan2[i + 1] = tdir; + tan2[i + 2] = tdir; + } + + // Compute tangents considering normals + for (rf_int i = 0; i < mesh->vertex_count; ++i) + { + rf_vec3 normal = {mesh->normals[i * 3 + 0], mesh->normals[i * 3 + 1], mesh->normals[i * 3 + 2] }; + rf_vec3 tangent = tan1[i]; + + // TODO: Review, not sure if tangent computation is right, just used reference proposed maths... + rf_vec3_ortho_normalize(&normal, &tangent); + mesh->tangents[i * 4 + 0] = tangent.x; + mesh->tangents[i * 4 + 1] = tangent.y; + mesh->tangents[i * 4 + 2] = tangent.z; + mesh->tangents[i * 4 + 3] = (rf_vec3_dot_product(rf_vec3_cross_product(normal, tangent), tan2[i]) < 0.0f) ? -1.0f : 1.0f; + } + + RF_FREE(temp_allocator, tan1); + RF_FREE(temp_allocator, tan2); + + // Load a new tangent attributes buffer + mesh->vbo_id[RF_LOC_VERTEX_TANGENT] = rf_gfx_load_attrib_buffer(mesh->vao_id, RF_LOC_VERTEX_TANGENT, mesh->tangents, mesh->vertex_count * 4 * sizeof(float), false); + + RF_LOG(RF_LOG_TYPE_INFO, "Tangents computed for mesh"); +} + +// Compute mesh binormals (aka bitangent) +RF_API void rf_mesh_compute_binormals(rf_mesh* mesh) +{ + for (rf_int i = 0; i < mesh->vertex_count; i++) + { + rf_vec3 normal = {mesh->normals[i * 3 + 0], mesh->normals[i * 3 + 1], mesh->normals[i * 3 + 2] }; + rf_vec3 tangent = {mesh->tangents[i * 4 + 0], mesh->tangents[i * 4 + 1], mesh->tangents[i * 4 + 2] }; + float tangent_w = mesh->tangents[i * 4 + 3]; + + // TODO: Register computed binormal in mesh->binormal? + // rf_vec3 binormal = rf_vec3_mul(rf_vec3_cross_product(normal, tangent), tangent_w); + } +} + +// Unload mesh from memory (RAM and/or VRAM) +RF_API void rf_unload_mesh(rf_mesh mesh, rf_allocator allocator) +{ + rf_gfx_unload_mesh(mesh); + + RF_FREE(allocator, mesh.vertices); + RF_FREE(allocator, mesh.texcoords); + RF_FREE(allocator, mesh.normals); + RF_FREE(allocator, mesh.colors); + RF_FREE(allocator, mesh.tangents); + RF_FREE(allocator, mesh.texcoords2); + RF_FREE(allocator, mesh.indices); + + RF_FREE(allocator, mesh.anim_vertices); + RF_FREE(allocator, mesh.anim_normals); + RF_FREE(allocator, mesh.bone_weights); + RF_FREE(allocator, mesh.bone_ids); + RF_FREE(allocator, mesh.vbo_id); +} + +RF_API rf_model rf_load_model(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_model model = {0}; + + if (rf_is_file_extension(filename, ".obj")) + { + model = rf_load_model_from_obj(filename, allocator, temp_allocator, io); + } + + if (rf_is_file_extension(filename, ".iqm")) + { + model = rf_load_model_from_iqm(filename, allocator, temp_allocator, io); + } + + if (rf_is_file_extension(filename, ".gltf") || rf_is_file_extension(filename, ".glb")) + { + model = rf_load_model_from_gltf(filename, allocator, temp_allocator, io); + } + + // Make sure model transform is set to identity matrix! + model.transform = rf_mat_identity(); + allocator = allocator; + + if (model.mesh_count == 0) + { + RF_LOG(RF_LOG_TYPE_WARNING, "No meshes can be loaded, default to cube mesh. Filename: %s", filename); + + model.mesh_count = 1; + model.meshes = (rf_mesh*) RF_ALLOC(allocator, model.mesh_count * sizeof(rf_mesh)); + memset(model.meshes, 0, model.mesh_count * sizeof(rf_mesh)); + model.meshes[0] = rf_gen_mesh_cube(1.0f, 1.0f, 1.0f, allocator, temp_allocator); + } + else + { + // Upload vertex data to GPU (static mesh) + for (rf_int i = 0; i < model.mesh_count; i++) + { + rf_gfx_load_mesh(&model.meshes[i], false); + } + } + + if (model.material_count == 0) + { + RF_LOG(RF_LOG_TYPE_WARNING, "No materials can be loaded, default to white material. Filename: %s", filename); + + model.material_count = 1; + model.materials = (rf_material*) RF_ALLOC(allocator, model.material_count * sizeof(rf_material)); + memset(model.materials, 0, model.material_count * sizeof(rf_material)); + model.materials[0] = rf_load_default_material(allocator); + + if (model.mesh_material == NULL) + { + model.mesh_material = (int*) RF_ALLOC(allocator, model.mesh_count * sizeof(int)); + } + } + + return model; +} + +// Load OBJ mesh data. Note: This calls into a library to do io, so we need to ask the user for IO callbacks +RF_API rf_model rf_load_model_from_obj(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_model model = {0}; + allocator = allocator; + + tinyobj_attrib_t attrib = {0}; + tinyobj_shape_t* meshes = NULL; + size_t mesh_count = 0; + + tinyobj_material_t* materials = NULL; + size_t material_count = 0; + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); // Set to NULL at the end of the function + RF_SET_TINYOBJ_IO_CALLBACKS(io); + { + unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; + int ret = tinyobj_parse_obj(&attrib, &meshes, (size_t*) &mesh_count, &materials, &material_count, filename, rf_tinyobj_file_reader_callback, flags); + + if (ret != TINYOBJ_SUCCESS) + { + RF_LOG(RF_LOG_TYPE_WARNING, "Model data could not be loaded. Filename %s", filename); + } + else + { + RF_LOG(RF_LOG_TYPE_INFO, "Model data loaded successfully: %i meshes / %i materials, filename: %s", mesh_count, material_count, filename); + } + + // Init model meshes array + { + // TODO: Support multiple meshes... in the meantime, only one mesh is returned + //model.mesh_count = mesh_count; + model.mesh_count = 1; + model.meshes = (rf_mesh*) RF_ALLOC(allocator, model.mesh_count * sizeof(rf_mesh)); + memset(model.meshes, 0, model.mesh_count * sizeof(rf_mesh)); + } + + // Init model materials array + if (material_count > 0) + { + model.material_count = material_count; + model.materials = (rf_material*) RF_ALLOC(allocator, model.material_count * sizeof(rf_material)); + memset(model.materials, 0, model.material_count * sizeof(rf_material)); + } + + model.mesh_material = (int*) RF_ALLOC(allocator, model.mesh_count * sizeof(int)); + memset(model.mesh_material, 0, model.mesh_count * sizeof(int)); + + // Init model meshes + for (rf_int m = 0; m < 1; m++) + { + rf_mesh mesh = (rf_mesh) + { + .vertex_count = attrib.num_faces * 3, + .triangle_count = attrib.num_faces, + + .vertices = (float*) RF_ALLOC(allocator, (attrib.num_faces * 3) * 3 * sizeof(float)), + .texcoords = (float*) RF_ALLOC(allocator, (attrib.num_faces * 3) * 2 * sizeof(float)), + .normals = (float*) RF_ALLOC(allocator, (attrib.num_faces * 3) * 3 * sizeof(float)), + .vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)), + }; + + memset(mesh.vertices, 0, mesh.vertex_count * 3 * sizeof(float)); + memset(mesh.texcoords, 0, mesh.vertex_count * 2 * sizeof(float)); + memset(mesh.normals, 0, mesh.vertex_count * 3 * sizeof(float)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + int vCount = 0; + int vtCount = 0; + int vnCount = 0; + + for (rf_int f = 0; f < attrib.num_faces; f++) + { + // Get indices for the face + tinyobj_vertex_index_t idx0 = attrib.faces[3 * f + 0]; + tinyobj_vertex_index_t idx1 = attrib.faces[3 * f + 1]; + tinyobj_vertex_index_t idx2 = attrib.faces[3 * f + 2]; + + // RF_LOG(RF_LOG_TYPE_DEBUG, "Face %i index: v %i/%i/%i . vt %i/%i/%i . vn %i/%i/%i\n", f, idx0.v_idx, idx1.v_idx, idx2.v_idx, idx0.vt_idx, idx1.vt_idx, idx2.vt_idx, idx0.vn_idx, idx1.vn_idx, idx2.vn_idx); + + // Fill vertices buffer (float) using vertex index of the face + for (rf_int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx0.v_idx * 3 + v]; } + vCount +=3; + + for (rf_int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx1.v_idx * 3 + v]; } + vCount +=3; + + for (rf_int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx2.v_idx * 3 + v]; } + vCount +=3; + + // Fill texcoords buffer (float) using vertex index of the face + // NOTE: Y-coordinate must be flipped upside-down + mesh.texcoords[vtCount + 0] = attrib.texcoords[idx0.vt_idx * 2 + 0]; + mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx0.vt_idx * 2 + 1]; vtCount += 2; + mesh.texcoords[vtCount + 0] = attrib.texcoords[idx1.vt_idx * 2 + 0]; + mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx1.vt_idx * 2 + 1]; vtCount += 2; + mesh.texcoords[vtCount + 0] = attrib.texcoords[idx2.vt_idx * 2 + 0]; + mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx2.vt_idx * 2 + 1]; vtCount += 2; + + // Fill normals buffer (float) using vertex index of the face + for (rf_int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx0.vn_idx * 3 + v]; } + vnCount +=3; + + for (rf_int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx1.vn_idx * 3 + v]; } + vnCount +=3; + + for (rf_int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx2.vn_idx * 3 + v]; } + vnCount +=3; + } + + model.meshes[m] = mesh; // Assign mesh data to model + + // Assign mesh material for current mesh + model.mesh_material[m] = attrib.material_ids[m]; + + // Set unfound materials to default + if (model.mesh_material[m] == -1) { model.mesh_material[m] = 0; } + } + + // Init model materials + for (rf_int m = 0; m < material_count; m++) + { + // Init material to default + // NOTE: Uses default shader, only RF_MAP_DIFFUSE supported + model.materials[m] = rf_load_default_material(allocator); + model.materials[m].maps[RF_MAP_DIFFUSE].texture = rf_get_default_texture(); // Get default texture, in case no texture is defined + + if (materials[m].diffuse_texname != NULL) + { + model.materials[m].maps[RF_MAP_DIFFUSE].texture = rf_load_texture_from_file(materials[m].diffuse_texname, temp_allocator, io); //char* diffuse_texname; // map_Kd + } + + model.materials[m].maps[RF_MAP_DIFFUSE].color = (rf_color) + { + (float)(materials[m].diffuse[0] * 255.0f), + (float)(materials[m].diffuse[1] * 255.0f), + (float)(materials[m].diffuse[2] * 255.0f), + 255 + }; + + model.materials[m].maps[RF_MAP_DIFFUSE].value = 0.0f; + + if (materials[m].specular_texname != NULL) + { + model.materials[m].maps[RF_MAP_SPECULAR].texture = rf_load_texture_from_file(materials[m].specular_texname, temp_allocator, io); //char* specular_texname; // map_Ks + } + + model.materials[m].maps[RF_MAP_SPECULAR].color = (rf_color) + { + (float)(materials[m].specular[0] * 255.0f), + (float)(materials[m].specular[1] * 255.0f), + (float)(materials[m].specular[2] * 255.0f), + 255 + }; + + model.materials[m].maps[RF_MAP_SPECULAR].value = 0.0f; + + if (materials[m].bump_texname != NULL) + { + model.materials[m].maps[RF_MAP_NORMAL].texture = rf_load_texture_from_file(materials[m].bump_texname, temp_allocator, io); //char* bump_texname; // map_bump, bump + } + + model.materials[m].maps[RF_MAP_NORMAL].color = RF_WHITE; + model.materials[m].maps[RF_MAP_NORMAL].value = materials[m].shininess; + + model.materials[m].maps[RF_MAP_EMISSION].color = (rf_color) + { + (float)(materials[m].emission[0] * 255.0f), + (float)(materials[m].emission[1] * 255.0f), + (float)(materials[m].emission[2] * 255.0f), + 255 + }; + + if (materials[m].displacement_texname != NULL) + { + model.materials[m].maps[RF_MAP_HEIGHT].texture = rf_load_texture_from_file(materials[m].displacement_texname, temp_allocator, io); //char* displacement_texname; // disp + } + } + + tinyobj_attrib_free(&attrib); + tinyobj_shapes_free(meshes, mesh_count); + tinyobj_materials_free(materials, material_count); + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + RF_SET_TINYOBJ_IO_CALLBACKS(RF_NULL_IO); + + // NOTE: At this point we have all model data loaded + RF_LOG(RF_LOG_TYPE_INFO, "Model loaded successfully in RAM. Filename: %s", filename); + + return rf_load_meshes_and_materials_for_model(model, allocator, temp_allocator); +} + +// Load IQM mesh data +RF_API rf_model rf_load_model_from_iqm(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + #pragma region constants + #define RF_IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define RF_IQM_VERSION 2 // only IQM version 2 supported + + #define RF_BONE_NAME_LENGTH 32 // rf_bone_info name string length + #define RF_MESH_NAME_LENGTH 32 // rf_mesh name string length + #pragma endregion + + #pragma region IQM file structs + typedef struct rf_iqm_header rf_iqm_header; + struct rf_iqm_header + { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + }; + + typedef struct rf_iqm_mesh rf_iqm_mesh; + struct rf_iqm_mesh + { + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; + }; + + typedef struct rf_iqm_triangle rf_iqm_triangle; + struct rf_iqm_triangle + { + unsigned int vertex[3]; + }; + + typedef struct rf_iqm_joint rf_iqm_joint; + struct rf_iqm_joint + { + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; + }; + + typedef struct rf_iqm_vertex_array rf_iqm_vertex_array; + struct rf_iqm_vertex_array + { + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; + }; + + // IQM vertex data types + typedef enum rf_iqm_vertex_type + { + RF_IQM_POSITION = 0, + RF_IQM_TEXCOORD = 1, + RF_IQM_NORMAL = 2, + RF_IQM_TANGENT = 3, // Note: Tangents unused by default + RF_IQM_BLENDINDEXES = 4, + RF_IQM_BLENDWEIGHTS = 5, + RF_IQM_COLOR = 6, // Note: Vertex colors unused by default + RF_IQM_CUSTOM = 0x10 // Note: Custom vertex values unused by default + } rf_iqm_vertex_type; + #pragma endregion + + rf_model model = {0}; + + size_t data_size = RF_FILE_SIZE(io, filename); + unsigned char* data = (unsigned char*) RF_ALLOC(temp_allocator, data_size); + + if (RF_READ_FILE(io, filename, data, data_size)) + { + RF_FREE(temp_allocator, data); + } + + rf_iqm_header iqm = *((rf_iqm_header*)data); + + rf_iqm_mesh* imesh; + rf_iqm_triangle* tri; + rf_iqm_vertex_array* va; + rf_iqm_joint* ijoint; + + float* vertex = NULL; + float* normal = NULL; + float* text = NULL; + char* blendi = NULL; + unsigned char* blendw = NULL; + + if (strncmp(iqm.magic, RF_IQM_MAGIC, sizeof(RF_IQM_MAGIC))) + { + RF_LOG(RF_LOG_TYPE_WARNING, "[%s] IQM file does not seem to be valid", filename); + return model; + } + + if (iqm.version != RF_IQM_VERSION) + { + RF_LOG(RF_LOG_TYPE_WARNING, "[%s] IQM file version is not supported (%i).", filename, iqm.version); + return model; + } + + // Meshes data processing + imesh = (rf_iqm_mesh*) RF_ALLOC(temp_allocator, sizeof(rf_iqm_mesh) * iqm.num_meshes); + memcpy(imesh, data + iqm.ofs_meshes, sizeof(rf_iqm_mesh) * iqm.num_meshes); + + model.mesh_count = iqm.num_meshes; + model.meshes = (rf_mesh*) RF_ALLOC(allocator, model.mesh_count * sizeof(rf_mesh)); + + char name[RF_MESH_NAME_LENGTH] = {0}; + for (rf_int i = 0; i < model.mesh_count; i++) + { + memcpy(name, data + (iqm.ofs_text + imesh[i].name), RF_MESH_NAME_LENGTH); + + model.meshes[i] = (rf_mesh) { + .vertex_count = imesh[i].num_vertexes + }; + + model.meshes[i].vertices = (float*) RF_ALLOC(allocator, model.meshes[i].vertex_count * 3 * sizeof(float)); // Default vertex positions + memset(model.meshes[i].vertices, 0, model.meshes[i].vertex_count * 3 * sizeof(float)); + + model.meshes[i].normals = (float*) RF_ALLOC(allocator, model.meshes[i].vertex_count * 3 * sizeof(float)); // Default vertex normals + memset(model.meshes[i].normals, 0, model.meshes[i].vertex_count * 3 * sizeof(float)); + + model.meshes[i].texcoords = (float*) RF_ALLOC(allocator, model.meshes[i].vertex_count * 2 * sizeof(float)); // Default vertex texcoords + memset(model.meshes[i].texcoords, 0, model.meshes[i].vertex_count * 2 * sizeof(float)); + + model.meshes[i].bone_ids = (int*) RF_ALLOC(allocator, model.meshes[i].vertex_count * 4 * sizeof(float)); // Up-to 4 bones supported! + memset(model.meshes[i].bone_ids, 0, model.meshes[i].vertex_count * 4 * sizeof(float)); + + model.meshes[i].bone_weights = (float*) RF_ALLOC(allocator, model.meshes[i].vertex_count * 4 * sizeof(float)); // Up-to 4 bones supported! + memset(model.meshes[i].bone_weights, 0, model.meshes[i].vertex_count * 4 * sizeof(float)); + + model.meshes[i].triangle_count = imesh[i].num_triangles; + + model.meshes[i].indices = (unsigned short*) RF_ALLOC(allocator, model.meshes[i].triangle_count * 3 * sizeof(unsigned short)); + memset(model.meshes[i].indices, 0, model.meshes[i].triangle_count * 3 * sizeof(unsigned short)); + + // Animated verted data, what we actually process for rendering + // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) + model.meshes[i].anim_vertices = (float*) RF_ALLOC(allocator, model.meshes[i].vertex_count * 3 * sizeof(float)); + memset(model.meshes[i].anim_vertices, 0, model.meshes[i].vertex_count * 3 * sizeof(float)); + + model.meshes[i].anim_normals = (float*) RF_ALLOC(allocator, model.meshes[i].vertex_count * 3 * sizeof(float)); + memset(model.meshes[i].anim_normals, 0, model.meshes[i].vertex_count * 3 * sizeof(float)); + + model.meshes[i].vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(model.meshes[i].vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + } + + // Triangles data processing + tri = (rf_iqm_triangle*) RF_ALLOC(temp_allocator, iqm.num_triangles * sizeof(rf_iqm_triangle)); + memcpy(tri, data + iqm.ofs_triangles, iqm.num_triangles * sizeof(rf_iqm_triangle)); + + for (rf_int m = 0; m < model.mesh_count; m++) + { + int tcounter = 0; + + for (rf_int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++) + { + // IQM triangles are stored counter clockwise, but raylib sets opengl to clockwise drawing, so we swap them around + model.meshes[m].indices[tcounter + 2] = tri[i].vertex[0] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter + 1] = tri[i].vertex[1] - imesh[m].first_vertex; + model.meshes[m].indices[tcounter ] = tri[i].vertex[2] - imesh[m].first_vertex; + tcounter += 3; + } + } + + // Vertex arrays data processing + va = (rf_iqm_vertex_array*) RF_ALLOC(temp_allocator, iqm.num_vertexarrays * sizeof(rf_iqm_vertex_array)); + memcpy(va, data + iqm.ofs_vertexarrays, iqm.num_vertexarrays * sizeof(rf_iqm_vertex_array)); + + for (rf_int i = 0; i < iqm.num_vertexarrays; i++) + { + switch (va[i].type) + { + case RF_IQM_POSITION: + { + vertex = (float*) RF_ALLOC(temp_allocator, iqm.num_vertexes * 3 * sizeof(float)); + memcpy(vertex, data + va[i].offset, iqm.num_vertexes * 3 * sizeof(float)); + + for (rf_int m = 0; m < iqm.num_meshes; m++) + { + int vertex_pos_counter = 0; + for (rf_int ii = imesh[m].first_vertex * 3; ii < (imesh[m].first_vertex + imesh[m].num_vertexes) * 3; ii++) + { + model.meshes[m].vertices [vertex_pos_counter] = vertex[ii]; + model.meshes[m].anim_vertices[vertex_pos_counter] = vertex[ii]; + vertex_pos_counter++; + } + } + } break; + + case RF_IQM_NORMAL: + { + normal = (float*) RF_ALLOC(temp_allocator, iqm.num_vertexes * 3 * sizeof(float)); + memcpy(normal, data + va[i].offset, iqm.num_vertexes * 3 * sizeof(float)); + + for (rf_int m = 0; m < iqm.num_meshes; m++) + { + int vertex_pos_counter = 0; + for (rf_int ii = imesh[m].first_vertex * 3; ii < (imesh[m].first_vertex + imesh[m].num_vertexes) * 3; ii++) + { + model.meshes[m].normals [vertex_pos_counter] = normal[ii]; + model.meshes[m].anim_normals[vertex_pos_counter] = normal[ii]; + vertex_pos_counter++; + } + } + } break; + + case RF_IQM_TEXCOORD: + { + text = (float*) RF_ALLOC(temp_allocator, iqm.num_vertexes * 2 * sizeof(float)); + memcpy(text, data + va[i].offset, iqm.num_vertexes * 2 * sizeof(float)); + + for (rf_int m = 0; m < iqm.num_meshes; m++) + { + int vertex_pos_counter = 0; + for (rf_int ii = imesh[m].first_vertex * 2; ii < (imesh[m].first_vertex + imesh[m].num_vertexes) * 2; ii++) + { + model.meshes[m].texcoords[vertex_pos_counter] = text[ii]; + vertex_pos_counter++; + } + } + } break; + + case RF_IQM_BLENDINDEXES: + { + blendi = (char*) RF_ALLOC(temp_allocator, iqm.num_vertexes * 4 * sizeof(char)); + memcpy(blendi, data + va[i].offset, iqm.num_vertexes * 4 * sizeof(char)); + + for (rf_int m = 0; m < iqm.num_meshes; m++) + { + int bone_counter = 0; + for (rf_int ii = imesh[m].first_vertex * 4; ii < (imesh[m].first_vertex + imesh[m].num_vertexes) * 4; ii++) + { + model.meshes[m].bone_ids[bone_counter] = blendi[ii]; + bone_counter++; + } + } + } break; + + case RF_IQM_BLENDWEIGHTS: + { + blendw = (unsigned char*) RF_ALLOC(temp_allocator, iqm.num_vertexes * 4 * sizeof(unsigned char)); + memcpy(blendw, data + va[i].offset, iqm.num_vertexes * 4 * sizeof(unsigned char)); + + for (rf_int m = 0; m < iqm.num_meshes; m++) + { + int bone_counter = 0; + for (rf_int ii = imesh[m].first_vertex * 4; ii < (imesh[m].first_vertex + imesh[m].num_vertexes) * 4; ii++) + { + model.meshes[m].bone_weights[bone_counter] = blendw[ii] / 255.0f; + bone_counter++; + } + } + } break; + } + } + + // Bones (joints) data processing + ijoint = (rf_iqm_joint*) RF_ALLOC(temp_allocator, iqm.num_joints * sizeof(rf_iqm_joint)); + memcpy(ijoint, data + iqm.ofs_joints, iqm.num_joints * sizeof(rf_iqm_joint)); + + model.bone_count = iqm.num_joints; + model.bones = (rf_bone_info*) RF_ALLOC(allocator, iqm.num_joints * sizeof(rf_bone_info)); + model.bind_pose = (rf_transform*) RF_ALLOC(allocator, iqm.num_joints * sizeof(rf_transform)); + + for (rf_int i = 0; i < iqm.num_joints; i++) + { + // Bones + model.bones[i].parent = ijoint[i].parent; + memcpy(model.bones[i].name, data + iqm.ofs_text + ijoint[i].name, RF_BONE_NAME_LENGTH * sizeof(char)); + + // Bind pose (base pose) + model.bind_pose[i].translation.x = ijoint[i].translate[0]; + model.bind_pose[i].translation.y = ijoint[i].translate[1]; + model.bind_pose[i].translation.z = ijoint[i].translate[2]; + + model.bind_pose[i].rotation.x = ijoint[i].rotate[0]; + model.bind_pose[i].rotation.y = ijoint[i].rotate[1]; + model.bind_pose[i].rotation.z = ijoint[i].rotate[2]; + model.bind_pose[i].rotation.w = ijoint[i].rotate[3]; + + model.bind_pose[i].scale.x = ijoint[i].scale[0]; + model.bind_pose[i].scale.y = ijoint[i].scale[1]; + model.bind_pose[i].scale.z = ijoint[i].scale[2]; + } + + // Build bind pose from parent joints + for (rf_int i = 0; i < model.bone_count; i++) + { + if (model.bones[i].parent >= 0) + { + model.bind_pose[i].rotation = rf_quaternion_mul(model.bind_pose[model.bones[i].parent].rotation, model.bind_pose[i].rotation); + model.bind_pose[i].translation = rf_vec3_rotate_by_quaternion(model.bind_pose[i].translation, model.bind_pose[model.bones[i].parent].rotation); + model.bind_pose[i].translation = rf_vec3_add(model.bind_pose[i].translation, model.bind_pose[model.bones[i].parent].translation); + model.bind_pose[i].scale = rf_vec3_mul_v(model.bind_pose[i].scale, model.bind_pose[model.bones[i].parent].scale); + } + } + + RF_FREE(temp_allocator, imesh); + RF_FREE(temp_allocator, tri); + RF_FREE(temp_allocator, va); + RF_FREE(temp_allocator, vertex); + RF_FREE(temp_allocator, normal); + RF_FREE(temp_allocator, text); + RF_FREE(temp_allocator, blendi); + RF_FREE(temp_allocator, blendw); + RF_FREE(temp_allocator, ijoint); + + return rf_load_meshes_and_materials_for_model(model, allocator, temp_allocator); +} + +/*********************************************************************************** + Function based on work by Wilhem Barbier (@wbrbr) + + Features: + - Supports .gltf and .glb files + - Supports embedded (base64) or external textures + - Loads the albedo/diffuse texture (other maps could be added) + - Supports multiple mesh per model and multiple primitives per model + + Some restrictions (not exhaustive): + - Triangle-only meshes + - Not supported node hierarchies or transforms + - Only loads the diffuse texture... but not too hard to support other maps (normal, roughness/metalness...) + - Only supports unsigned short indices (no unsigned char/unsigned int) + - Only supports float for texture coordinates (no unsigned char/unsigned short) +*************************************************************************************/ +// Load texture from cgltf_image +RF_INTERNAL rf_texture2d rf_load_texture_from_cgltf_image(cgltf_image* image, const char* tex_path, rf_color tint, rf_allocator temp_allocator, rf_io_callbacks io) +{ + rf_texture2d texture = {0}; + + if (image->uri) + { + if ((strlen(image->uri) > 5) && + (image->uri[0] == 'd') && + (image->uri[1] == 'a') && + (image->uri[2] == 't') && + (image->uri[3] == 'a') && + (image->uri[4] == ':')) + { + // Data URI + // Format: data:;base64, + + // Find the comma + int i = 0; + while ((image->uri[i] != ',') && (image->uri[i] != 0)) i++; + + if (image->uri[i] == 0) + { + RF_LOG(RF_LOG_TYPE_WARNING, "CGLTF rf_image: Invalid data URI"); + } + else + { + rf_base64_output data = rf_decode_base64((const unsigned char*)image->uri + i + 1, temp_allocator); + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + int w, h; + unsigned char* raw = stbi_load_from_memory(data.buffer, data.size, &w, &h, NULL, 4); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + rf_image rimage = { + .data = raw, + .width = w, + .height = h, + .format = RF_UNCOMPRESSED_R8G8B8A8, + }; + + // TODO: Tint shouldn't be applied here! + rf_image_color_tint(rimage, tint); + + texture = rf_load_texture_from_image(rimage); + + rf_unload_image(rimage, temp_allocator); + RF_FREE(temp_allocator, data.buffer); + } + } + else + { + char buff[1024]; + snprintf(buff, 1024, "%s/%s", tex_path, image->uri); + rf_image rimage = rf_load_image_from_file(buff, temp_allocator, temp_allocator, io); + + // TODO: Tint shouldn't be applied here! + rf_image_color_tint(rimage, tint); + + texture = rf_load_texture_from_image(rimage); + + rf_unload_image(rimage, temp_allocator); + } + } + else if (image->buffer_view) + { + unsigned char* data = (unsigned char*) RF_ALLOC(temp_allocator, image->buffer_view->size); + int n = image->buffer_view->offset; + int stride = image->buffer_view->stride ? image->buffer_view->stride : 1; + + for (rf_int i = 0; i < image->buffer_view->size; i++) + { + data[i] = ((unsigned char* )image->buffer_view->buffer->data)[n]; + n += stride; + } + + int w, h; + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + unsigned char* raw = stbi_load_from_memory(data, image->buffer_view->size, &w, &h, NULL, 4); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + rf_image rimage = { + .data = raw, + .width = w, + .height = h, + .format = RF_UNCOMPRESSED_R8G8B8A8, + }; + + // TODO: Tint shouldn't be applied here! + rf_image_color_tint(rimage, tint); + + texture = rf_load_texture_from_image(rimage); + + rf_unload_image(rimage, temp_allocator); + RF_FREE(temp_allocator, data); + RF_FREE(temp_allocator, raw); + } + else + { + texture = rf_load_texture_from_image((rf_image) { + .data = &tint, + .width = 1, + .height = 1, + .format = RF_UNCOMPRESSED_R8G8B8A8, + .valid = true + }); + } + + return texture; +} + +// Load model from files (meshes and materials) +RF_API rf_model rf_load_model_from_gltf(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + #define rf_load_accessor(type, nbcomp, acc, dst)\ + { \ + int n = 0; \ + type* buf = (type*) acc->buffer_view->buffer->data + acc->buffer_view->offset / sizeof(type) + acc->offset / sizeof(type); \ + for (rf_int k = 0; k < acc->count; k++) { \ + for (rf_int l = 0; l < nbcomp; l++) { \ + dst[nbcomp * k + l] = buf[n + l]; \ + } \ + n += acc->stride / sizeof(type); \ + } \ + } + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + rf_model model = {0}; + + cgltf_options options = { + cgltf_file_type_invalid, + .file = { + .read = &rf_cgltf_io_read, + .release = &rf_cgltf_io_release, + .user_data = &io + } + }; + + int data_size = RF_FILE_SIZE(io, filename); + void* data = RF_ALLOC(temp_allocator, data_size); + if (!RF_READ_FILE(io, filename, data, data_size)) + { + RF_FREE(temp_allocator, data); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + return model; + } + + cgltf_data* cgltf_data = NULL; + cgltf_result result = cgltf_parse(&options, data, data_size, &cgltf_data); + + if (result == cgltf_result_success) + { + RF_LOG(RF_LOG_TYPE_INFO, "[%s][%s] rf_model meshes/materials: %i/%i", filename, (cgltf_data->file_type == 2) ? "glb" : "gltf", cgltf_data->meshes_count, cgltf_data->materials_count); + + // Read cgltf_data buffers + result = cgltf_load_buffers(&options, cgltf_data, filename); + if (result != cgltf_result_success) { + RF_LOG(RF_LOG_TYPE_INFO, "[%s][%s] Error loading mesh/material buffers", filename, (cgltf_data->file_type == 2) ? "glb" : "gltf"); + } + + int primitivesCount = 0; + + for (rf_int i = 0; i < cgltf_data->meshes_count; i++) + { + primitivesCount += (int)cgltf_data->meshes[i].primitives_count; + } + + // Process glTF cgltf_data and map to model + allocator = allocator; + model.mesh_count = primitivesCount; + model.material_count = cgltf_data->materials_count + 1; + model.meshes = (rf_mesh*) RF_ALLOC(allocator, model.mesh_count * sizeof(rf_mesh)); + model.materials = (rf_material*) RF_ALLOC(allocator, model.material_count * sizeof(rf_material)); + model.mesh_material = (int*) RF_ALLOC(allocator, model.mesh_count * sizeof(int)); + + memset(model.meshes, 0, model.mesh_count * sizeof(rf_mesh)); + + for (rf_int i = 0; i < model.mesh_count; i++) + { + model.meshes[i].vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(model.meshes[i].vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + } + + //For each material + for (rf_int i = 0; i < model.material_count - 1; i++) + { + model.materials[i] = rf_load_default_material(allocator); + rf_color tint = (rf_color){ 255, 255, 255, 255 }; + const char* tex_path = rf_get_directory_path_from_file_path(filename); + + //Ensure material follows raylib support for PBR (metallic/roughness flow) + if (cgltf_data->materials[i].has_pbr_metallic_roughness) + { + float roughness = cgltf_data->materials[i].pbr_metallic_roughness.roughness_factor; + float metallic = cgltf_data->materials[i].pbr_metallic_roughness.metallic_factor; + + // NOTE: rf_material name not used for the moment + //if (model.materials[i].name && cgltf_data->materials[i].name) strcpy(model.materials[i].name, cgltf_data->materials[i].name); + + // TODO: REview: shouldn't these be *255 ??? + tint.r = (unsigned char)(cgltf_data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255); + tint.g = (unsigned char)(cgltf_data->materials[i].pbr_metallic_roughness.base_color_factor[1] * 255); + tint.b = (unsigned char)(cgltf_data->materials[i].pbr_metallic_roughness.base_color_factor[2] * 255); + tint.a = (unsigned char)(cgltf_data->materials[i].pbr_metallic_roughness.base_color_factor[3] * 255); + + model.materials[i].maps[RF_MAP_ROUGHNESS].color = tint; + + if (cgltf_data->materials[i].pbr_metallic_roughness.base_color_texture.texture) + { + model.materials[i].maps[RF_MAP_ALBEDO].texture = rf_load_texture_from_cgltf_image(cgltf_data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, tex_path, tint, temp_allocator, io); + } + + // NOTE: Tint isn't need for other textures.. pass null or clear? + // Just set as white, multiplying by white has no effect + tint = RF_WHITE; + + if (cgltf_data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) + { + model.materials[i].maps[RF_MAP_ROUGHNESS].texture = rf_load_texture_from_cgltf_image(cgltf_data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, tex_path, tint, temp_allocator, io); + } + model.materials[i].maps[RF_MAP_ROUGHNESS].value = roughness; + model.materials[i].maps[RF_MAP_METALNESS].value = metallic; + + if (cgltf_data->materials[i].normal_texture.texture) + { + model.materials[i].maps[RF_MAP_NORMAL].texture = rf_load_texture_from_cgltf_image(cgltf_data->materials[i].normal_texture.texture->image, tex_path, tint, temp_allocator, io); + } + + if (cgltf_data->materials[i].occlusion_texture.texture) + { + model.materials[i].maps[RF_MAP_OCCLUSION].texture = rf_load_texture_from_cgltf_image(cgltf_data->materials[i].occlusion_texture.texture->image, tex_path, tint, temp_allocator, io); + } + } + } + + model.materials[model.material_count - 1] = rf_load_default_material(allocator); + + int primitiveIndex = 0; + + for (rf_int i = 0; i < cgltf_data->meshes_count; i++) + { + for (rf_int p = 0; p < cgltf_data->meshes[i].primitives_count; p++) + { + for (rf_int j = 0; j < cgltf_data->meshes[i].primitives[p].attributes_count; j++) + { + if (cgltf_data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) + { + cgltf_accessor* acc = cgltf_data->meshes[i].primitives[p].attributes[j].data; + model.meshes[primitiveIndex].vertex_count = acc->count; + model.meshes[primitiveIndex].vertices = (float*) RF_ALLOC(allocator, sizeof(float)*model.meshes[primitiveIndex].vertex_count * 3); + + rf_load_accessor(float, 3, acc, model.meshes[primitiveIndex].vertices) + } + else if (cgltf_data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) + { + cgltf_accessor* acc = cgltf_data->meshes[i].primitives[p].attributes[j].data; + model.meshes[primitiveIndex].normals = (float*) RF_ALLOC(allocator, sizeof(float)*acc->count * 3); + + rf_load_accessor(float, 3, acc, model.meshes[primitiveIndex].normals) + } + else if (cgltf_data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) + { + cgltf_accessor* acc = cgltf_data->meshes[i].primitives[p].attributes[j].data; + + if (acc->component_type == cgltf_component_type_r_32f) + { + model.meshes[primitiveIndex].texcoords = (float*) RF_ALLOC(allocator, sizeof(float)*acc->count * 2); + rf_load_accessor(float, 2, acc, model.meshes[primitiveIndex].texcoords) + } + else + { + // TODO: Support normalized unsigned unsigned char/unsigned short texture coordinates + RF_LOG(RF_LOG_TYPE_WARNING, "[%s] rf_texture coordinates must be float", filename); + } + } + } + + cgltf_accessor* acc = cgltf_data->meshes[i].primitives[p].indices; + + if (acc) + { + if (acc->component_type == cgltf_component_type_r_16u) + { + model.meshes[primitiveIndex].triangle_count = acc->count/3; + model.meshes[primitiveIndex].indices = (unsigned short*) RF_ALLOC(allocator, sizeof(unsigned short)*model.meshes[primitiveIndex].triangle_count * 3); + rf_load_accessor(unsigned short, 1, acc, model.meshes[primitiveIndex].indices) + } + else + { + // TODO: Support unsigned unsigned char/unsigned int + RF_LOG(RF_LOG_TYPE_WARNING, "[%s] Indices must be unsigned short", filename); + } + } + else + { + // Unindexed mesh + model.meshes[primitiveIndex].triangle_count = model.meshes[primitiveIndex].vertex_count/3; + } + + if (cgltf_data->meshes[i].primitives[p].material) + { + // Compute the offset + model.mesh_material[primitiveIndex] = cgltf_data->meshes[i].primitives[p].material - cgltf_data->materials; + } + else + { + model.mesh_material[primitiveIndex] = model.material_count - 1; + } + + primitiveIndex++; + } + } + + cgltf_free(cgltf_data); + } + else + { + RF_LOG(RF_LOG_TYPE_WARNING, "[%s] glTF cgltf_data could not be loaded", filename); + } + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + return model; + + #undef rf_load_accessor +} + +// Load model from generated mesh. Note: The function takes ownership of the mesh in model.meshes[0] +RF_API rf_model rf_load_model_from_mesh(rf_mesh mesh, rf_allocator allocator) +{ + rf_model model = {0}; + + model.transform = rf_mat_identity(); + + model.mesh_count = 1; + model.meshes = (rf_mesh*) RF_ALLOC(allocator, model.mesh_count * sizeof(rf_mesh)); + memset(model.meshes, 0, model.mesh_count * sizeof(rf_mesh)); + model.meshes[0] = mesh; + + model.material_count = 1; + model.materials = (rf_material*) RF_ALLOC(allocator, model.material_count * sizeof(rf_material)); + memset(model.materials, 0, model.material_count * sizeof(rf_material)); + model.materials[0] = rf_load_default_material(allocator); + + model.mesh_material = (int*) RF_ALLOC(allocator, model.mesh_count * sizeof(int)); + memset(model.mesh_material, 0, model.mesh_count * sizeof(int)); + model.mesh_material[0] = 0; // First material index + + return model; +} + +// Unload model from memory (RAM and/or VRAM) +RF_API void rf_unload_model(rf_model model, rf_allocator allocator) +{ + for (rf_int i = 0; i < model.mesh_count; i++) rf_unload_mesh(model.meshes[i], allocator); + + // As the user could be sharing shaders and textures between models, + // we don't unload the material but just free it's maps, the user + // is responsible for freeing models shaders and textures + for (rf_int i = 0; i < model.material_count; i++) RF_FREE(allocator, model.materials[i].maps); + + RF_FREE(allocator, model.meshes); + RF_FREE(allocator, model.materials); + RF_FREE(allocator, model.mesh_material); + + // Unload animation data + RF_FREE(allocator, model.bones); + RF_FREE(allocator, model.bind_pose); + + RF_LOG(RF_LOG_TYPE_INFO, "Unloaded model data from RAM and VRAM"); +} + +#pragma region materials + +// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RF_API rf_material rf_load_default_material(rf_allocator allocator) +{ + rf_material material = {0}; + material.maps = (rf_material_map*) RF_ALLOC(allocator, RF_MAX_MATERIAL_MAPS * sizeof(rf_material_map)); + memset(material.maps, 0, RF_MAX_MATERIAL_MAPS * sizeof(rf_material_map)); + + material.shader = rf_get_default_shader(); + material.maps[RF_MAP_DIFFUSE].texture = rf_get_default_texture(); // White texture (1x1 pixel) + //material.maps[RF_MAP_NORMAL].texture; // NOTE: By default, not set + //material.maps[RF_MAP_SPECULAR].texture; // NOTE: By default, not set + + material.maps[RF_MAP_DIFFUSE].color = RF_WHITE; // Diffuse color + material.maps[RF_MAP_SPECULAR].color = RF_WHITE; // Specular color + + return material; +} + +// TODO: Support IQM and GLTF for materials parsing +// TODO: Process materials to return +// Load materials from model file +RF_API rf_materials_array rf_load_materials_from_mtl(const char* filename, rf_allocator allocator, rf_io_callbacks io) +{ + if (!filename) return (rf_materials_array) {0}; + + rf_materials_array result = {0}; + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(allocator); + RF_SET_TINYOBJ_IO_CALLBACKS(io); + { + size_t size = 0; + tinyobj_material_t* mats = 0; + if (tinyobj_parse_mtl_file(&mats, (size_t*) &size, filename, rf_tinyobj_file_reader_callback) != TINYOBJ_SUCCESS) + { + // Log Error + } + tinyobj_materials_free(mats, result.size); + + result.size = size; + } + RF_SET_TINYOBJ_IO_CALLBACKS(RF_NULL_IO); + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + // Set materials shader to default (DIFFUSE, SPECULAR, NORMAL) + for (rf_int i = 0; i < result.size; i++) + { + result.materials[i].shader = rf_get_default_shader(); + } + + return result; +} + +RF_API void rf_unload_material(rf_material material, rf_allocator allocator) +{ + // Unload material shader (avoid unloading default shader, managed by raylib) + if (material.shader.id != rf_get_default_shader().id) + { + rf_gfx_unload_shader(material.shader); + } + + // Unload loaded texture maps (avoid unloading default texture, managed by raylib) + for (rf_int i = 0; i < RF_MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id != rf_get_default_texture().id) + { + rf_gfx_delete_textures(material.maps[i].texture.id); + } + } + + RF_FREE(allocator, material.maps); +} + +RF_API void rf_set_material_texture(rf_material* material, rf_material_map_type map_type, rf_texture2d texture); // Set texture for a material map type (rf_map_diffuse, rf_map_specular...) + +RF_API void rf_set_model_mesh_material(rf_model* model, int mesh_id, int material_id); // Set material for a mesh + +#pragma endregion + +#pragma region model animations +RF_API rf_model_animation_array rf_load_model_animations_from_iqm_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io) +{ + int size = RF_FILE_SIZE(io, filename); + void* data = RF_ALLOC(temp_allocator, size); + + rf_model_animation_array result = rf_load_model_animations_from_iqm(data, size, allocator, temp_allocator); + + RF_FREE(temp_allocator, data); + + return result; +} + +RF_API rf_model_animation_array rf_load_model_animations_from_iqm(const unsigned char* data, int data_size, rf_allocator allocator, rf_allocator temp_allocator) +{ + if (!data || !data_size) return (rf_model_animation_array) {0}; + + #define RF_IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number + #define RF_IQM_VERSION 2 // only IQM version 2 supported + + typedef struct rf_iqm_header rf_iqm_header; + struct rf_iqm_header + { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + }; + + typedef struct rf_iqm_pose rf_iqm_pose; + struct rf_iqm_pose + { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + }; + + typedef struct rf_iqm_anim rf_iqm_anim; + struct rf_iqm_anim + { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + }; + + rf_iqm_header iqm; + + // Read IQM header + memcpy(&iqm, data, sizeof(rf_iqm_header)); + + if (strncmp(iqm.magic, RF_IQM_MAGIC, sizeof(RF_IQM_MAGIC))) + { + char temp_str[sizeof(RF_IQM_MAGIC) + 1] = {0}; + memcpy(temp_str, iqm.magic, sizeof(RF_IQM_MAGIC)); + RF_LOG_ERROR(RF_BAD_FORMAT, "Magic Number \"%s\"does not match.", temp_str); + + return (rf_model_animation_array){0}; + } + + if (iqm.version != RF_IQM_VERSION) + { + RF_LOG_ERROR(RF_BAD_FORMAT, "IQM version %i is incorrect.", iqm.version); + + return (rf_model_animation_array){0}; + } + + rf_model_animation_array result = { + .size = iqm.num_anims, + }; + + // Get bones data + rf_iqm_pose* poses = (rf_iqm_pose*) RF_ALLOC(temp_allocator, iqm.num_poses * sizeof(rf_iqm_pose)); + memcpy(poses, data + iqm.ofs_poses, iqm.num_poses * sizeof(rf_iqm_pose)); + + // Get animations data + rf_iqm_anim* anim = (rf_iqm_anim*) RF_ALLOC(temp_allocator, iqm.num_anims * sizeof(rf_iqm_anim)); + memcpy(anim, data + iqm.ofs_anims, iqm.num_anims * sizeof(rf_iqm_anim)); + + rf_model_animation* animations = (rf_model_animation*) RF_ALLOC(allocator, iqm.num_anims * sizeof(rf_model_animation)); + + result.anims = animations; + result.size = iqm.num_anims; + + // frameposes + unsigned short* framedata = (unsigned short*) RF_ALLOC(temp_allocator, iqm.num_frames * iqm.num_framechannels * sizeof(unsigned short)); + memcpy(framedata, data + iqm.ofs_frames, iqm.num_frames*iqm.num_framechannels * sizeof(unsigned short)); + + for (rf_int a = 0; a < iqm.num_anims; a++) + { + animations[a].frame_count = anim[a].num_frames; + animations[a].bone_count = iqm.num_poses; + animations[a].bones = (rf_bone_info*) RF_ALLOC(allocator, iqm.num_poses * sizeof(rf_bone_info)); + animations[a].frame_poses = (rf_transform**) RF_ALLOC(allocator, anim[a].num_frames * sizeof(rf_transform*)); + //animations[a].framerate = anim.framerate; // TODO: Use framerate? + + for (rf_int j = 0; j < iqm.num_poses; j++) + { + strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); + animations[a].bones[j].parent = poses[j].parent; + } + + for (rf_int j = 0; j < anim[a].num_frames; j++) + { + animations[a].frame_poses[j] = (rf_transform*) RF_ALLOC(allocator, iqm.num_poses * sizeof(rf_transform)); + } + + int dcounter = anim[a].first_frame*iqm.num_framechannels; + + for (rf_int frame = 0; frame < anim[a].num_frames; frame++) + { + for (rf_int i = 0; i < iqm.num_poses; i++) + { + animations[a].frame_poses[frame][i].translation.x = poses[i].channeloffset[0]; + + if (poses[i].mask & 0x01) + { + animations[a].frame_poses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; + dcounter++; + } + + animations[a].frame_poses[frame][i].translation.y = poses[i].channeloffset[1]; + + if (poses[i].mask & 0x02) + { + animations[a].frame_poses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; + dcounter++; + } + + animations[a].frame_poses[frame][i].translation.z = poses[i].channeloffset[2]; + + if (poses[i].mask & 0x04) + { + animations[a].frame_poses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; + dcounter++; + } + + animations[a].frame_poses[frame][i].rotation.x = poses[i].channeloffset[3]; + + if (poses[i].mask & 0x08) + { + animations[a].frame_poses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; + dcounter++; + } + + animations[a].frame_poses[frame][i].rotation.y = poses[i].channeloffset[4]; + + if (poses[i].mask & 0x10) + { + animations[a].frame_poses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; + dcounter++; + } + + animations[a].frame_poses[frame][i].rotation.z = poses[i].channeloffset[5]; + + if (poses[i].mask & 0x20) + { + animations[a].frame_poses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; + dcounter++; + } + + animations[a].frame_poses[frame][i].rotation.w = poses[i].channeloffset[6]; + + if (poses[i].mask & 0x40) + { + animations[a].frame_poses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; + dcounter++; + } + + animations[a].frame_poses[frame][i].scale.x = poses[i].channeloffset[7]; + + if (poses[i].mask & 0x80) + { + animations[a].frame_poses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; + dcounter++; + } + + animations[a].frame_poses[frame][i].scale.y = poses[i].channeloffset[8]; + + if (poses[i].mask & 0x100) + { + animations[a].frame_poses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; + dcounter++; + } + + animations[a].frame_poses[frame][i].scale.z = poses[i].channeloffset[9]; + + if (poses[i].mask & 0x200) + { + animations[a].frame_poses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; + dcounter++; + } + + animations[a].frame_poses[frame][i].rotation = rf_quaternion_normalize(animations[a].frame_poses[frame][i].rotation); + } + } + + // Build frameposes + for (rf_int frame = 0; frame < anim[a].num_frames; frame++) + { + for (rf_int i = 0; i < animations[a].bone_count; i++) + { + if (animations[a].bones[i].parent >= 0) + { + animations[a].frame_poses[frame][i].rotation = rf_quaternion_mul(animations[a].frame_poses[frame][animations[a].bones[i].parent].rotation, animations[a].frame_poses[frame][i].rotation); + animations[a].frame_poses[frame][i].translation = rf_vec3_rotate_by_quaternion(animations[a].frame_poses[frame][i].translation, animations[a].frame_poses[frame][animations[a].bones[i].parent].rotation); + animations[a].frame_poses[frame][i].translation = rf_vec3_add(animations[a].frame_poses[frame][i].translation, animations[a].frame_poses[frame][animations[a].bones[i].parent].translation); + animations[a].frame_poses[frame][i].scale = rf_vec3_mul_v(animations[a].frame_poses[frame][i].scale, animations[a].frame_poses[frame][animations[a].bones[i].parent].scale); + } + } + } + } + + RF_FREE(temp_allocator, framedata); + RF_FREE(temp_allocator, poses); + RF_FREE(temp_allocator, anim); + + return result; +} + +// Update model animated vertex data (positions and normals) for a given frame +RF_API void rf_update_model_animation(rf_model model, rf_model_animation anim, int frame) +{ + if ((anim.frame_count > 0) && (anim.bones != NULL) && (anim.frame_poses != NULL)) + { + return; + } + + if (frame >= anim.frame_count) + { + frame = frame%anim.frame_count; + } + + for (rf_int m = 0; m < model.mesh_count; m++) + { + rf_vec3 anim_vertex = {0}; + rf_vec3 anim_normal = {0}; + + rf_vec3 in_translation = {0}; + rf_quaternion in_rotation = {0}; + rf_vec3 in_scale = {0}; + + rf_vec3 out_translation = {0}; + rf_quaternion out_rotation = {0}; + rf_vec3 out_scale = {0}; + + int vertex_pos_counter = 0; + int bone_counter = 0; + int bone_id = 0; + + for (rf_int i = 0; i < model.meshes[m].vertex_count; i++) + { + bone_id = model.meshes[m].bone_ids[bone_counter]; + in_translation = model.bind_pose[bone_id].translation; + in_rotation = model.bind_pose[bone_id].rotation; + in_scale = model.bind_pose[bone_id].scale; + out_translation = anim.frame_poses[frame][bone_id].translation; + out_rotation = anim.frame_poses[frame][bone_id].rotation; + out_scale = anim.frame_poses[frame][bone_id].scale; + + // Vertices processing + // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.anim_vertices (animated vertex position) + anim_vertex = (rf_vec3){model.meshes[m].vertices[vertex_pos_counter], model.meshes[m].vertices[vertex_pos_counter + 1], model.meshes[m].vertices[vertex_pos_counter + 2] }; + anim_vertex = rf_vec3_mul_v(anim_vertex, out_scale); + anim_vertex = rf_vec3_sub(anim_vertex, in_translation); + anim_vertex = rf_vec3_rotate_by_quaternion(anim_vertex, rf_quaternion_mul(out_rotation, rf_quaternion_invert(in_rotation))); + anim_vertex = rf_vec3_add(anim_vertex, out_translation); + model.meshes[m].anim_vertices[vertex_pos_counter] = anim_vertex.x; + model.meshes[m].anim_vertices[vertex_pos_counter + 1] = anim_vertex.y; + model.meshes[m].anim_vertices[vertex_pos_counter + 2] = anim_vertex.z; + + // Normals processing + // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) + anim_normal = (rf_vec3){model.meshes[m].normals[vertex_pos_counter], model.meshes[m].normals[vertex_pos_counter + 1], model.meshes[m].normals[vertex_pos_counter + 2] }; + anim_normal = rf_vec3_rotate_by_quaternion(anim_normal, rf_quaternion_mul(out_rotation, rf_quaternion_invert(in_rotation))); + model.meshes[m].anim_normals[vertex_pos_counter] = anim_normal.x; + model.meshes[m].anim_normals[vertex_pos_counter + 1] = anim_normal.y; + model.meshes[m].anim_normals[vertex_pos_counter + 2] = anim_normal.z; + vertex_pos_counter += 3; + + bone_counter += 4; + } + + // Upload new vertex data to GPU for model drawing + rf_gfx_update_buffer(model.meshes[m].vbo_id[0], model.meshes[m].anim_vertices, model.meshes[m].vertex_count * 3 * sizeof(float)); // Update vertex position + rf_gfx_update_buffer(model.meshes[m].vbo_id[2], model.meshes[m].anim_vertices, model.meshes[m].vertex_count * 3 * sizeof(float)); // Update vertex normals + } +} + +// Check model animation skeleton match. Only number of bones and parent connections are checked +RF_API bool rf_is_model_animation_valid(rf_model model, rf_model_animation anim) +{ + int result = true; + + if (model.bone_count != anim.bone_count) result = false; + else + { + for (rf_int i = 0; i < model.bone_count; i++) + { + if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; } + } + } + + return result; +} + +// Unload animation data +RF_API void rf_unload_model_animation(rf_model_animation anim, rf_allocator allocator) +{ + for (rf_int i = 0; i < anim.frame_count; i++) RF_FREE(allocator, anim.frame_poses[i]); + + RF_FREE(allocator, anim.bones); + RF_FREE(allocator, anim.frame_poses); +} +#pragma endregion + +#pragma region mesh generation + +RF_API rf_mesh rf_gen_mesh_cube(float width, float height, float length, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + #define rf_custom_mesh_gen_cube //Todo: Investigate this macro + /* + Platonic solids: + par_shapes_mesh* par_shapes_create_tetrahedron(); // 4 sides polyhedron (pyramid) + par_shapes_mesh* par_shapes_create_cube(); // 6 sides polyhedron (cube) + par_shapes_mesh* par_shapes_create_octahedron(); // 8 sides polyhedron (dyamond) + par_shapes_mesh* par_shapes_create_dodecahedron(); // 12 sides polyhedron + par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron + */ + + // Platonic solid generation: cube (6 sides) + // NOTE: No normals/texcoords generated by default + //RF_SET_PARSHAPES_ALLOCATOR(temp_allocator); + { + par_shapes_mesh* cube = par_shapes_create_cube(); + cube->tcoords = PAR_MALLOC(float, 2 * cube->npoints); + + for (rf_int i = 0; i < 2 * cube->npoints; i++) + { + cube->tcoords[i] = 0.0f; + } + + par_shapes_scale(cube, width, height, length); + par_shapes_translate(cube, -width / 2, 0.0f, -length / 2); + par_shapes_compute_normals(cube); + + mesh.vertices = (float*) RF_ALLOC(allocator, cube->ntriangles * 3 * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, cube->ntriangles * 3 * 2 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, cube->ntriangles * 3 * 3 * sizeof(float)); + + mesh.vertex_count = cube->ntriangles * 3; + mesh.triangle_count = cube->ntriangles; + + for (rf_int k = 0; k < mesh.vertex_count; k++) + { + mesh.vertices[k * 3] = cube->points[cube->triangles[k] * 3]; + mesh.vertices[k * 3 + 1] = cube->points[cube->triangles[k] * 3 + 1]; + mesh.vertices[k * 3 + 2] = cube->points[cube->triangles[k] * 3 + 2]; + + mesh.normals[k * 3] = cube->normals[cube->triangles[k] * 3]; + mesh.normals[k * 3 + 1] = cube->normals[cube->triangles[k] * 3 + 1]; + mesh.normals[k * 3 + 2] = cube->normals[cube->triangles[k] * 3 + 2]; + + mesh.texcoords[k * 2] = cube->tcoords[cube->triangles[k] * 2]; + mesh.texcoords[k * 2 + 1] = cube->tcoords[cube->triangles[k] * 2 + 1]; + } + + par_shapes_free_mesh(cube); + } + //RF_SET_PARSHAPES_ALLOCATOR(RF_NULL_ALLOCATOR); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate polygonal mesh +RF_API rf_mesh rf_gen_mesh_poly(int sides, float radius, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + int vertex_count = sides * 3; + + // Vertices definition + rf_vec3* vertices = (rf_vec3*) RF_ALLOC(temp_allocator, vertex_count * sizeof(rf_vec3)); + for (rf_int i = 0, v = 0; i < 360; i += 360/sides, v += 3) + { + vertices[v ] = (rf_vec3){ 0.0f, 0.0f, 0.0f }; + vertices[v + 1] = (rf_vec3) { sinf(RF_DEG2RAD * i) * radius, 0.0f, cosf(RF_DEG2RAD * i) * radius }; + vertices[v + 2] = (rf_vec3) { sinf(RF_DEG2RAD * (i + 360 / sides)) * radius, 0.0f, cosf(RF_DEG2RAD * (i + 360 / sides)) * radius }; + } + + // Normals definition + rf_vec3* normals = (rf_vec3*) RF_ALLOC(temp_allocator, vertex_count * sizeof(rf_vec3)); + for (rf_int n = 0; n < vertex_count; n++) normals[n] = (rf_vec3){0.0f, 1.0f, 0.0f }; // rf_vec3.up; + + // TexCoords definition + rf_vec2* texcoords = (rf_vec2*) RF_ALLOC(temp_allocator, vertex_count * sizeof(rf_vec2)); + for (rf_int n = 0; n < vertex_count; n++) texcoords[n] = (rf_vec2) {0.0f, 0.0f }; + + mesh.vertex_count = vertex_count; + mesh.triangle_count = sides; + mesh.vertices = (float*) RF_ALLOC(allocator, mesh.vertex_count * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, mesh.vertex_count * 2 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, mesh.vertex_count * 3 * sizeof(float)); + + // rf_mesh vertices position array + for (rf_int i = 0; i < mesh.vertex_count; i++) + { + mesh.vertices[3*i] = vertices[i].x; + mesh.vertices[3*i + 1] = vertices[i].y; + mesh.vertices[3*i + 2] = vertices[i].z; + } + + // rf_mesh texcoords array + for (rf_int i = 0; i < mesh.vertex_count; i++) + { + mesh.texcoords[2*i] = texcoords[i].x; + mesh.texcoords[2*i + 1] = texcoords[i].y; + } + + // rf_mesh normals array + for (rf_int i = 0; i < mesh.vertex_count; i++) + { + mesh.normals[3*i] = normals[i].x; + mesh.normals[3*i + 1] = normals[i].y; + mesh.normals[3*i + 2] = normals[i].z; + } + + RF_FREE(temp_allocator, vertices); + RF_FREE(temp_allocator, normals); + RF_FREE(temp_allocator, texcoords); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate plane mesh (with subdivisions) +RF_API rf_mesh rf_gen_mesh_plane(float width, float length, int res_x, int res_z, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + #define rf_custom_mesh_gen_plane //Todo: Investigate this macro + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + { + par_shapes_mesh* plane = par_shapes_create_plane(res_x, res_z); // No normals/texcoords generated!!! + par_shapes_scale(plane, width, length, 1.0f); + + float axis[] = { 1, 0, 0 }; + par_shapes_rotate(plane, -RF_PI / 2.0f, axis); + par_shapes_translate(plane, -width / 2, 0.0f, length / 2); + + mesh.vertices = (float*) RF_ALLOC(allocator, plane->ntriangles * 3 * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, plane->ntriangles * 3 * 2 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, plane->ntriangles * 3 * 3 * sizeof(float)); + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + mesh.vertex_count = plane->ntriangles * 3; + mesh.triangle_count = plane->ntriangles; + + for (rf_int k = 0; k < mesh.vertex_count; k++) + { + mesh.vertices[k * 3 ] = plane->points[plane->triangles[k] * 3 ]; + mesh.vertices[k * 3 + 1] = plane->points[plane->triangles[k] * 3 + 1]; + mesh.vertices[k * 3 + 2] = plane->points[plane->triangles[k] * 3 + 2]; + + mesh.normals[k * 3 ] = plane->normals[plane->triangles[k] * 3 ]; + mesh.normals[k * 3 + 1] = plane->normals[plane->triangles[k] * 3 + 1]; + mesh.normals[k * 3 + 2] = plane->normals[plane->triangles[k] * 3 + 2]; + + mesh.texcoords[k * 2 ] = plane->tcoords[plane->triangles[k] * 2 ]; + mesh.texcoords[k * 2 + 1] = plane->tcoords[plane->triangles[k] * 2 + 1]; + } + + par_shapes_free_mesh(plane); + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate sphere mesh (standard sphere) +RF_API rf_mesh rf_gen_mesh_sphere(float radius, int rings, int slices, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + { + par_shapes_mesh* sphere = par_shapes_create_parametric_sphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally + + mesh.vertices = (float*) RF_ALLOC(allocator, sphere->ntriangles * 3 * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, sphere->ntriangles * 3 * 2 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, sphere->ntriangles * 3 * 3 * sizeof(float)); + + mesh.vertex_count = sphere->ntriangles * 3; + mesh.triangle_count = sphere->ntriangles; + + for (rf_int k = 0; k < mesh.vertex_count; k++) + { + mesh.vertices[k * 3 ] = sphere->points[sphere->triangles[k] * 3]; + mesh.vertices[k * 3 + 1] = sphere->points[sphere->triangles[k] * 3 + 1]; + mesh.vertices[k * 3 + 2] = sphere->points[sphere->triangles[k] * 3 + 2]; + + mesh.normals[k * 3 ] = sphere->normals[sphere->triangles[k] * 3]; + mesh.normals[k * 3 + 1] = sphere->normals[sphere->triangles[k] * 3 + 1]; + mesh.normals[k * 3 + 2] = sphere->normals[sphere->triangles[k] * 3 + 2]; + + mesh.texcoords[k * 2 ] = sphere->tcoords[sphere->triangles[k] * 2]; + mesh.texcoords[k * 2 + 1] = sphere->tcoords[sphere->triangles[k] * 2 + 1]; + } + + par_shapes_free_mesh(sphere); + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate hemi-sphere mesh (half sphere, no bottom cap) +RF_API rf_mesh rf_gen_mesh_hemi_sphere(float radius, int rings, int slices, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + { + par_shapes_mesh* sphere = par_shapes_create_hemisphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally + + mesh.vertices = (float*) RF_ALLOC(allocator, sphere->ntriangles * 3 * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, sphere->ntriangles * 3 * 2 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, sphere->ntriangles * 3 * 3 * sizeof(float)); + + mesh.vertex_count = sphere->ntriangles * 3; + mesh.triangle_count = sphere->ntriangles; + + for (rf_int k = 0; k < mesh.vertex_count; k++) + { + mesh.vertices[k * 3 ] = sphere->points[sphere->triangles[k] * 3]; + mesh.vertices[k * 3 + 1] = sphere->points[sphere->triangles[k] * 3 + 1]; + mesh.vertices[k * 3 + 2] = sphere->points[sphere->triangles[k] * 3 + 2]; + + mesh.normals[k * 3 ] = sphere->normals[sphere->triangles[k] * 3]; + mesh.normals[k * 3 + 1] = sphere->normals[sphere->triangles[k] * 3 + 1]; + mesh.normals[k * 3 + 2] = sphere->normals[sphere->triangles[k] * 3 + 2]; + + mesh.texcoords[k * 2 ] = sphere->tcoords[sphere->triangles[k] * 2]; + mesh.texcoords[k * 2 + 1] = sphere->tcoords[sphere->triangles[k] * 2 + 1]; + } + + par_shapes_free_mesh(sphere); + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate cylinder mesh +RF_API rf_mesh rf_gen_mesh_cylinder(float radius, float height, int slices, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + { + // Instance a cylinder that sits on the Z=0 plane using the given tessellation + // levels across the UV domain. Think of "slices" like a number of pizza + // slices, and "stacks" like a number of stacked rings. + // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale + par_shapes_mesh* cylinder = par_shapes_create_cylinder(slices, 8); + par_shapes_scale(cylinder, radius, radius, height); + float axis[] = { 1, 0, 0 }; + par_shapes_rotate(cylinder, -RF_PI / 2.0f, axis); + + // Generate an orientable disk shape (top cap) + float center[] = { 0, 0, 0 }; + float normal[] = { 0, 0, 1 }; + float normal_minus_1[] = { 0, 0, -1 }; + par_shapes_mesh* cap_top = par_shapes_create_disk(radius, slices, center, normal); + cap_top->tcoords = PAR_MALLOC(float, 2*cap_top->npoints); + for (rf_int i = 0; i < 2 * cap_top->npoints; i++) + { + cap_top->tcoords[i] = 0.0f; + } + + par_shapes_rotate(cap_top, -RF_PI / 2.0f, axis); + par_shapes_translate(cap_top, 0, height, 0); + + // Generate an orientable disk shape (bottom cap) + par_shapes_mesh* cap_bottom = par_shapes_create_disk(radius, slices, center, normal_minus_1); + cap_bottom->tcoords = PAR_MALLOC(float, 2*cap_bottom->npoints); + for (rf_int i = 0; i < 2*cap_bottom->npoints; i++) cap_bottom->tcoords[i] = 0.95f; + par_shapes_rotate(cap_bottom, RF_PI / 2.0f, axis); + + par_shapes_merge_and_free(cylinder, cap_top); + par_shapes_merge_and_free(cylinder, cap_bottom); + + mesh.vertices = (float*) RF_ALLOC(allocator, cylinder->ntriangles * 3 * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, cylinder->ntriangles * 3 * 2 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, cylinder->ntriangles * 3 * 3 * sizeof(float)); + + mesh.vertex_count = cylinder->ntriangles * 3; + mesh.triangle_count = cylinder->ntriangles; + + for (rf_int k = 0; k < mesh.vertex_count; k++) + { + mesh.vertices[k * 3 ] = cylinder->points[cylinder->triangles[k] * 3 ]; + mesh.vertices[k * 3 + 1] = cylinder->points[cylinder->triangles[k] * 3 + 1]; + mesh.vertices[k * 3 + 2] = cylinder->points[cylinder->triangles[k] * 3 + 2]; + + mesh.normals[k * 3 ] = cylinder->normals[cylinder->triangles[k] * 3 ]; + mesh.normals[k * 3 + 1] = cylinder->normals[cylinder->triangles[k] * 3 + 1]; + mesh.normals[k * 3 + 2] = cylinder->normals[cylinder->triangles[k] * 3 + 2]; + + mesh.texcoords[k * 2 ] = cylinder->tcoords[cylinder->triangles[k] * 2 ]; + mesh.texcoords[k * 2 + 1] = cylinder->tcoords[cylinder->triangles[k] * 2 + 1]; + } + + par_shapes_free_mesh(cylinder); + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate torus mesh +RF_API rf_mesh rf_gen_mesh_torus(float radius, float size, int rad_seg, int sides, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + if (radius > 1.0f) radius = 1.0f; + else if (radius < 0.1f) radius = 0.1f; + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + { + // Create a donut that sits on the Z=0 plane with the specified inner radius + // The outer radius can be controlled with par_shapes_scale + par_shapes_mesh* torus = par_shapes_create_torus(rad_seg, sides, radius); + par_shapes_scale(torus, size/2, size/2, size/2); + + mesh.vertices = (float*) RF_ALLOC(allocator, torus->ntriangles * 3 * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, torus->ntriangles * 3 * 2 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, torus->ntriangles * 3 * 3 * sizeof(float)); + + mesh.vertex_count = torus->ntriangles * 3; + mesh.triangle_count = torus->ntriangles; + + for (rf_int k = 0; k < mesh.vertex_count; k++) + { + mesh.vertices[k * 3 ] = torus->points[torus->triangles[k] * 3 ]; + mesh.vertices[k * 3 + 1] = torus->points[torus->triangles[k] * 3 + 1]; + mesh.vertices[k * 3 + 2] = torus->points[torus->triangles[k] * 3 + 2]; + + mesh.normals[k * 3 ] = torus->normals[torus->triangles[k] * 3 ]; + mesh.normals[k * 3 + 1] = torus->normals[torus->triangles[k] * 3 + 1]; + mesh.normals[k * 3 + 2] = torus->normals[torus->triangles[k] * 3 + 2]; + + mesh.texcoords[k * 2 ] = torus->tcoords[torus->triangles[k] * 2 ]; + mesh.texcoords[k * 2 + 1] = torus->tcoords[torus->triangles[k] * 2 + 1]; + } + + par_shapes_free_mesh(torus); + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate trefoil knot mesh +RF_API rf_mesh rf_gen_mesh_knot(float radius, float size, int rad_seg, int sides, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + if (radius > 3.0f) radius = 3.0f; + else if (radius < 0.5f) radius = 0.5f; + + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(temp_allocator); + { + par_shapes_mesh* knot = par_shapes_create_trefoil_knot(rad_seg, sides, radius); + par_shapes_scale(knot, size, size, size); + + mesh.vertices = (float*) RF_ALLOC(allocator, knot->ntriangles * 3 * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, knot->ntriangles * 3 * 2 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, knot->ntriangles * 3 * 3 * sizeof(float)); + + mesh.vertex_count = knot->ntriangles * 3; + mesh.triangle_count = knot->ntriangles; + + for (rf_int k = 0; k < mesh.vertex_count; k++) + { + mesh.vertices[k * 3 ] = knot->points[knot->triangles[k] * 3 ]; + mesh.vertices[k * 3 + 1] = knot->points[knot->triangles[k] * 3 + 1]; + mesh.vertices[k * 3 + 2] = knot->points[knot->triangles[k] * 3 + 2]; + + mesh.normals[k * 3 ] = knot->normals[knot->triangles[k] * 3 ]; + mesh.normals[k * 3 + 1] = knot->normals[knot->triangles[k] * 3 + 1]; + mesh.normals[k * 3 + 2] = knot->normals[knot->triangles[k] * 3 + 2]; + + mesh.texcoords[k * 2 ] = knot->tcoords[knot->triangles[k] * 2 ]; + mesh.texcoords[k * 2 + 1] = knot->tcoords[knot->triangles[k] * 2 + 1]; + } + + par_shapes_free_mesh(knot); + } + RF_SET_GLOBAL_DEPENDENCIES_ALLOCATOR(RF_NULL_ALLOCATOR); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate a mesh from heightmap +// NOTE: Vertex data is uploaded to GPU +RF_API rf_mesh rf_gen_mesh_heightmap(rf_image heightmap, rf_vec3 size, rf_allocator allocator, rf_allocator temp_allocator) +{ +#define RF_GRAY_VALUE(c) ((c.r+c.g+c.b)/3) + + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + int map_x = heightmap.width; + int map_z = heightmap.height; + + rf_color* pixels = rf_image_pixels_to_rgba32(heightmap, temp_allocator); + + // NOTE: One vertex per pixel + mesh.triangle_count = (map_x - 1) * (map_z - 1) * 2; // One quad every four pixels + + mesh.vertex_count = mesh.triangle_count * 3; + + mesh.vertices = (float*) RF_ALLOC(allocator, mesh.vertex_count * 3 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, mesh.vertex_count * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, mesh.vertex_count * 2 * sizeof(float)); + mesh.colors = NULL; + + int vertex_pos_counter = 0; // Used to count vertices float by float + int vertex_texcoord_counter = 0; // Used to count texcoords float by float + int n_counter = 0; // Used to count normals float by float + int tris_counter = 0; + + rf_vec3 scale_factor = { size.x / map_x, size.y / 255.0f, size.z / map_z }; + + for (rf_int z = 0; z < map_z-1; z++) + { + for (rf_int x = 0; x < map_x-1; x++) + { + // Fill vertices array with data + //---------------------------------------------------------- + + // one triangle - 3 vertex + mesh.vertices[vertex_pos_counter ] = (float) x * scale_factor.x; + mesh.vertices[vertex_pos_counter + 1] = (float) RF_GRAY_VALUE(pixels[x + z * map_x]) * scale_factor.y; + mesh.vertices[vertex_pos_counter + 2] = (float) z * scale_factor.z; + + mesh.vertices[vertex_pos_counter + 3] = (float) x * scale_factor.x; + mesh.vertices[vertex_pos_counter + 4] = (float) RF_GRAY_VALUE(pixels[x + (z + 1) * map_x]) * scale_factor.y; + mesh.vertices[vertex_pos_counter + 5] = (float) (z + 1) * scale_factor.z; + + mesh.vertices[vertex_pos_counter + 6] = (float)(x + 1) * scale_factor.x; + mesh.vertices[vertex_pos_counter + 7] = (float)RF_GRAY_VALUE(pixels[(x + 1) + z * map_x]) * scale_factor.y; + mesh.vertices[vertex_pos_counter + 8] = (float)z * scale_factor.z; + + // another triangle - 3 vertex + mesh.vertices[vertex_pos_counter + 9 ] = mesh.vertices[vertex_pos_counter + 6]; + mesh.vertices[vertex_pos_counter + 10] = mesh.vertices[vertex_pos_counter + 7]; + mesh.vertices[vertex_pos_counter + 11] = mesh.vertices[vertex_pos_counter + 8]; + + mesh.vertices[vertex_pos_counter + 12] = mesh.vertices[vertex_pos_counter + 3]; + mesh.vertices[vertex_pos_counter + 13] = mesh.vertices[vertex_pos_counter + 4]; + mesh.vertices[vertex_pos_counter + 14] = mesh.vertices[vertex_pos_counter + 5]; + + mesh.vertices[vertex_pos_counter + 15] = (float)(x + 1) * scale_factor.x; + mesh.vertices[vertex_pos_counter + 16] = (float)RF_GRAY_VALUE(pixels[(x + 1) + (z + 1) * map_x]) * scale_factor.y; + mesh.vertices[vertex_pos_counter + 17] = (float)(z + 1) * scale_factor.z; + vertex_pos_counter += 18; // 6 vertex, 18 floats + + // Fill texcoords array with data + //-------------------------------------------------------------- + mesh.texcoords[vertex_texcoord_counter ] = (float)x / (map_x - 1); + mesh.texcoords[vertex_texcoord_counter + 1] = (float)z / (map_z - 1); + + mesh.texcoords[vertex_texcoord_counter + 2] = (float)x / (map_x - 1); + mesh.texcoords[vertex_texcoord_counter + 3] = (float)(z + 1) / (map_z - 1); + + mesh.texcoords[vertex_texcoord_counter + 4] = (float)(x + 1) / (map_x - 1); + mesh.texcoords[vertex_texcoord_counter + 5] = (float)z / (map_z - 1); + + mesh.texcoords[vertex_texcoord_counter + 6] = mesh.texcoords[vertex_texcoord_counter + 4]; + mesh.texcoords[vertex_texcoord_counter + 7] = mesh.texcoords[vertex_texcoord_counter + 5]; + + mesh.texcoords[vertex_texcoord_counter + 8] = mesh.texcoords[vertex_texcoord_counter + 2]; + mesh.texcoords[vertex_texcoord_counter + 9] = mesh.texcoords[vertex_texcoord_counter + 3]; + + mesh.texcoords[vertex_texcoord_counter + 10] = (float)(x + 1) / (map_x - 1); + mesh.texcoords[vertex_texcoord_counter + 11] = (float)(z + 1) / (map_z - 1); + + vertex_texcoord_counter += 12; // 6 texcoords, 12 floats + + // Fill normals array with data + //-------------------------------------------------------------- + for (rf_int i = 0; i < 18; i += 3) + { + mesh.normals[n_counter + i ] = 0.0f; + mesh.normals[n_counter + i + 1] = 1.0f; + mesh.normals[n_counter + i + 2] = 0.0f; + } + + // TODO: Calculate normals in an efficient way + + n_counter += 18; // 6 vertex, 18 floats + tris_counter += 2; + } + } + + RF_FREE(temp_allocator, pixels); + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +// Generate a cubes mesh from pixel data +// NOTE: Vertex data is uploaded to GPU +RF_API rf_mesh rf_gen_mesh_cubicmap(rf_image cubicmap, rf_vec3 cube_size, rf_allocator allocator, rf_allocator temp_allocator) +{ + rf_mesh mesh = {0}; + mesh.vbo_id = (unsigned int*) RF_ALLOC(allocator, RF_MAX_MESH_VBO * sizeof(unsigned int)); + memset(mesh.vbo_id, 0, RF_MAX_MESH_VBO * sizeof(unsigned int)); + + rf_color* cubicmap_pixels = rf_image_pixels_to_rgba32(cubicmap, temp_allocator); + + int map_width = cubicmap.width; + int map_height = cubicmap.height; + + // NOTE: Max possible number of triangles numCubes*(12 triangles by cube) + int maxTriangles = cubicmap.width*cubicmap.height*12; + + int vertex_pos_counter = 0; // Used to count vertices + int vertex_texcoord_counter = 0; // Used to count texcoords + int n_counter = 0; // Used to count normals + + float w = cube_size.x; + float h = cube_size.z; + float h2 = cube_size.y; + + rf_vec3* map_vertices = (rf_vec3*) RF_ALLOC(temp_allocator, maxTriangles * 3 * sizeof(rf_vec3)); + rf_vec2 *map_texcoords = (rf_vec2*) RF_ALLOC(temp_allocator, maxTriangles * 3 * sizeof(rf_vec2)); + rf_vec3* map_normals = (rf_vec3*) RF_ALLOC(temp_allocator, maxTriangles * 3 * sizeof(rf_vec3)); + + // Define the 6 normals of the cube, we will combine them accordingly later... + rf_vec3 n1 = { 1.0f, 0.0f, 0.0f }; + rf_vec3 n2 = { -1.0f, 0.0f, 0.0f }; + rf_vec3 n3 = { 0.0f, 1.0f, 0.0f }; + rf_vec3 n4 = { 0.0f, -1.0f, 0.0f }; + rf_vec3 n5 = { 0.0f, 0.0f, 1.0f }; + rf_vec3 n6 = { 0.0f, 0.0f, -1.0f }; + + // NOTE: We use texture rectangles to define different textures for top-bottom-front-back-right-left (6) + typedef struct rf_recf rf_recf; + struct rf_recf + { + float x; + float y; + float width; + float height; + }; + + rf_recf right_tex_uv = { 0.0f, 0.0f, 0.5f, 0.5f }; + rf_recf left_tex_uv = { 0.5f, 0.0f, 0.5f, 0.5f }; + rf_recf front_tex_uv = { 0.0f, 0.0f, 0.5f, 0.5f }; + rf_recf back_tex_uv = { 0.5f, 0.0f, 0.5f, 0.5f }; + rf_recf top_tex_uv = { 0.0f, 0.5f, 0.5f, 0.5f }; + rf_recf bottom_tex_uv = { 0.5f, 0.5f, 0.5f, 0.5f }; + + for (rf_int z = 0; z < map_height; ++z) + { + for (rf_int x = 0; x < map_width; ++x) + { + // Define the 8 vertex of the cube, we will combine them accordingly later... + rf_vec3 v1 = {w * (x - 0.5f), h2, h * (z - 0.5f) }; + rf_vec3 v2 = {w * (x - 0.5f), h2, h * (z + 0.5f) }; + rf_vec3 v3 = {w * (x + 0.5f), h2, h * (z + 0.5f) }; + rf_vec3 v4 = {w * (x + 0.5f), h2, h * (z - 0.5f) }; + rf_vec3 v5 = {w * (x + 0.5f), 0, h * (z - 0.5f) }; + rf_vec3 v6 = {w * (x - 0.5f), 0, h * (z - 0.5f) }; + rf_vec3 v7 = {w * (x - 0.5f), 0, h * (z + 0.5f) }; + rf_vec3 v8 = {w * (x + 0.5f), 0, h * (z + 0.5f) }; + + // We check pixel color to be RF_WHITE, we will full cubes + if ((cubicmap_pixels[z*cubicmap.width + x].r == 255) && + (cubicmap_pixels[z*cubicmap.width + x].g == 255) && + (cubicmap_pixels[z*cubicmap.width + x].b == 255)) + { + // Define triangles (Checking Collateral Cubes!) + //---------------------------------------------- + + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + map_vertices[vertex_pos_counter] = v1; + map_vertices[vertex_pos_counter + 1] = v2; + map_vertices[vertex_pos_counter + 2] = v3; + map_vertices[vertex_pos_counter + 3] = v1; + map_vertices[vertex_pos_counter + 4] = v3; + map_vertices[vertex_pos_counter + 5] = v4; + vertex_pos_counter += 6; + + map_normals[n_counter] = n3; + map_normals[n_counter + 1] = n3; + map_normals[n_counter + 2] = n3; + map_normals[n_counter + 3] = n3; + map_normals[n_counter + 4] = n3; + map_normals[n_counter + 5] = n3; + n_counter += 6; + + map_texcoords[vertex_texcoord_counter] = (rf_vec2){top_tex_uv.x, top_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 1] = (rf_vec2){top_tex_uv.x, top_tex_uv.y + top_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 2] = (rf_vec2){top_tex_uv.x + top_tex_uv.width, top_tex_uv.y + top_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 3] = (rf_vec2){top_tex_uv.x, top_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 4] = (rf_vec2){top_tex_uv.x + top_tex_uv.width, top_tex_uv.y + top_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 5] = (rf_vec2){top_tex_uv.x + top_tex_uv.width, top_tex_uv.y }; + vertex_texcoord_counter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + map_vertices[vertex_pos_counter] = v6; + map_vertices[vertex_pos_counter + 1] = v8; + map_vertices[vertex_pos_counter + 2] = v7; + map_vertices[vertex_pos_counter + 3] = v6; + map_vertices[vertex_pos_counter + 4] = v5; + map_vertices[vertex_pos_counter + 5] = v8; + vertex_pos_counter += 6; + + map_normals[n_counter] = n4; + map_normals[n_counter + 1] = n4; + map_normals[n_counter + 2] = n4; + map_normals[n_counter + 3] = n4; + map_normals[n_counter + 4] = n4; + map_normals[n_counter + 5] = n4; + n_counter += 6; + + map_texcoords[vertex_texcoord_counter] = (rf_vec2){bottom_tex_uv.x + bottom_tex_uv.width, bottom_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 1] = (rf_vec2){bottom_tex_uv.x, bottom_tex_uv.y + bottom_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 2] = (rf_vec2){bottom_tex_uv.x + bottom_tex_uv.width, bottom_tex_uv.y + bottom_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 3] = (rf_vec2){bottom_tex_uv.x + bottom_tex_uv.width, bottom_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 4] = (rf_vec2){bottom_tex_uv.x, bottom_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 5] = (rf_vec2){bottom_tex_uv.x, bottom_tex_uv.y + bottom_tex_uv.height }; + vertex_texcoord_counter += 6; + + if (((z < cubicmap.height - 1) && + (cubicmap_pixels[(z + 1)*cubicmap.width + x].r == 0) && + (cubicmap_pixels[(z + 1)*cubicmap.width + x].g == 0) && + (cubicmap_pixels[(z + 1)*cubicmap.width + x].b == 0)) || (z == cubicmap.height - 1)) + { + // Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8 + // NOTE: Collateral occluded faces are not generated + map_vertices[vertex_pos_counter] = v2; + map_vertices[vertex_pos_counter + 1] = v7; + map_vertices[vertex_pos_counter + 2] = v3; + map_vertices[vertex_pos_counter + 3] = v3; + map_vertices[vertex_pos_counter + 4] = v7; + map_vertices[vertex_pos_counter + 5] = v8; + vertex_pos_counter += 6; + + map_normals[n_counter] = n6; + map_normals[n_counter + 1] = n6; + map_normals[n_counter + 2] = n6; + map_normals[n_counter + 3] = n6; + map_normals[n_counter + 4] = n6; + map_normals[n_counter + 5] = n6; + n_counter += 6; + + map_texcoords[vertex_texcoord_counter] = (rf_vec2){front_tex_uv.x, front_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 1] = (rf_vec2){front_tex_uv.x, front_tex_uv.y + front_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 2] = (rf_vec2){front_tex_uv.x + front_tex_uv.width, front_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 3] = (rf_vec2){front_tex_uv.x + front_tex_uv.width, front_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 4] = (rf_vec2){front_tex_uv.x, front_tex_uv.y + front_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 5] = (rf_vec2){front_tex_uv.x + front_tex_uv.width, front_tex_uv.y + front_tex_uv.height }; + vertex_texcoord_counter += 6; + } + + if (((z > 0) && + (cubicmap_pixels[(z - 1)*cubicmap.width + x].r == 0) && + (cubicmap_pixels[(z - 1)*cubicmap.width + x].g == 0) && + (cubicmap_pixels[(z - 1)*cubicmap.width + x].b == 0)) || (z == 0)) + { + // Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5 + // NOTE: Collateral occluded faces are not generated + map_vertices[vertex_pos_counter] = v1; + map_vertices[vertex_pos_counter + 1] = v5; + map_vertices[vertex_pos_counter + 2] = v6; + map_vertices[vertex_pos_counter + 3] = v1; + map_vertices[vertex_pos_counter + 4] = v4; + map_vertices[vertex_pos_counter + 5] = v5; + vertex_pos_counter += 6; + + map_normals[n_counter] = n5; + map_normals[n_counter + 1] = n5; + map_normals[n_counter + 2] = n5; + map_normals[n_counter + 3] = n5; + map_normals[n_counter + 4] = n5; + map_normals[n_counter + 5] = n5; + n_counter += 6; + + map_texcoords[vertex_texcoord_counter] = (rf_vec2){back_tex_uv.x + back_tex_uv.width, back_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 1] = (rf_vec2){back_tex_uv.x, back_tex_uv.y + back_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 2] = (rf_vec2){back_tex_uv.x + back_tex_uv.width, back_tex_uv.y + back_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 3] = (rf_vec2){back_tex_uv.x + back_tex_uv.width, back_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 4] = (rf_vec2){back_tex_uv.x, back_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 5] = (rf_vec2){back_tex_uv.x, back_tex_uv.y + back_tex_uv.height }; + vertex_texcoord_counter += 6; + } + + if (((x < cubicmap.width - 1) && + (cubicmap_pixels[z*cubicmap.width + (x + 1)].r == 0) && + (cubicmap_pixels[z*cubicmap.width + (x + 1)].g == 0) && + (cubicmap_pixels[z*cubicmap.width + (x + 1)].b == 0)) || (x == cubicmap.width - 1)) + { + // Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5 + // NOTE: Collateral occluded faces are not generated + map_vertices[vertex_pos_counter] = v3; + map_vertices[vertex_pos_counter + 1] = v8; + map_vertices[vertex_pos_counter + 2] = v4; + map_vertices[vertex_pos_counter + 3] = v4; + map_vertices[vertex_pos_counter + 4] = v8; + map_vertices[vertex_pos_counter + 5] = v5; + vertex_pos_counter += 6; + + map_normals[n_counter] = n1; + map_normals[n_counter + 1] = n1; + map_normals[n_counter + 2] = n1; + map_normals[n_counter + 3] = n1; + map_normals[n_counter + 4] = n1; + map_normals[n_counter + 5] = n1; + n_counter += 6; + + map_texcoords[vertex_texcoord_counter] = (rf_vec2){right_tex_uv.x, right_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 1] = (rf_vec2){right_tex_uv.x, right_tex_uv.y + right_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 2] = (rf_vec2){right_tex_uv.x + right_tex_uv.width, right_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 3] = (rf_vec2){right_tex_uv.x + right_tex_uv.width, right_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 4] = (rf_vec2){right_tex_uv.x, right_tex_uv.y + right_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 5] = (rf_vec2){right_tex_uv.x + right_tex_uv.width, right_tex_uv.y + right_tex_uv.height }; + vertex_texcoord_counter += 6; + } + + if (((x > 0) && + (cubicmap_pixels[z*cubicmap.width + (x - 1)].r == 0) && + (cubicmap_pixels[z*cubicmap.width + (x - 1)].g == 0) && + (cubicmap_pixels[z*cubicmap.width + (x - 1)].b == 0)) || (x == 0)) + { + // Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7 + // NOTE: Collateral occluded faces are not generated + map_vertices[vertex_pos_counter] = v1; + map_vertices[vertex_pos_counter + 1] = v7; + map_vertices[vertex_pos_counter + 2] = v2; + map_vertices[vertex_pos_counter + 3] = v1; + map_vertices[vertex_pos_counter + 4] = v6; + map_vertices[vertex_pos_counter + 5] = v7; + vertex_pos_counter += 6; + + map_normals[n_counter] = n2; + map_normals[n_counter + 1] = n2; + map_normals[n_counter + 2] = n2; + map_normals[n_counter + 3] = n2; + map_normals[n_counter + 4] = n2; + map_normals[n_counter + 5] = n2; + n_counter += 6; + + map_texcoords[vertex_texcoord_counter] = (rf_vec2){left_tex_uv.x, left_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 1] = (rf_vec2){left_tex_uv.x + left_tex_uv.width, left_tex_uv.y + left_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 2] = (rf_vec2){left_tex_uv.x + left_tex_uv.width, left_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 3] = (rf_vec2){left_tex_uv.x, left_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 4] = (rf_vec2){left_tex_uv.x, left_tex_uv.y + left_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 5] = (rf_vec2){left_tex_uv.x + left_tex_uv.width, left_tex_uv.y + left_tex_uv.height }; + vertex_texcoord_counter += 6; + } + } + // We check pixel color to be RF_BLACK, we will only draw floor and roof + else if ((cubicmap_pixels[z*cubicmap.width + x].r == 0) && + (cubicmap_pixels[z*cubicmap.width + x].g == 0) && + (cubicmap_pixels[z*cubicmap.width + x].b == 0)) + { + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + map_vertices[vertex_pos_counter] = v1; + map_vertices[vertex_pos_counter + 1] = v3; + map_vertices[vertex_pos_counter + 2] = v2; + map_vertices[vertex_pos_counter + 3] = v1; + map_vertices[vertex_pos_counter + 4] = v4; + map_vertices[vertex_pos_counter + 5] = v3; + vertex_pos_counter += 6; + + map_normals[n_counter] = n4; + map_normals[n_counter + 1] = n4; + map_normals[n_counter + 2] = n4; + map_normals[n_counter + 3] = n4; + map_normals[n_counter + 4] = n4; + map_normals[n_counter + 5] = n4; + n_counter += 6; + + map_texcoords[vertex_texcoord_counter] = (rf_vec2){top_tex_uv.x, top_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 1] = (rf_vec2){top_tex_uv.x + top_tex_uv.width, top_tex_uv.y + top_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 2] = (rf_vec2){top_tex_uv.x, top_tex_uv.y + top_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 3] = (rf_vec2){top_tex_uv.x, top_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 4] = (rf_vec2){top_tex_uv.x + top_tex_uv.width, top_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 5] = (rf_vec2){top_tex_uv.x + top_tex_uv.width, top_tex_uv.y + top_tex_uv.height }; + vertex_texcoord_counter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + map_vertices[vertex_pos_counter] = v6; + map_vertices[vertex_pos_counter + 1] = v7; + map_vertices[vertex_pos_counter + 2] = v8; + map_vertices[vertex_pos_counter + 3] = v6; + map_vertices[vertex_pos_counter + 4] = v8; + map_vertices[vertex_pos_counter + 5] = v5; + vertex_pos_counter += 6; + + map_normals[n_counter] = n3; + map_normals[n_counter + 1] = n3; + map_normals[n_counter + 2] = n3; + map_normals[n_counter + 3] = n3; + map_normals[n_counter + 4] = n3; + map_normals[n_counter + 5] = n3; + n_counter += 6; + + map_texcoords[vertex_texcoord_counter] = (rf_vec2){bottom_tex_uv.x + bottom_tex_uv.width, bottom_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 1] = (rf_vec2){bottom_tex_uv.x + bottom_tex_uv.width, bottom_tex_uv.y + bottom_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 2] = (rf_vec2){bottom_tex_uv.x, bottom_tex_uv.y + bottom_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 3] = (rf_vec2){bottom_tex_uv.x + bottom_tex_uv.width, bottom_tex_uv.y }; + map_texcoords[vertex_texcoord_counter + 4] = (rf_vec2){bottom_tex_uv.x, bottom_tex_uv.y + bottom_tex_uv.height }; + map_texcoords[vertex_texcoord_counter + 5] = (rf_vec2){bottom_tex_uv.x, bottom_tex_uv.y }; + vertex_texcoord_counter += 6; + } + } + } + + // Move data from map_vertices temp arays to vertices float array + mesh.vertex_count = vertex_pos_counter; + mesh.triangle_count = vertex_pos_counter/3; + + mesh.vertices = (float*) RF_ALLOC(allocator, mesh.vertex_count * 3 * sizeof(float)); + mesh.normals = (float*) RF_ALLOC(allocator, mesh.vertex_count * 3 * sizeof(float)); + mesh.texcoords = (float*) RF_ALLOC(allocator, mesh.vertex_count * 2 * sizeof(float)); + mesh.colors = NULL; + + int f_counter = 0; + + // Move vertices data + for (rf_int i = 0; i < vertex_pos_counter; i++) + { + mesh.vertices[f_counter] = map_vertices[i].x; + mesh.vertices[f_counter + 1] = map_vertices[i].y; + mesh.vertices[f_counter + 2] = map_vertices[i].z; + f_counter += 3; + } + + f_counter = 0; + + // Move normals data + for (rf_int i = 0; i < n_counter; i++) + { + mesh.normals[f_counter] = map_normals[i].x; + mesh.normals[f_counter + 1] = map_normals[i].y; + mesh.normals[f_counter + 2] = map_normals[i].z; + f_counter += 3; + } + + f_counter = 0; + + // Move texcoords data + for (rf_int i = 0; i < vertex_texcoord_counter; i++) + { + mesh.texcoords[f_counter] = map_texcoords[i].x; + mesh.texcoords[f_counter + 1] = map_texcoords[i].y; + f_counter += 2; + } + + RF_FREE(temp_allocator, map_vertices); + RF_FREE(temp_allocator, map_normals); + RF_FREE(temp_allocator, map_texcoords); + + RF_FREE(temp_allocator, cubicmap_pixels); // Free image pixel data + + // Upload vertex data to GPU (static mesh) + rf_gfx_load_mesh(&mesh, false); + + return mesh; +} + +#pragma endregion +/*** End of inlined file: rayfork-3d.c ***/ + + +/*** Start of inlined file: rayfork-drawing.c ***/ +#pragma region internal functions + +// Get texture to draw shapes, the user can customize this using rf_set_shapes_texture +RF_INTERNAL rf_texture2d rf_get_shapes_texture() +{ + if (rf_ctx.tex_shapes.id == 0) + { + rf_ctx.tex_shapes = rf_get_default_texture(); + rf_ctx.rec_tex_shapes = (rf_rec) {0.0f, 0.0f, 1.0f, 1.0f }; + } + + return rf_ctx.tex_shapes; +} + +// Cubic easing in-out. Note: Required for rf_draw_line_bezier() +RF_INTERNAL float rf_shapes_ease_cubic_in_out(float t, float b, float c, float d) +{ + if ((t /= 0.5f*d) < 1) return 0.5f*c*t*t*t + b; + + t -= 2; + + return 0.5f*c*(t*t*t + 2.0f) + b; +} + +#pragma endregion + +// Set background color (framebuffer clear color) +RF_API void rf_clear(rf_color color) +{ + rf_gfx_clear_color(color.r, color.g, color.b, color.a); // Set clear color + rf_gfx_clear_screen_buffers(); // Clear current framebuffers +} + +// Setup canvas (framebuffer) to start drawing +RF_API void rf_begin() +{ + rf_gfx_load_identity(); // Reset current matrix (MODELVIEW) + rf_gfx_mult_matrixf(rf_mat_to_float16(rf_ctx.screen_scaling).v); // Apply screen scaling + + //rf_gfx_translatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1, not required with OpenGL 3.3+ +} + +// End canvas drawing and swap buffers (double buffering) +RF_API void rf_end() +{ + rf_gfx_draw(); +} + +// Initialize 2D mode with custom camera (2D) +RF_API void rf_begin_2d(rf_camera2d camera) +{ + rf_gfx_draw(); + + rf_gfx_load_identity(); // Reset current matrix (MODELVIEW) + + // Apply screen scaling if required + rf_gfx_mult_matrixf(rf_mat_to_float16(rf_ctx.screen_scaling).v); + + // Apply 2d camera transformation to rf_gfxobal_model_view + rf_gfx_mult_matrixf(rf_mat_to_float16(rf_get_camera_matrix2d(camera)).v); +} + +// Ends 2D mode with custom camera +RF_API void rf_end_2d() +{ + rf_gfx_draw(); + + rf_gfx_load_identity(); // Reset current matrix (MODELVIEW) + rf_gfx_mult_matrixf(rf_mat_to_float16(rf_ctx.screen_scaling).v); // Apply screen scaling if required +} + +// Initializes 3D mode with custom camera (3D) +RF_API void rf_begin_3d(rf_camera3d camera) +{ + rf_gfx_draw(); + + rf_gfx_matrix_mode(RF_PROJECTION); // Switch to GL_PROJECTION matrix + rf_gfx_push_matrix(); // Save previous matrix, which contains the settings for the 2d ortho GL_PROJECTION + rf_gfx_load_identity(); // Reset current matrix (PROJECTION) + + float aspect = (float) rf_ctx.current_width / (float)rf_ctx.current_height; + + if (camera.type == RF_CAMERA_PERSPECTIVE) + { + // Setup perspective GL_PROJECTION + double top = 0.01 * tan(camera.fovy*0.5*RF_DEG2RAD); + double right = top*aspect; + + rf_gfx_frustum(-right, right, -top, top, 0.01, 1000.0); + } + else if (camera.type == RF_CAMERA_ORTHOGRAPHIC) + { + // Setup orthographic GL_PROJECTION + double top = camera.fovy/2.0; + double right = top*aspect; + + rf_gfx_ortho(-right,right,-top,top, 0.01, 1000.0); + } + + // NOTE: zNear and zFar values are important when computing depth buffer values + + rf_gfx_matrix_mode(RF_MODELVIEW); // Switch back to rf_gfxobal_model_view matrix + rf_gfx_load_identity(); // Reset current matrix (MODELVIEW) + + // Setup rf_camera3d view + rf_mat mat_view = rf_mat_look_at(camera.position, camera.target, camera.up); + rf_gfx_mult_matrixf(rf_mat_to_float16(mat_view).v); // Multiply MODELVIEW matrix by view matrix (camera) + + rf_gfx_enable_depth_test(); // Enable DEPTH_TEST for 3D +} + +// Ends 3D mode and returns to default 2D orthographic mode +RF_API void rf_end_3d() +{ + rf_gfx_draw(); // Process internal buffers (update + draw) + + rf_gfx_matrix_mode(RF_PROJECTION); // Switch to GL_PROJECTION matrix + rf_gfx_pop_matrix(); // Restore previous matrix (PROJECTION) from matrix rf_gfxobal_gl_stack + + rf_gfx_matrix_mode(RF_MODELVIEW); // Get back to rf_gfxobal_model_view matrix + rf_gfx_load_identity(); // Reset current matrix (MODELVIEW) + + rf_gfx_mult_matrixf(rf_mat_to_float16(rf_ctx.screen_scaling).v); // Apply screen scaling if required + + rf_gfx_disable_depth_test(); // Disable DEPTH_TEST for 2D +} + +// Initializes render texture for drawing +RF_API void rf_begin_render_to_texture(rf_render_texture2d target) +{ + rf_gfx_draw(); + + rf_gfx_enable_render_texture(target.id); // Enable render target + + // Set viewport to framebuffer size + rf_gfx_viewport(0, 0, target.texture.width, target.texture.height); + + rf_gfx_matrix_mode(RF_PROJECTION); // Switch to PROJECTION matrix + rf_gfx_load_identity(); // Reset current matrix (PROJECTION) + + // Set orthographic GL_PROJECTION to current framebuffer size + // NOTE: Configured top-left corner as (0, 0) + rf_gfx_ortho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); + + rf_gfx_matrix_mode(RF_MODELVIEW); // Switch back to MODELVIEW matrix + rf_gfx_load_identity(); // Reset current matrix (MODELVIEW) + + //rf_gfx_scalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) + + // Setup current width/height for proper aspect ratio + // calculation when using rf_begin_mode3d() + rf_ctx.current_width = target.texture.width; + rf_ctx.current_height = target.texture.height; +} + +// Ends drawing to render texture +RF_API void rf_end_render_to_texture() +{ + rf_gfx_draw(); + + rf_gfx_disable_render_texture(); // Disable render target + + // Set viewport to default framebuffer size + rf_set_viewport(rf_ctx.render_width, rf_ctx.render_height); + + // Reset current screen size + rf_ctx.current_width = rf_ctx.render_width; + rf_ctx.current_height = rf_ctx.render_height; +} + +// Begin scissor mode (define screen area for following drawing) +// NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left +RF_API void rf_begin_scissor_mode(int x, int y, int width, int height) +{ + rf_gfx_draw(); // Force drawing elements + + rf_gfx_enable_scissor_test(); + rf_gfx_scissor(x, rf_ctx.render_width - (y + height), width, height); +} + +// End scissor mode +RF_API void rf_end_scissor_mode() +{ + rf_gfx_draw(); // Force drawing elements + rf_gfx_disable_scissor_test(); +} + +// Begin custom shader mode +RF_API void rf_begin_shader(rf_shader shader) +{ + if (rf_ctx.current_shader.id != shader.id) + { + rf_gfx_draw(); + rf_ctx.current_shader = shader; + } +} + +// End custom shader mode (returns to default shader) +RF_API void rf_end_shader() +{ + rf_begin_shader(rf_ctx.default_shader); +} + +// Begin blending mode (alpha, additive, multiplied). Default blend mode is alpha +RF_API void rf_begin_blend_mode(rf_blend_mode mode) +{ + rf_gfx_blend_mode(mode); +} + +// End blending mode (reset to default: alpha blending) +RF_API void rf_end_blend_mode() +{ + rf_gfx_blend_mode(RF_BLEND_ALPHA); +} + +// Draw a pixel +RF_API void rf_draw_pixel(int pos_x, int pos_y, rf_color color) +{ + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2i(pos_x, pos_y); + rf_gfx_vertex2i(pos_x + 1, pos_y + 1); + rf_gfx_end(); +} + +// Draw a pixel (Vector version) +RF_API void rf_draw_pixel_v(rf_vec2 position, rf_color color) +{ + rf_draw_pixel(position.x, position.y, color); +} + +// Draw a line +RF_API void rf_draw_line(int startPosX, int startPosY, int endPosX, int endPosY, rf_color color) +{ + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2i(startPosX, startPosY); + rf_gfx_vertex2i(endPosX, endPosY); + rf_gfx_end(); +} + +// Draw a line (Vector version) +RF_API void rf_draw_line_v(rf_vec2 startPos, rf_vec2 endPos, rf_color color) +{ + rf_draw_line(startPos.x, startPos.y, endPos.x, endPos.y, color); +} + +// Draw a line defining thickness +RF_API void rf_draw_line_ex(rf_vec2 start_pos, rf_vec2 end_pos, float thick, rf_color color) +{ + if (start_pos.x > end_pos.x) + { + rf_vec2 temp_pos = start_pos; + start_pos = end_pos; + end_pos = temp_pos; + } + + float dx = end_pos.x - start_pos.x; + float dy = end_pos.y - start_pos.y; + + float d = sqrtf(dx*dx + dy*dy); + float angle = asinf(dy/d); + + rf_gfx_enable_texture(rf_get_shapes_texture().id); + + rf_gfx_push_matrix(); + rf_gfx_translatef((float)start_pos.x, (float)start_pos.y, 0.0f); + rf_gfx_rotatef(RF_RAD2DEG * angle, 0.0f, 0.0f, 1.0f); + rf_gfx_translatef(0, (thick > 1.0f)? -thick/2.0f : -1.0f, 0.0f); + + rf_gfx_begin(RF_QUADS); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_normal3f(0.0f, 0.0f, 1.0f); + + rf_gfx_tex_coord2f(rf_ctx.rec_tex_shapes.x / rf_ctx.tex_shapes.width, rf_ctx.rec_tex_shapes.y / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(0.0f, 0.0f); + + rf_gfx_tex_coord2f(rf_ctx.rec_tex_shapes.x / rf_ctx.tex_shapes.width, (rf_ctx.rec_tex_shapes.y + rf_ctx.rec_tex_shapes.height) / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(0.0f, thick); + + rf_gfx_tex_coord2f((rf_ctx.rec_tex_shapes.x + rf_ctx.rec_tex_shapes.width) / rf_ctx.tex_shapes.width, (rf_ctx.rec_tex_shapes.y + rf_ctx.rec_tex_shapes.height) / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(d, thick); + + rf_gfx_tex_coord2f((rf_ctx.rec_tex_shapes.x + rf_ctx.rec_tex_shapes.width) / rf_ctx.tex_shapes.width, rf_ctx.rec_tex_shapes.y / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(d, 0.0f); + rf_gfx_end(); + rf_gfx_pop_matrix(); + + rf_gfx_disable_texture(); +} + +// Draw line using cubic-bezier curves in-out +RF_API void rf_draw_line_bezier(rf_vec2 start_pos, rf_vec2 end_pos, float thick, rf_color color) +{ +#define RF_LINE_DIVISIONS 24 // Bezier line divisions + + rf_vec2 previous = start_pos; + rf_vec2 current; + + for (rf_int i = 1; i <= RF_LINE_DIVISIONS; i++) + { + // Cubic easing in-out + // NOTE: Easing is calculated only for y position value + current.y = rf_shapes_ease_cubic_in_out((float)i, start_pos.y, end_pos.y - start_pos.y, (float)RF_LINE_DIVISIONS); + current.x = previous.x + (end_pos.x - start_pos.x)/ (float)RF_LINE_DIVISIONS; + + rf_draw_line_ex(previous, current, thick, color); + + previous = current; + } + +#undef RF_LINE_DIVISIONS +} + +// Draw lines sequence +RF_API void rf_draw_line_strip(rf_vec2 *points, int points_count, rf_color color) +{ + if (points_count >= 2) + { + if (rf_gfx_check_buffer_limit(points_count)) rf_gfx_draw(); + + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + for (rf_int i = 0; i < points_count - 1; i++) + { + rf_gfx_vertex2f(points[i].x, points[i].y); + rf_gfx_vertex2f(points[i + 1].x, points[i + 1].y); + } + rf_gfx_end(); + } +} + +// Draw a color-filled circle +RF_API void rf_draw_circle(int center_x, int center_y, float radius, rf_color color) +{ + rf_draw_circle_sector((rf_vec2) {center_x, center_y }, radius, 0, 360, 36, color); +} + +// Draw a color-filled circle (Vector version) +RF_API void rf_draw_circle_v(rf_vec2 center, float radius, rf_color color) +{ + rf_draw_circle(center.x, center.y, radius, color); +} + +// Draw a piece of a circle +RF_API void rf_draw_circle_sector(rf_vec2 center, float radius, int start_angle, int end_angle, int segments, rf_color color) +{ + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero + + // Function expects (endAngle > start_angle) + if (end_angle < start_angle) + { + // Swap values + int tmp = start_angle; + start_angle = end_angle; + end_angle = tmp; + } + + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + float CIRCLE_ERROR_RATE = 0.5f; + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (end_angle - start_angle) * ceilf(2 * RF_PI / th) / 360; + + if (segments <= 0) segments = 4; + } + + float step_length = (float)(end_angle - start_angle)/(float)segments; + float angle = start_angle; + if (rf_gfx_check_buffer_limit(3*segments)) rf_gfx_draw(); + + rf_gfx_begin(RF_TRIANGLES); + for (rf_int i = 0; i < segments; i++) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_vertex2f(center.x, center.y); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*radius, center.y + cosf(RF_DEG2RAD*angle)*radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*radius); + + angle += step_length; + } + rf_gfx_end(); +} + +RF_API void rf_draw_circle_sector_lines(rf_vec2 center, float radius, int start_angle, int end_angle, int segments, rf_color color) +{ + if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero issue + + // Function expects (endAngle > start_angle) + if (end_angle < start_angle) + { + // Swap values + int tmp = start_angle; + start_angle = end_angle; + end_angle = tmp; + } + + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + + float CIRCLE_ERROR_RATE = 0.5f; + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = (end_angle - start_angle) * ceilf(2 * RF_PI / th) / 360; + + if (segments <= 0) segments = 4; + } + + float step_length = (float)(end_angle - start_angle)/(float)segments; + float angle = start_angle; + + // Hide the cap lines when the circle is full + bool show_cap_lines = true; + int limit = 2*(segments + 2); + if ((end_angle - start_angle)%360 == 0) { limit = 2*segments; show_cap_lines = false; } + + if (rf_gfx_check_buffer_limit(limit)) rf_gfx_draw(); + + rf_gfx_begin(RF_LINES); + if (show_cap_lines) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(center.x, center.y); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*radius, center.y + cosf(RF_DEG2RAD*angle)*radius); + } + + for (rf_int i = 0; i < segments; i++) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*radius, center.y + cosf(RF_DEG2RAD*angle)*radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*radius); + + angle += step_length; + } + + if (show_cap_lines) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(center.x, center.y); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*radius, center.y + cosf(RF_DEG2RAD*angle)*radius); + } + rf_gfx_end(); +} + +// Draw a gradient-filled circle +// NOTE: Gradient goes from center (color1) to border (color2) +RF_API void rf_draw_circle_gradient(int center_x, int center_y, float radius, rf_color color1, rf_color color2) +{ + if (rf_gfx_check_buffer_limit(3 * 36)) rf_gfx_draw(); + + rf_gfx_begin(RF_TRIANGLES); + for (rf_int i = 0; i < 360; i += 10) + { + rf_gfx_color4ub(color1.r, color1.g, color1.b, color1.a); + rf_gfx_vertex2f(center_x, center_y); + rf_gfx_color4ub(color2.r, color2.g, color2.b, color2.a); + rf_gfx_vertex2f(center_x + sinf(RF_DEG2RAD*i)*radius, center_y + cosf(RF_DEG2RAD*i)*radius); + rf_gfx_color4ub(color2.r, color2.g, color2.b, color2.a); + rf_gfx_vertex2f(center_x + sinf(RF_DEG2RAD*(i + 10))*radius, center_y + cosf(RF_DEG2RAD*(i + 10))*radius); + } + rf_gfx_end(); +} + +// Draw circle outline +RF_API void rf_draw_circle_lines(int center_x, int center_y, float radius, rf_color color) +{ + if (rf_gfx_check_buffer_limit(2 * 36)) rf_gfx_draw(); + + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + // NOTE: Circle outline is drawn pixel by pixel every degree (0 to 360) + for (rf_int i = 0; i < 360; i += 10) + { + rf_gfx_vertex2f(center_x + sinf(RF_DEG2RAD*i)*radius, center_y + cosf(RF_DEG2RAD*i)*radius); + rf_gfx_vertex2f(center_x + sinf(RF_DEG2RAD*(i + 10))*radius, center_y + cosf(RF_DEG2RAD*(i + 10))*radius); + } + rf_gfx_end(); +} + +RF_API void rf_draw_ring(rf_vec2 center, float inner_radius, float outer_radius, int start_angle, int end_angle, int segments, rf_color color) +{ + if (start_angle == end_angle) return; + + // Function expects (outerRadius > innerRadius) + if (outer_radius < inner_radius) + { + float tmp = outer_radius; + outer_radius = inner_radius; + inner_radius = tmp; + + if (outer_radius <= 0.0f) outer_radius = 0.1f; + } + + // Function expects (endAngle > start_angle) + if (end_angle < start_angle) + { + // Swap values + int tmp = start_angle; + start_angle = end_angle; + end_angle = tmp; + } + + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + + float CIRCLE_ERROR_RATE = 0.5f; + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/outer_radius, 2) - 1); + segments = (end_angle - start_angle) * ceilf(2 * RF_PI / th) / 360; + + if (segments <= 0) segments = 4; + } + + // Not a ring + if (inner_radius <= 0.0f) + { + rf_draw_circle_sector(center, outer_radius, start_angle, end_angle, segments, color); + return; + } + + float step_length = (float)(end_angle - start_angle)/(float)segments; + float angle = start_angle; + if (rf_gfx_check_buffer_limit(6*segments)) rf_gfx_draw(); + + rf_gfx_begin(RF_TRIANGLES); + for (rf_int i = 0; i < segments; i++) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*inner_radius, center.y + cosf(RF_DEG2RAD*angle)*inner_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*outer_radius, center.y + cosf(RF_DEG2RAD*angle)*outer_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*inner_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*inner_radius); + + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*inner_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*inner_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*outer_radius, center.y + cosf(RF_DEG2RAD*angle)*outer_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*outer_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*outer_radius); + + angle += step_length; + } + rf_gfx_end(); + +} + +RF_API void rf_draw_ring_lines(rf_vec2 center, float inner_radius, float outer_radius, int start_angle, int end_angle, int segments, rf_color color) +{ + if (start_angle == end_angle) return; + + // Function expects (outerRadius > innerRadius) + if (outer_radius < inner_radius) + { + float tmp = outer_radius; + outer_radius = inner_radius; + inner_radius = tmp; + + if (outer_radius <= 0.0f) outer_radius = 0.1f; + } + + // Function expects (endAngle > start_angle) + if (end_angle < start_angle) + { + // Swap values + int tmp = start_angle; + start_angle = end_angle; + end_angle = tmp; + } + + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + + float CIRCLE_ERROR_RATE = 0.5f; + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/outer_radius, 2) - 1); + segments = (end_angle - start_angle) * ceilf(2 * RF_PI / th) / 360; + + if (segments <= 0) segments = 4; + } + + if (inner_radius <= 0.0f) + { + rf_draw_circle_sector_lines(center, outer_radius, start_angle, end_angle, segments, color); + return; + } + + float step_length = (float)(end_angle - start_angle)/(float)segments; + float angle = start_angle; + + bool show_cap_lines = true; + int limit = 4 * (segments + 1); + if ((end_angle - start_angle)%360 == 0) { limit = 4 * segments; show_cap_lines = false; } + + if (rf_gfx_check_buffer_limit(limit)) rf_gfx_draw(); + + rf_gfx_begin(RF_LINES); + if (show_cap_lines) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*outer_radius, center.y + cosf(RF_DEG2RAD*angle)*outer_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*inner_radius, center.y + cosf(RF_DEG2RAD*angle)*inner_radius); + } + + for (rf_int i = 0; i < segments; i++) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*outer_radius, center.y + cosf(RF_DEG2RAD*angle)*outer_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*outer_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*outer_radius); + + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*inner_radius, center.y + cosf(RF_DEG2RAD*angle)*inner_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*inner_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*inner_radius); + + angle += step_length; + } + + if (show_cap_lines) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*outer_radius, center.y + cosf(RF_DEG2RAD*angle)*outer_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*inner_radius, center.y + cosf(RF_DEG2RAD*angle)*inner_radius); + } + rf_gfx_end(); +} + +// Draw a color-filled rectangle +RF_API void rf_draw_rectangle(int posX, int posY, int width, int height, rf_color color) +{ + rf_draw_rectangle_v((rf_vec2){ (float)posX, (float)posY }, (rf_vec2){ (float)width, (float)height }, color); +} + +// Draw a color-filled rectangle (Vector version) +RF_API void rf_draw_rectangle_v(rf_vec2 position, rf_vec2 size, rf_color color) +{ + rf_draw_rectangle_pro((rf_rec) { position.x, position.y, size.x, size.y }, (rf_vec2){ 0.0f, 0.0f }, 0.0f, color); +} + +// Draw a color-filled rectangle +RF_API void rf_draw_rectangle_rec(rf_rec rec, rf_color color) +{ + rf_draw_rectangle_pro(rec, (rf_vec2){ 0.0f, 0.0f }, 0.0f, color); +} + +// Draw a color-filled rectangle with pro parameters +RF_API void rf_draw_rectangle_pro(rf_rec rec, rf_vec2 origin, float rotation, rf_color color) +{ + rf_gfx_enable_texture(rf_get_shapes_texture().id); + + rf_gfx_push_matrix(); + rf_gfx_translatef(rec.x, rec.y, 0.0f); + rf_gfx_rotatef(rotation, 0.0f, 0.0f, 1.0f); + rf_gfx_translatef(-origin.x, -origin.y, 0.0f); + + rf_gfx_begin(RF_QUADS); + rf_gfx_normal3f(0.0f, 0.0f, 1.0f); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_tex_coord2f(rf_ctx.rec_tex_shapes.x / rf_ctx.tex_shapes.width, rf_ctx.rec_tex_shapes.y / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(0.0f, 0.0f); + + rf_gfx_tex_coord2f(rf_ctx.rec_tex_shapes.x / rf_ctx.tex_shapes.width, (rf_ctx.rec_tex_shapes.y + rf_ctx.rec_tex_shapes.height) / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(0.0f, rec.height); + + rf_gfx_tex_coord2f((rf_ctx.rec_tex_shapes.x + rf_ctx.rec_tex_shapes.width) / rf_ctx.tex_shapes.width, (rf_ctx.rec_tex_shapes.y + rf_ctx.rec_tex_shapes.height) / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(rec.width, rec.height); + + rf_gfx_tex_coord2f((rf_ctx.rec_tex_shapes.x + rf_ctx.rec_tex_shapes.width) / rf_ctx.tex_shapes.width, rf_ctx.rec_tex_shapes.y / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(rec.width, 0.0f); + rf_gfx_end(); + rf_gfx_pop_matrix(); + + rf_gfx_disable_texture(); +} + +// Draw a vertical-gradient-filled rectangle +// NOTE: Gradient goes from bottom (color1) to top (color2) +RF_API void rf_draw_rectangle_gradient_v(int pos_x, int pos_y, int width, int height, rf_color color1, rf_color color2) +{ + rf_draw_rectangle_gradient((rf_rec) {(float)pos_x, (float)pos_y, (float)width, (float)height }, color1, color2, color2, color1); +} + +// Draw a horizontal-gradient-filled rectangle +// NOTE: Gradient goes from bottom (color1) to top (color2) +RF_API void rf_draw_rectangle_gradient_h(int pos_x, int pos_y, int width, int height, rf_color color1, rf_color color2) +{ + rf_draw_rectangle_gradient((rf_rec) {(float)pos_x, (float)pos_y, (float)width, (float)height }, color1, color1, color2, color2); +} + +// Draw a gradient-filled rectangle +// NOTE: Colors refer to corners, starting at top-lef corner and counter-clockwise +RF_API void rf_draw_rectangle_gradient(rf_rec rec, rf_color col1, rf_color col2, rf_color col3, rf_color col4) +{ + rf_gfx_enable_texture(rf_get_shapes_texture().id); + + rf_gfx_push_matrix(); + rf_gfx_begin(RF_QUADS); + rf_gfx_normal3f(0.0f, 0.0f, 1.0f); + + // NOTE: Default raylib font character 95 is a white square + rf_gfx_color4ub(col1.r, col1.g, col1.b, col1.a); + rf_gfx_tex_coord2f(rf_ctx.rec_tex_shapes.x / rf_ctx.tex_shapes.width, rf_ctx.rec_tex_shapes.y / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(rec.x, rec.y); + + rf_gfx_color4ub(col2.r, col2.g, col2.b, col2.a); + rf_gfx_tex_coord2f(rf_ctx.rec_tex_shapes.x / rf_ctx.tex_shapes.width, (rf_ctx.rec_tex_shapes.y + rf_ctx.rec_tex_shapes.height) / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(rec.x, rec.y + rec.height); + + rf_gfx_color4ub(col3.r, col3.g, col3.b, col3.a); + rf_gfx_tex_coord2f((rf_ctx.rec_tex_shapes.x + rf_ctx.rec_tex_shapes.width) / rf_ctx.tex_shapes.width, (rf_ctx.rec_tex_shapes.y + rf_ctx.rec_tex_shapes.height) / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(rec.x + rec.width, rec.y + rec.height); + + rf_gfx_color4ub(col4.r, col4.g, col4.b, col4.a); + rf_gfx_tex_coord2f((rf_ctx.rec_tex_shapes.x + rf_ctx.rec_tex_shapes.width) / rf_ctx.tex_shapes.width, rf_ctx.rec_tex_shapes.y / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(rec.x + rec.width, rec.y); + rf_gfx_end(); + rf_gfx_pop_matrix(); + + rf_gfx_disable_texture(); +} + +// Draw rectangle outline with extended parameters +RF_API void rf_draw_rectangle_outline(rf_rec rec, int line_thick, rf_color color) +{ + if (line_thick > rec.width || line_thick > rec.height) + { + if (rec.width > rec.height) line_thick = (int)rec.height/2; + else if (rec.width < rec.height) line_thick = (int)rec.width/2; + } + + rf_draw_rectangle_pro((rf_rec) {(int)rec.x, (int)rec.y, (int)rec.width, line_thick }, (rf_vec2){0.0f, 0.0f}, 0.0f, color); + rf_draw_rectangle_pro((rf_rec) {(int)(rec.x - line_thick + rec.width), (int)(rec.y + line_thick), line_thick, (int)(rec.height - line_thick * 2.0f) }, (rf_vec2){0.0f, 0.0f}, 0.0f, color); + rf_draw_rectangle_pro((rf_rec) {(int)rec.x, (int)(rec.y + rec.height - line_thick), (int)rec.width, line_thick }, (rf_vec2){0.0f, 0.0f}, 0.0f, color); + rf_draw_rectangle_pro((rf_rec) {(int)rec.x, (int)(rec.y + line_thick), line_thick, (int)(rec.height - line_thick * 2) }, (rf_vec2) {0.0f, 0.0f }, 0.0f, color); +} + +// Draw rectangle with rounded edges +RF_API void rf_draw_rectangle_rounded(rf_rec rec, float roundness, int segments, rf_color color) +{ + // Not a rounded rectangle + if ((roundness <= 0.0f) || (rec.width < 1) || (rec.height < 1 )) + { + rf_draw_rectangle_pro(rec, (rf_vec2){0.0f, 0.0f}, 0.0f, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + + float CIRCLE_ERROR_RATE = 0.5f; + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = ceilf(2 * RF_PI / th) / 4; + if (segments <= 0) segments = 4; + } + + float step_length = 90.0f/(float)segments; + + /* Quick sketch to make sense of all of this (there are 9 parts to draw, also mark the 12 points we'll use below) + * Not my best attempt at ASCII art, just preted it's rounded rectangle :) + * P0 P1 + * ____________________ + * /| |\ + * /1| 2 |3\ + *P7 /__|____________________|__\ P2 + * / | | _\ P2 + * | |P8 P9| | + * | 8 | 9 | 4 | + * | __|____________________|__ | + *P6 \ |P11 P10| / P3 + * \7| 6 |5/ + * \|____________________|/ + * P5 P4 + */ + + const rf_vec2 point[12] = { // coordinates of the 12 points that define the rounded rect (the idea here is to make things easier) + {(float)rec.x + radius, rec.y}, {(float)(rec.x + rec.width) - radius, rec.y}, { rec.x + rec.width, (float)rec.y + radius }, // PO, P1, P2 + {rec.x + rec.width, (float)(rec.y + rec.height) - radius}, {(float)(rec.x + rec.width) - radius, rec.y + rec.height}, // P3, P4 + {(float)rec.x + radius, rec.y + rec.height}, { rec.x, (float)(rec.y + rec.height) - radius}, {rec.x, (float)rec.y + radius}, // P5, P6, P7 + {(float)rec.x + radius, (float)rec.y + radius}, {(float)(rec.x + rec.width) - radius, (float)rec.y + radius}, // P8, P9 + {(float)(rec.x + rec.width) - radius, (float)(rec.y + rec.height) - radius}, {(float)rec.x + radius, (float)(rec.y + rec.height) - radius} // P10, P11 + }; + + const rf_vec2 centers[4] = {point[8], point[9], point[10], point[11] }; + const float angles[4] = { 180.0f, 90.0f, 0.0f, 270.0f }; + if (rf_gfx_check_buffer_limit(12*segments + 5*6)) rf_gfx_draw(); // 4 corners with 3 vertices per segment + 5 rectangles with 6 vertices each + + rf_gfx_begin(RF_TRIANGLES); + // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner + for (rf_int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const rf_vec2 center = centers[k]; + for (rf_int i = 0; i < segments; i++) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(center.x, center.y); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*radius, center.y + cosf(RF_DEG2RAD*angle)*radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*radius); + angle += step_length; + } + } + + // [2] Upper rf_rec + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[0].x, point[0].y); + rf_gfx_vertex2f(point[8].x, point[8].y); + rf_gfx_vertex2f(point[9].x, point[9].y); + rf_gfx_vertex2f(point[1].x, point[1].y); + rf_gfx_vertex2f(point[0].x, point[0].y); + rf_gfx_vertex2f(point[9].x, point[9].y); + + // [4] Right rf_rec + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[9].x, point[9].y); + rf_gfx_vertex2f(point[10].x, point[10].y); + rf_gfx_vertex2f(point[3].x, point[3].y); + rf_gfx_vertex2f(point[2].x, point[2].y); + rf_gfx_vertex2f(point[9].x, point[9].y); + rf_gfx_vertex2f(point[3].x, point[3].y); + + // [6] Bottom rf_rec + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[11].x, point[11].y); + rf_gfx_vertex2f(point[5].x, point[5].y); + rf_gfx_vertex2f(point[4].x, point[4].y); + rf_gfx_vertex2f(point[10].x, point[10].y); + rf_gfx_vertex2f(point[11].x, point[11].y); + rf_gfx_vertex2f(point[4].x, point[4].y); + + // [8] Left rf_rec + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[7].x, point[7].y); + rf_gfx_vertex2f(point[6].x, point[6].y); + rf_gfx_vertex2f(point[11].x, point[11].y); + rf_gfx_vertex2f(point[8].x, point[8].y); + rf_gfx_vertex2f(point[7].x, point[7].y); + rf_gfx_vertex2f(point[11].x, point[11].y); + + // [9] Middle rf_rec + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[8].x, point[8].y); + rf_gfx_vertex2f(point[11].x, point[11].y); + rf_gfx_vertex2f(point[10].x, point[10].y); + rf_gfx_vertex2f(point[9].x, point[9].y); + rf_gfx_vertex2f(point[8].x, point[8].y); + rf_gfx_vertex2f(point[10].x, point[10].y); + rf_gfx_end(); + +} + +// Draw rectangle with rounded edges outline +RF_API void rf_draw_rectangle_rounded_lines(rf_rec rec, float roundness, int segments, int line_thick, rf_color color) +{ + if (line_thick < 0) line_thick = 0; + + // Not a rounded rectangle + if (roundness <= 0.0f) + { + rf_draw_rectangle_outline((rf_rec) {rec.x - line_thick, rec.y - line_thick, rec.width + 2 * line_thick, rec.height + 2 * line_thick}, line_thick, color); + return; + } + + if (roundness >= 1.0f) roundness = 1.0f; + + // Calculate corner radius + float radius = (rec.width > rec.height)? (rec.height*roundness)/2 : (rec.width*roundness)/2; + if (radius <= 0.0f) return; + + // Calculate number of segments to use for the corners + if (segments < 4) + { + // Calculate how many segments we need to draw a smooth circle, taken from https://stackoverflow.com/a/2244088 + + float CIRCLE_ERROR_RATE = 0.5f; + + // Calculate the maximum angle between segments based on the error rate. + float th = acosf(2*powf(1 - CIRCLE_ERROR_RATE/radius, 2) - 1); + segments = ceilf(2 * RF_PI / th) / 2; + if (segments <= 0) segments = 4; + } + + float step_length = 90.0f/(float)segments; + const float outer_radius = radius + (float)line_thick, inner_radius = radius; + + /* Quick sketch to make sense of all of this (mark the 16 + 4(corner centers P16-19) points we'll use below) + * Not my best attempt at ASCII art, just preted it's rounded rectangle :) + * P0 P1 + * ==================== + * // P8 P9 \ + * // \ + *P7 // P15 P10 \\ P2 + \\ P2 + * || *P16 P17* || + * || || + * || P14 P11 || + *P6 \\ *P19 P18* // P3 + * \\ // + * \\ P13 P12 // + * ==================== + * P5 P4 + + */ + const rf_vec2 point[16] = + { + {(float)rec.x + inner_radius, rec.y - line_thick}, {(float)(rec.x + rec.width) - inner_radius, rec.y - line_thick}, { rec.x + rec.width + line_thick, (float)rec.y + inner_radius }, // PO, P1, P2 + {rec.x + rec.width + line_thick, (float)(rec.y + rec.height) - inner_radius}, {(float)(rec.x + rec.width) - inner_radius, rec.y + rec.height + line_thick}, // P3, P4 + {(float)rec.x + inner_radius, rec.y + rec.height + line_thick}, { rec.x - line_thick, (float)(rec.y + rec.height) - inner_radius}, {rec.x - line_thick, (float)rec.y + inner_radius}, // P5, P6, P7 + {(float)rec.x + inner_radius, rec.y}, {(float)(rec.x + rec.width) - inner_radius, rec.y}, // P8, P9 + { rec.x + rec.width, (float)rec.y + inner_radius }, {rec.x + rec.width, (float)(rec.y + rec.height) - inner_radius}, // P10, P11 + {(float)(rec.x + rec.width) - inner_radius, rec.y + rec.height}, {(float)rec.x + inner_radius, rec.y + rec.height}, // P12, P13 + { rec.x, (float)(rec.y + rec.height) - inner_radius}, {rec.x, (float)rec.y + inner_radius} // P14, P15 + }; + + const rf_vec2 centers[4] = + { + {(float)rec.x + inner_radius, (float)rec.y + inner_radius}, {(float)(rec.x + rec.width) - inner_radius, (float)rec.y + inner_radius}, // P16, P17 + {(float)(rec.x + rec.width) - inner_radius, (float)(rec.y + rec.height) - inner_radius}, {(float)rec.x + inner_radius, (float)(rec.y + rec.height) - inner_radius} // P18, P19 + }; + + const float angles[4] = { 180.0f, 90.0f, 0.0f, 270.0f }; + + if (line_thick > 1) + { + if (rf_gfx_check_buffer_limit(4 * 6*segments + 4 * 6)) rf_gfx_draw(); // 4 corners with 6(2 * 3) vertices for each segment + 4 rectangles with 6 vertices each + + rf_gfx_begin(RF_TRIANGLES); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (rf_int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const rf_vec2 center = centers[k]; + + for (rf_int i = 0; i < segments; i++) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*inner_radius, center.y + cosf(RF_DEG2RAD*angle)*inner_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*outer_radius, center.y + cosf(RF_DEG2RAD*angle)*outer_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*inner_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*inner_radius); + + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*inner_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*inner_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*outer_radius, center.y + cosf(RF_DEG2RAD*angle)*outer_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*outer_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*outer_radius); + + angle += step_length; + } + } + + // Upper rectangle + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[0].x, point[0].y); + rf_gfx_vertex2f(point[8].x, point[8].y); + rf_gfx_vertex2f(point[9].x, point[9].y); + rf_gfx_vertex2f(point[1].x, point[1].y); + rf_gfx_vertex2f(point[0].x, point[0].y); + rf_gfx_vertex2f(point[9].x, point[9].y); + + // Right rectangle + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[10].x, point[10].y); + rf_gfx_vertex2f(point[11].x, point[11].y); + rf_gfx_vertex2f(point[3].x, point[3].y); + rf_gfx_vertex2f(point[2].x, point[2].y); + rf_gfx_vertex2f(point[10].x, point[10].y); + rf_gfx_vertex2f(point[3].x, point[3].y); + + // Lower rectangle + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[13].x, point[13].y); + rf_gfx_vertex2f(point[5].x, point[5].y); + rf_gfx_vertex2f(point[4].x, point[4].y); + rf_gfx_vertex2f(point[12].x, point[12].y); + rf_gfx_vertex2f(point[13].x, point[13].y); + rf_gfx_vertex2f(point[4].x, point[4].y); + + // Left rectangle + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[7].x, point[7].y); + rf_gfx_vertex2f(point[6].x, point[6].y); + rf_gfx_vertex2f(point[14].x, point[14].y); + rf_gfx_vertex2f(point[15].x, point[15].y); + rf_gfx_vertex2f(point[7].x, point[7].y); + rf_gfx_vertex2f(point[14].x, point[14].y); + rf_gfx_end(); + + } + else + { + // Use LINES to draw the outline + if (rf_gfx_check_buffer_limit(8*segments + 4 * 2)) rf_gfx_draw(); // 4 corners with 2 vertices for each segment + 4 rectangles with 2 vertices each + + rf_gfx_begin(RF_LINES); + + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner + for (rf_int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop + { + float angle = angles[k]; + const rf_vec2 center = centers[k]; + + for (rf_int i = 0; i < segments; i++) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*angle)*outer_radius, center.y + cosf(RF_DEG2RAD*angle)*outer_radius); + rf_gfx_vertex2f(center.x + sinf(RF_DEG2RAD*(angle + step_length))*outer_radius, center.y + cosf(RF_DEG2RAD*(angle + step_length))*outer_radius); + angle += step_length; + } + } + // And now the remaining 4 lines + for(int i = 0; i < 8; i += 2) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(point[i].x, point[i].y); + rf_gfx_vertex2f(point[i + 1].x, point[i + 1].y); + } + rf_gfx_end(); + } +} + +// Draw a triangle +// NOTE: Vertex must be provided in counter-clockwise order +RF_API void rf_draw_triangle(rf_vec2 v1, rf_vec2 v2, rf_vec2 v3, rf_color color) +{ + if (rf_gfx_check_buffer_limit(4)) rf_gfx_draw(); + rf_gfx_begin(RF_TRIANGLES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(v1.x, v1.y); + rf_gfx_vertex2f(v2.x, v2.y); + rf_gfx_vertex2f(v3.x, v3.y); + rf_gfx_end(); + +} + +// Draw a triangle using lines +// NOTE: Vertex must be provided in counter-clockwise order +RF_API void rf_draw_triangle_lines(rf_vec2 v1, rf_vec2 v2, rf_vec2 v3, rf_color color) +{ + if (rf_gfx_check_buffer_limit(6)) rf_gfx_draw(); + + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex2f(v1.x, v1.y); + rf_gfx_vertex2f(v2.x, v2.y); + + rf_gfx_vertex2f(v2.x, v2.y); + rf_gfx_vertex2f(v3.x, v3.y); + + rf_gfx_vertex2f(v3.x, v3.y); + rf_gfx_vertex2f(v1.x, v1.y); + rf_gfx_end(); +} + +// Draw a triangle fan defined by points +// NOTE: First vertex provided is the center, shared by all triangles +RF_API void rf_draw_triangle_fan(rf_vec2 *points, int points_count, rf_color color) +{ + if (points_count >= 3) + { + if (rf_gfx_check_buffer_limit((points_count - 2) * 4)) rf_gfx_draw(); + + rf_gfx_enable_texture(rf_get_shapes_texture().id); + rf_gfx_begin(RF_QUADS); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + for (rf_int i = 1; i < points_count - 1; i++) + { + rf_gfx_tex_coord2f(rf_ctx.rec_tex_shapes.x / rf_ctx.tex_shapes.width, rf_ctx.rec_tex_shapes.y / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(points[0].x, points[0].y); + + rf_gfx_tex_coord2f(rf_ctx.rec_tex_shapes.x / rf_ctx.tex_shapes.width, (rf_ctx.rec_tex_shapes.y + rf_ctx.rec_tex_shapes.height) / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(points[i].x, points[i].y); + + rf_gfx_tex_coord2f((rf_ctx.rec_tex_shapes.x + rf_ctx.rec_tex_shapes.width) / rf_ctx.tex_shapes.width, (rf_ctx.rec_tex_shapes.y + rf_ctx.rec_tex_shapes.height) / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(points[i + 1].x, points[i + 1].y); + + rf_gfx_tex_coord2f((rf_ctx.rec_tex_shapes.x + rf_ctx.rec_tex_shapes.width) / rf_ctx.tex_shapes.width, rf_ctx.rec_tex_shapes.y / rf_ctx.tex_shapes.height); + rf_gfx_vertex2f(points[i + 1].x, points[i + 1].y); + } + rf_gfx_end(); + rf_gfx_disable_texture(); + } +} + +// Draw a triangle strip defined by points +// NOTE: Every new vertex connects with previous two +RF_API void rf_draw_triangle_strip(rf_vec2 *points, int points_count, rf_color color) +{ + if (points_count >= 3) + { + if (rf_gfx_check_buffer_limit(points_count)) rf_gfx_draw(); + + rf_gfx_begin(RF_TRIANGLES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + for (rf_int i = 2; i < points_count; i++) + { + if ((i%2) == 0) + { + rf_gfx_vertex2f(points[i].x, points[i].y); + rf_gfx_vertex2f(points[i - 2].x, points[i - 2].y); + rf_gfx_vertex2f(points[i - 1].x, points[i - 1].y); + } + else + { + rf_gfx_vertex2f(points[i].x, points[i].y); + rf_gfx_vertex2f(points[i - 1].x, points[i - 1].y); + rf_gfx_vertex2f(points[i - 2].x, points[i - 2].y); + } + } + rf_gfx_end(); + } +} + +// Draw a regular polygon of n sides (Vector version) +RF_API void rf_draw_poly(rf_vec2 center, int sides, float radius, float rotation, rf_color color) +{ + if (sides < 3) sides = 3; + float centralAngle = 0.0f; + + if (rf_gfx_check_buffer_limit(4 * (360/sides))) rf_gfx_draw(); + + rf_gfx_push_matrix(); + rf_gfx_translatef(center.x, center.y, 0.0f); + rf_gfx_rotatef(rotation, 0.0f, 0.0f, 1.0f); + rf_gfx_begin(RF_TRIANGLES); + for (rf_int i = 0; i < sides; i++) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_vertex2f(0, 0); + rf_gfx_vertex2f(sinf(RF_DEG2RAD*centralAngle)*radius, cosf(RF_DEG2RAD*centralAngle)*radius); + + centralAngle += 360.0f/(float)sides; + rf_gfx_vertex2f(sinf(RF_DEG2RAD*centralAngle)*radius, cosf(RF_DEG2RAD*centralAngle)*radius); + } + rf_gfx_end(); + + rf_gfx_pop_matrix(); +} + +// Draw a rf_texture2d with extended parameters +RF_API void rf_draw_texture(rf_texture2d texture, int x, int y, rf_color tint) +{ + rf_draw_texture_ex(texture, x, y, texture.width, texture.height, 0, tint); +} + +// Draw a rf_texture2d with extended parameters +RF_API void rf_draw_texture_ex(rf_texture2d texture, int x, int y, int w, int h, float rotation, rf_color tint) +{ + rf_rec source_rec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + rf_rec dest_rec = { x, y, w, h }; + rf_vec2 origin = { 0.0f, 0.0f }; + + rf_draw_texture_region(texture, source_rec, dest_rec, origin, rotation, tint); +} + +// Draw a part of a texture (defined by a rectangle) with 'pro' parameters. Note: origin is relative to destination rectangle size +RF_API void rf_draw_texture_region(rf_texture2d texture, rf_rec source_rec, rf_rec dest_rec, rf_vec2 origin, float rotation, rf_color tint) +{ + // Check if texture is valid + if (texture.id > 0) + { + float width = (float)texture.width; + float height = (float)texture.height; + + bool flip_x = false; + + if (source_rec.width < 0) { flip_x = true; source_rec.width *= -1; } + if (source_rec.height < 0) source_rec.y -= source_rec.height; + + rf_gfx_enable_texture(texture.id); + + rf_gfx_push_matrix(); + rf_gfx_translatef(dest_rec.x, dest_rec.y, 0.0f); + rf_gfx_rotatef(rotation, 0.0f, 0.0f, 1.0f); + rf_gfx_translatef(-origin.x, -origin.y, 0.0f); + + rf_gfx_begin(RF_QUADS); + rf_gfx_color4ub(tint.r, tint.g, tint.b, tint.a); + rf_gfx_normal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Bottom-left corner for texture and quad + if (flip_x) rf_gfx_tex_coord2f((source_rec.x + source_rec.width) / width, source_rec.y / height); + else rf_gfx_tex_coord2f(source_rec.x / width, source_rec.y / height); + rf_gfx_vertex2f(0.0f, 0.0f); + + // Bottom-right corner for texture and quad + if (flip_x) rf_gfx_tex_coord2f((source_rec.x + source_rec.width) / width, (source_rec.y + source_rec.height) / height); + else rf_gfx_tex_coord2f(source_rec.x / width, (source_rec.y + source_rec.height) / height); + rf_gfx_vertex2f(0.0f, dest_rec.height); + + // Top-right corner for texture and quad + if (flip_x) rf_gfx_tex_coord2f(source_rec.x / width, (source_rec.y + source_rec.height) / height); + else rf_gfx_tex_coord2f((source_rec.x + source_rec.width) / width, (source_rec.y + source_rec.height) / height); + rf_gfx_vertex2f(dest_rec.width, dest_rec.height); + + // Top-left corner for texture and quad + if (flip_x) rf_gfx_tex_coord2f(source_rec.x / width, source_rec.y / height); + else rf_gfx_tex_coord2f((source_rec.x + source_rec.width) / width, source_rec.y / height); + rf_gfx_vertex2f(dest_rec.width, 0.0f); + rf_gfx_end(); + rf_gfx_pop_matrix(); + + rf_gfx_disable_texture(); + } +} + +// Draws a texture (or part of it) that stretches or shrinks nicely using n-patch info +RF_API void rf_draw_texture_npatch(rf_texture2d texture, rf_npatch_info n_patch_info, rf_rec dest_rec, rf_vec2 origin, float rotation, rf_color tint) +{ + if (texture.id > 0) + { + float width = (float)texture.width; + float height = (float)texture.height; + + float patch_width = (dest_rec.width <= 0.0f)? 0.0f : dest_rec.width; + float patch_height = (dest_rec.height <= 0.0f)? 0.0f : dest_rec.height; + + if (n_patch_info.source_rec.width < 0) n_patch_info.source_rec.x -= n_patch_info.source_rec.width; + if (n_patch_info.source_rec.height < 0) n_patch_info.source_rec.y -= n_patch_info.source_rec.height; + if (n_patch_info.type == RF_NPT_3PATCH_HORIZONTAL) patch_height = n_patch_info.source_rec.height; + if (n_patch_info.type == RF_NPT_3PATCH_VERTICAL) patch_width = n_patch_info.source_rec.width; + + bool draw_center = true; + bool draw_middle = true; + float left_border = (float)n_patch_info.left; + float top_border = (float)n_patch_info.top; + float right_border = (float)n_patch_info.right; + float bottom_border = (float)n_patch_info.bottom; + + // adjust the lateral (left and right) border widths in case patch_width < texture.width + if (patch_width <= (left_border + right_border) && n_patch_info.type != RF_NPT_3PATCH_VERTICAL) + { + draw_center = false; + left_border = (left_border / (left_border + right_border)) * patch_width; + right_border = patch_width - left_border; + } + // adjust the lateral (top and bottom) border heights in case patch_height < texture.height + if (patch_height <= (top_border + bottom_border) && n_patch_info.type != RF_NPT_3PATCH_HORIZONTAL) + { + draw_middle = false; + top_border = (top_border / (top_border + bottom_border)) * patch_height; + bottom_border = patch_height - top_border; + } + + rf_vec2 vert_a, vert_b, vert_c, vert_d; + vert_a.x = 0.0f; // outer left + vert_a.y = 0.0f; // outer top + vert_b.x = left_border; // inner left + vert_b.y = top_border; // inner top + vert_c.x = patch_width - right_border; // inner right + vert_c.y = patch_height - bottom_border; // inner bottom + vert_d.x = patch_width; // outer right + vert_d.y = patch_height; // outer bottom + + rf_vec2 coord_a, coord_b, coord_c, coord_d; + coord_a.x = n_patch_info.source_rec.x / width; + coord_a.y = n_patch_info.source_rec.y / height; + coord_b.x = (n_patch_info.source_rec.x + left_border) / width; + coord_b.y = (n_patch_info.source_rec.y + top_border) / height; + coord_c.x = (n_patch_info.source_rec.x + n_patch_info.source_rec.width - right_border) / width; + coord_c.y = (n_patch_info.source_rec.y + n_patch_info.source_rec.height - bottom_border) / height; + coord_d.x = (n_patch_info.source_rec.x + n_patch_info.source_rec.width) / width; + coord_d.y = (n_patch_info.source_rec.y + n_patch_info.source_rec.height) / height; + + rf_gfx_enable_texture(texture.id); + + rf_gfx_push_matrix(); + rf_gfx_translatef(dest_rec.x, dest_rec.y, 0.0f); + rf_gfx_rotatef(rotation, 0.0f, 0.0f, 1.0f); + rf_gfx_translatef(-origin.x, -origin.y, 0.0f); + + rf_gfx_begin(RF_QUADS); + rf_gfx_color4ub(tint.r, tint.g, tint.b, tint.a); + rf_gfx_normal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + if (n_patch_info.type == RF_NPT_9PATCH) + { + // ------------------------------------------------------------ + // TOP-LEFT QUAD + rf_gfx_tex_coord2f(coord_a.x, coord_b.y); rf_gfx_vertex2f(vert_a.x, vert_b.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_b.y); rf_gfx_vertex2f(vert_b.x, vert_b.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_a.y); rf_gfx_vertex2f(vert_b.x, vert_a.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_a.x, coord_a.y); rf_gfx_vertex2f(vert_a.x, vert_a.y); // Top-left corner for texture and quad + if (draw_center) + { + // TOP-CENTER QUAD + rf_gfx_tex_coord2f(coord_b.x, coord_b.y); rf_gfx_vertex2f(vert_b.x, vert_b.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_b.y); rf_gfx_vertex2f(vert_c.x, vert_b.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_a.y); rf_gfx_vertex2f(vert_c.x, vert_a.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_a.y); rf_gfx_vertex2f(vert_b.x, vert_a.y); // Top-left corner for texture and quad + } + // TOP-RIGHT QUAD + rf_gfx_tex_coord2f(coord_c.x, coord_b.y); rf_gfx_vertex2f(vert_c.x, vert_b.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_b.y); rf_gfx_vertex2f(vert_d.x, vert_b.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_a.y); rf_gfx_vertex2f(vert_d.x, vert_a.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_a.y); rf_gfx_vertex2f(vert_c.x, vert_a.y); // Top-left corner for texture and quad + if (draw_middle) + { + // ------------------------------------------------------------ + // MIDDLE-LEFT QUAD + rf_gfx_tex_coord2f(coord_a.x, coord_c.y); rf_gfx_vertex2f(vert_a.x, vert_c.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_c.y); rf_gfx_vertex2f(vert_b.x, vert_c.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_b.y); rf_gfx_vertex2f(vert_b.x, vert_b.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_a.x, coord_b.y); rf_gfx_vertex2f(vert_a.x, vert_b.y); // Top-left corner for texture and quad + if (draw_center) + { + // MIDDLE-CENTER QUAD + rf_gfx_tex_coord2f(coord_b.x, coord_c.y); rf_gfx_vertex2f(vert_b.x, vert_c.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_c.y); rf_gfx_vertex2f(vert_c.x, vert_c.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_b.y); rf_gfx_vertex2f(vert_c.x, vert_b.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_b.y); rf_gfx_vertex2f(vert_b.x, vert_b.y); // Top-left corner for texture and quad + } + + // MIDDLE-RIGHT QUAD + rf_gfx_tex_coord2f(coord_c.x, coord_c.y); rf_gfx_vertex2f(vert_c.x, vert_c.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_c.y); rf_gfx_vertex2f(vert_d.x, vert_c.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_b.y); rf_gfx_vertex2f(vert_d.x, vert_b.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_b.y); rf_gfx_vertex2f(vert_c.x, vert_b.y); // Top-left corner for texture and quad + } + + // ------------------------------------------------------------ + // BOTTOM-LEFT QUAD + rf_gfx_tex_coord2f(coord_a.x, coord_d.y); rf_gfx_vertex2f(vert_a.x, vert_d.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_d.y); rf_gfx_vertex2f(vert_b.x, vert_d.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_c.y); rf_gfx_vertex2f(vert_b.x, vert_c.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_a.x, coord_c.y); rf_gfx_vertex2f(vert_a.x, vert_c.y); // Top-left corner for texture and quad + if (draw_center) + { + // BOTTOM-CENTER QUAD + rf_gfx_tex_coord2f(coord_b.x, coord_d.y); rf_gfx_vertex2f(vert_b.x, vert_d.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_d.y); rf_gfx_vertex2f(vert_c.x, vert_d.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_c.y); rf_gfx_vertex2f(vert_c.x, vert_c.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_c.y); rf_gfx_vertex2f(vert_b.x, vert_c.y); // Top-left corner for texture and quad + } + + // BOTTOM-RIGHT QUAD + rf_gfx_tex_coord2f(coord_c.x, coord_d.y); rf_gfx_vertex2f(vert_c.x, vert_d.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_d.y); rf_gfx_vertex2f(vert_d.x, vert_d.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_c.y); rf_gfx_vertex2f(vert_d.x, vert_c.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_c.y); rf_gfx_vertex2f(vert_c.x, vert_c.y); // Top-left corner for texture and quad + } + else if (n_patch_info.type == RF_NPT_3PATCH_VERTICAL) + { + // TOP QUAD + // ----------------------------------------------------------- + // rf_texture coords Vertices + rf_gfx_tex_coord2f(coord_a.x, coord_b.y); rf_gfx_vertex2f(vert_a.x, vert_b.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_b.y); rf_gfx_vertex2f(vert_d.x, vert_b.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_a.y); rf_gfx_vertex2f(vert_d.x, vert_a.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_a.x, coord_a.y); rf_gfx_vertex2f(vert_a.x, vert_a.y); // Top-left corner for texture and quad + if (draw_center) + { + // MIDDLE QUAD + // ----------------------------------------------------------- + // rf_texture coords Vertices + rf_gfx_tex_coord2f(coord_a.x, coord_c.y); rf_gfx_vertex2f(vert_a.x, vert_c.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_c.y); rf_gfx_vertex2f(vert_d.x, vert_c.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_b.y); rf_gfx_vertex2f(vert_d.x, vert_b.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_a.x, coord_b.y); rf_gfx_vertex2f(vert_a.x, vert_b.y); // Top-left corner for texture and quad + } + // BOTTOM QUAD + // ----------------------------------------------------------- + // rf_texture coords Vertices + rf_gfx_tex_coord2f(coord_a.x, coord_d.y); rf_gfx_vertex2f(vert_a.x, vert_d.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_d.y); rf_gfx_vertex2f(vert_d.x, vert_d.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_c.y); rf_gfx_vertex2f(vert_d.x, vert_c.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_a.x, coord_c.y); rf_gfx_vertex2f(vert_a.x, vert_c.y); // Top-left corner for texture and quad + } + else if (n_patch_info.type == RF_NPT_3PATCH_HORIZONTAL) + { + // LEFT QUAD + // ----------------------------------------------------------- + // rf_texture coords Vertices + rf_gfx_tex_coord2f(coord_a.x, coord_d.y); rf_gfx_vertex2f(vert_a.x, vert_d.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_d.y); rf_gfx_vertex2f(vert_b.x, vert_d.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_a.y); rf_gfx_vertex2f(vert_b.x, vert_a.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_a.x, coord_a.y); rf_gfx_vertex2f(vert_a.x, vert_a.y); // Top-left corner for texture and quad + if (draw_center) + { + // CENTER QUAD + // ----------------------------------------------------------- + // rf_texture coords Vertices + rf_gfx_tex_coord2f(coord_b.x, coord_d.y); rf_gfx_vertex2f(vert_b.x, vert_d.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_d.y); rf_gfx_vertex2f(vert_c.x, vert_d.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_a.y); rf_gfx_vertex2f(vert_c.x, vert_a.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_b.x, coord_a.y); rf_gfx_vertex2f(vert_b.x, vert_a.y); // Top-left corner for texture and quad + } + // RIGHT QUAD + // ----------------------------------------------------------- + // rf_texture coords Vertices + rf_gfx_tex_coord2f(coord_c.x, coord_d.y); rf_gfx_vertex2f(vert_c.x, vert_d.y); // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_d.y); rf_gfx_vertex2f(vert_d.x, vert_d.y); // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f(coord_d.x, coord_a.y); rf_gfx_vertex2f(vert_d.x, vert_a.y); // Top-right corner for texture and quad + rf_gfx_tex_coord2f(coord_c.x, coord_a.y); rf_gfx_vertex2f(vert_c.x, vert_a.y); // Top-left corner for texture and quad + } + rf_gfx_end(); + rf_gfx_pop_matrix(); + + rf_gfx_disable_texture(); + + } +} + +// Draw text (using default font) +RF_API void rf_draw_string(const char* text, int text_len, int posX, int posY, int fontSize, rf_color color) +{ + // Check if default font has been loaded + if (rf_get_default_font().texture.id == 0 || text_len == 0) return; + + rf_vec2 position = { (float)posX, (float)posY }; + + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; + + rf_draw_string_ex(rf_get_default_font(), text, text_len, position, (float)fontSize, (float)spacing, color); +} + +// Draw text with custom font +RF_API void rf_draw_string_ex(rf_font font, const char* text, int text_len, rf_vec2 position, float font_size, float spacing, rf_color tint) +{ + int text_offset_y = 0; // Required for line break! + float text_offset_x = 0.0f; // Offset between characters + float scale_factor = 0.0f; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + scale_factor = font_size / font.base_size; + + for (rf_int i = 0; i < text_len; i++) + { + rf_decoded_rune decoded_rune = rf_decode_utf8_char(&text[i], text_len - i); + letter = decoded_rune.codepoint; + index = rf_get_glyph_index(font, letter); + + // NOTE: Normally we exit the decoding sequence as soon as a bad unsigned char is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set 'next = 1' + if (letter == 0x3f) decoded_rune.bytes_processed = 1; + i += (decoded_rune.bytes_processed - 1); + + if (letter == '\n') + { + // NOTE: Fixed line spacing of 1.5 lines + text_offset_y += (int)((font.base_size + font.base_size/2)*scale_factor); + text_offset_x = 0.0f; + } + else + { + if (letter != ' ') + { + rf_rec src_rec = font.glyphs[index].rec; + rf_rec dst_rec = { position.x + text_offset_x + font.glyphs[index].offset_x * scale_factor, + position.y + text_offset_y + font.glyphs[index].offset_y * scale_factor, + font.glyphs[index].width * scale_factor, + font.glyphs[index].height * scale_factor }; + rf_draw_texture_region(font.texture, src_rec, dst_rec, (rf_vec2){0}, 0.0f, tint); + } + + if (font.glyphs[index].advance_x == 0) text_offset_x += ((float)font.glyphs[index].width * scale_factor + spacing); + else text_offset_x += ((float)font.glyphs[index].advance_x * scale_factor + spacing); + } + } +} + +// Draw text wrapped +RF_API void rf_draw_string_wrap(rf_font font, const char* text, int text_len, rf_vec2 position, float font_size, float spacing, rf_color tint, float wrap_width, rf_text_wrap_mode mode) +{ + rf_rec rec = { 0, 0, wrap_width, FLT_MAX }; + rf_draw_string_rec(font, text, text_len, rec, font_size, spacing, mode, tint); +} + +// Draw text using font inside rectangle limits +RF_API void rf_draw_string_rec(rf_font font, const char* text, int text_len, rf_rec rec, float font_size, float spacing, rf_text_wrap_mode wrap, rf_color tint) +{ + int text_offset_x = 0; // Offset between characters + int text_offset_y = 0; // Required for line break! + float scale_factor = 0.0f; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + scale_factor = font_size/font.base_size; + + enum + { + MEASURE_WORD_WRAP_STATE = 0, + MEASURE_RESULT_STATE = 1 + }; + + int state = wrap == RF_WORD_WRAP ? MEASURE_WORD_WRAP_STATE : MEASURE_RESULT_STATE; + int start_line = -1; // Index where to begin drawing (where a line begins) + int end_line = -1; // Index where to stop drawing (where a line ends) + int lastk = -1; // Holds last value of the character position + + for (rf_int i = 0, k = 0; i < text_len; i++, k++) + { + int glyph_width = 0; + + rf_decoded_rune decoded_rune = rf_decode_utf8_char(&text[i], text_len - i); + letter = decoded_rune.codepoint; + index = rf_get_glyph_index(font, letter); + + // NOTE: normally we exit the decoding sequence as soon as a bad unsigned char is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) decoded_rune.bytes_processed = 1; + i += decoded_rune.bytes_processed - 1; + + if (letter != '\n') + { + glyph_width = (font.glyphs[index].advance_x == 0)? + (int)(font.glyphs[index].width*scale_factor + spacing): + (int)(font.glyphs[index].advance_x*scale_factor + spacing); + } + + // NOTE: When wrap is ON we first measure how much of the text we can draw before going outside of the rec container + // We store this info in start_line and end_line, then we change states, draw the text between those two variables + // and change states again and again recursively until the end of the text (or until we get outside of the container). + // When wrap is OFF we don't need the measure state so we go to the drawing state immediately + // and begin drawing on the next line before we can get outside the container. + if (state == MEASURE_WORD_WRAP_STATE) + { + // TODO: there are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more + // See: http://jkorpela.fi/chars/spaces.html + if ((letter == ' ') || (letter == '\t') || (letter == '\n')) end_line = i; + + if ((text_offset_x + glyph_width + 1) >= rec.width) + { + end_line = (end_line < 1)? i : end_line; + if (i == end_line) end_line -= decoded_rune.bytes_processed; + if ((start_line + decoded_rune.bytes_processed) == end_line) end_line = i - decoded_rune.bytes_processed; + state = !state; + } + else if ((i + 1) == text_len) + { + end_line = i; + state = !state; + } + else if (letter == '\n') + { + state = !state; + } + + if (state == MEASURE_RESULT_STATE) + { + text_offset_x = 0; + i = start_line; + glyph_width = 0; + + // Save character position when we switch states + int tmp = lastk; + lastk = k - 1; + k = tmp; + } + } + else + { + if (letter == '\n') + { + if (!wrap) + { + text_offset_y += (int)((font.base_size + font.base_size/2)*scale_factor); + text_offset_x = 0; + } + } + else + { + if (!wrap && ((text_offset_x + glyph_width + 1) >= rec.width)) + { + text_offset_y += (int)((font.base_size + font.base_size/2)*scale_factor); + text_offset_x = 0; + } + + if ((text_offset_y + (int)(font.base_size*scale_factor)) > rec.height) break; + + // Draw glyph + if ((letter != ' ') && (letter != '\t')) + { + rf_draw_texture_region(font.texture, font.glyphs[index].rec, + (rf_rec) { + rec.x + text_offset_x + font.glyphs[index].offset_x * scale_factor, + rec.y + text_offset_y + font.glyphs[index].offset_y * scale_factor, + font.glyphs[index].width * scale_factor, + font.glyphs[index].height * scale_factor + }, + (rf_vec2){ 0, 0 }, 0.0f, tint); + } + } + + if (wrap && (i == end_line)) + { + text_offset_y += (int)((font.base_size + font.base_size/2)*scale_factor); + text_offset_x = 0; + start_line = end_line; + end_line = -1; + glyph_width = 0; + k = lastk; + state = !state; + } + } + + text_offset_x += glyph_width; + } +} + +RF_API void rf_draw_text(const char* text, int posX, int posY, int font_size, rf_color color) { rf_draw_string(text, strlen(text), posX, posY, font_size, color); } + +RF_API void rf_draw_text_ex(rf_font font, const char* text, rf_vec2 position, float fontSize, float spacing, rf_color tint) { rf_draw_string_ex(font, text, strlen(text), position, fontSize, spacing, tint); } + +RF_API void rf_draw_text_wrap(rf_font font, const char* text, rf_vec2 position, float font_size, float spacing, rf_color tint, float wrap_width, rf_text_wrap_mode mode) { rf_draw_string_wrap(font, text, strlen(text), position, font_size, spacing, tint, wrap_width, mode); } + +RF_API void rf_draw_text_rec(rf_font font, const char* text, rf_rec rec, float font_size, float spacing, rf_text_wrap_mode wrap, rf_color tint) { rf_draw_string_rec(font, text, strlen(text), rec, font_size, spacing, wrap, tint); } + +RF_API void rf_draw_line3d(rf_vec3 start_pos, rf_vec3 end_pos, rf_color color) +{ + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_vertex3f(start_pos.x, start_pos.y, start_pos.z); + rf_gfx_vertex3f(end_pos.x, end_pos.y, end_pos.z); + rf_gfx_end(); +} + +// Draw a circle in 3D world space +RF_API void rf_draw_circle3d(rf_vec3 center, float radius, rf_vec3 rotation_axis, float rotationAngle, rf_color color) +{ + if (rf_gfx_check_buffer_limit(2 * 36)) rf_gfx_draw(); + + rf_gfx_push_matrix(); + rf_gfx_translatef(center.x, center.y, center.z); + rf_gfx_rotatef(rotationAngle, rotation_axis.x, rotation_axis.y, rotation_axis.z); + + rf_gfx_begin(RF_LINES); + for (rf_int i = 0; i < 360; i += 10) + { + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_vertex3f(sinf(RF_DEG2RAD*i)*radius, cosf(RF_DEG2RAD*i)*radius, 0.0f); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*(i + 10))*radius, cosf(RF_DEG2RAD*(i + 10))*radius, 0.0f); + } + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw cube +// NOTE: Cube position is the center position +RF_API void rf_draw_cube(rf_vec3 position, float width, float height, float length, rf_color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + if (rf_gfx_check_buffer_limit(36)) rf_gfx_draw(); + + rf_gfx_push_matrix(); + // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) + rf_gfx_translatef(position.x, position.y, position.z); + //rf_gfx_rotatef(45, 0, 1, 0); + //rf_gfx_scalef(1.0f, 1.0f, 1.0f); // NOTE: Vertices are directly scaled on definition + + rf_gfx_begin(RF_TRIANGLES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + // Front face + rf_gfx_vertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rf_gfx_vertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rf_gfx_vertex3f(x - width/2, y + height/2, z + length/2); // Top Left + + rf_gfx_vertex3f(x + width/2, y + height/2, z + length/2); // Top Right + rf_gfx_vertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rf_gfx_vertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + + // Back face + rf_gfx_vertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left + rf_gfx_vertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rf_gfx_vertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + + rf_gfx_vertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rf_gfx_vertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rf_gfx_vertex3f(x - width/2, y + height/2, z - length/2); // Top Left + + // Top face + rf_gfx_vertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rf_gfx_vertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left + rf_gfx_vertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + rf_gfx_vertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rf_gfx_vertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rf_gfx_vertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + // Bottom face + rf_gfx_vertex3f(x - width/2, y - height/2, z - length/2); // Top Left + rf_gfx_vertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rf_gfx_vertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + + rf_gfx_vertex3f(x + width/2, y - height/2, z - length/2); // Top Right + rf_gfx_vertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rf_gfx_vertex3f(x - width/2, y - height/2, z - length/2); // Top Left + + // Right face + rf_gfx_vertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rf_gfx_vertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rf_gfx_vertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + rf_gfx_vertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left + rf_gfx_vertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rf_gfx_vertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + // Left face + rf_gfx_vertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rf_gfx_vertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rf_gfx_vertex3f(x - width/2, y + height/2, z - length/2); // Top Right + + rf_gfx_vertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rf_gfx_vertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rf_gfx_vertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw cube wires +RF_API void rf_draw_cube_wires(rf_vec3 position, float width, float height, float length, rf_color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + if (rf_gfx_check_buffer_limit(36)) rf_gfx_draw(); + + rf_gfx_push_matrix(); + rf_gfx_translatef(position.x, position.y, position.z); + + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + // Front Face ----------------------------------------------------- + // Bottom Line + rf_gfx_vertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + rf_gfx_vertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + + // Left Line + rf_gfx_vertex3f(x+width/2, y-height/2, z+length/2); // Bottom Right + rf_gfx_vertex3f(x+width/2, y+height/2, z+length/2); // Top Right + + // Top Line + rf_gfx_vertex3f(x+width/2, y+height/2, z+length/2); // Top Right + rf_gfx_vertex3f(x-width/2, y+height/2, z+length/2); // Top Left + + // Right Line + rf_gfx_vertex3f(x-width/2, y+height/2, z+length/2); // Top Left + rf_gfx_vertex3f(x-width/2, y-height/2, z+length/2); // Bottom Left + + // Back Face ------------------------------------------------------ + // Bottom Line + rf_gfx_vertex3f(x-width/2, y-height/2, z-length/2); // Bottom Left + rf_gfx_vertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + + // Left Line + rf_gfx_vertex3f(x+width/2, y-height/2, z-length/2); // Bottom Right + rf_gfx_vertex3f(x+width/2, y+height/2, z-length/2); // Top Right + + // Top Line + rf_gfx_vertex3f(x+width/2, y+height/2, z-length/2); // Top Right + rf_gfx_vertex3f(x-width/2, y+height/2, z-length/2); // Top Left + + // Right Line + rf_gfx_vertex3f(x-width/2, y+height/2, z-length/2); // Top Left + rf_gfx_vertex3f(x-width/2, y-height/2, z-length/2); // Bottom Left + + // Top Face ------------------------------------------------------- + // Left Line + rf_gfx_vertex3f(x-width/2, y+height/2, z+length/2); // Top Left Front + rf_gfx_vertex3f(x-width/2, y+height/2, z-length/2); // Top Left Back + + // Right Line + rf_gfx_vertex3f(x+width/2, y+height/2, z+length/2); // Top Right Front + rf_gfx_vertex3f(x+width/2, y+height/2, z-length/2); // Top Right Back + + // Bottom Face --------------------------------------------------- + // Left Line + rf_gfx_vertex3f(x-width/2, y-height/2, z+length/2); // Top Left Front + rf_gfx_vertex3f(x-width/2, y-height/2, z-length/2); // Top Left Back + + // Right Line + rf_gfx_vertex3f(x+width/2, y-height/2, z+length/2); // Top Right Front + rf_gfx_vertex3f(x+width/2, y-height/2, z-length/2); // Top Right Back + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw cube +// NOTE: Cube position is the center position +RF_API void rf_draw_cube_texture(rf_texture2d texture, rf_vec3 position, float width, float height, float length, rf_color color) +{ + float x = position.x; + float y = position.y; + float z = position.z; + + if (rf_gfx_check_buffer_limit(36)) rf_gfx_draw(); + + rf_gfx_enable_texture(texture.id); + + //rf_gfx_push_matrix(); + // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) + //rf_gfx_translatef(2.0f, 0.0f, 0.0f); + //rf_gfx_rotatef(45, 0, 1, 0); + //rf_gfx_scalef(2.0f, 2.0f, 2.0f); + + rf_gfx_begin(RF_QUADS); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + // Front Face + rf_gfx_normal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer + rf_gfx_tex_coord2f(0.0f, 0.0f); rf_gfx_vertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 0.0f); rf_gfx_vertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 1.0f); rf_gfx_vertex3f(x + width/2, y + height/2, z + length/2); // Top Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 1.0f); rf_gfx_vertex3f(x - width/2, y + height/2, z + length/2); // Top Left Of The rf_texture and Quad + // Back Face + rf_gfx_normal3f(0.0f, 0.0f, - 1.0f); // Normal Pointing Away From Viewer + rf_gfx_tex_coord2f(1.0f, 0.0f); rf_gfx_vertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 1.0f); rf_gfx_vertex3f(x - width/2, y + height/2, z - length/2); // Top Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 1.0f); rf_gfx_vertex3f(x + width/2, y + height/2, z - length/2); // Top Left Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 0.0f); rf_gfx_vertex3f(x + width/2, y - height/2, z - length/2); // Bottom Left Of The rf_texture and Quad + // Top Face + rf_gfx_normal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up + rf_gfx_tex_coord2f(0.0f, 1.0f); rf_gfx_vertex3f(x - width/2, y + height/2, z - length/2); // Top Left Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 0.0f); rf_gfx_vertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 0.0f); rf_gfx_vertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 1.0f); rf_gfx_vertex3f(x + width/2, y + height/2, z - length/2); // Top Right Of The rf_texture and Quad + // Bottom Face + rf_gfx_normal3f(0.0f, - 1.0f, 0.0f); // Normal Pointing Down + rf_gfx_tex_coord2f(1.0f, 1.0f); rf_gfx_vertex3f(x - width/2, y - height/2, z - length/2); // Top Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 1.0f); rf_gfx_vertex3f(x + width/2, y - height/2, z - length/2); // Top Left Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 0.0f); rf_gfx_vertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 0.0f); rf_gfx_vertex3f(x - width/2, y - height/2, z + length/2); // Bottom Right Of The rf_texture and Quad + // Right face + rf_gfx_normal3f(1.0f, 0.0f, 0.0f); // Normal Pointing Right + rf_gfx_tex_coord2f(1.0f, 0.0f); rf_gfx_vertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 1.0f); rf_gfx_vertex3f(x + width/2, y + height/2, z - length/2); // Top Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 1.0f); rf_gfx_vertex3f(x + width/2, y + height/2, z + length/2); // Top Left Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 0.0f); rf_gfx_vertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left Of The rf_texture and Quad + // Left Face + rf_gfx_normal3f(-1.0f, 0.0f, 0.0f); // Normal Pointing Left + rf_gfx_tex_coord2f(0.0f, 0.0f); rf_gfx_vertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 0.0f); rf_gfx_vertex3f(x - width/2, y - height/2, z + length/2); // Bottom Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(1.0f, 1.0f); rf_gfx_vertex3f(x - width/2, y + height/2, z + length/2); // Top Right Of The rf_texture and Quad + rf_gfx_tex_coord2f(0.0f, 1.0f); rf_gfx_vertex3f(x - width/2, y + height/2, z - length/2); // Top Left Of The rf_texture and Quad + rf_gfx_end(); + //rf_gfx_pop_matrix(); + + rf_gfx_disable_texture(); +} + +// Draw sphere +RF_API void rf_draw_sphere(rf_vec3 center_pos, float radius, rf_color color) +{ + rf_draw_sphere_ex(center_pos, radius, 16, 16, color); +} + +// Draw sphere with extended parameters +RF_API void rf_draw_sphere_ex(rf_vec3 center_pos, float radius, int rings, int slices, rf_color color) +{ + int num_vertex = (rings + 2)*slices*6; + if (rf_gfx_check_buffer_limit(num_vertex)) rf_gfx_draw(); + + rf_gfx_push_matrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rf_gfx_translatef(center_pos.x, center_pos.y, center_pos.z); + rf_gfx_scalef(radius, radius, radius); + + rf_gfx_begin(RF_TRIANGLES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + for (rf_int i = 0; i < (rings + 2); i++) + { + for (rf_int j = 0; j < slices; j++) + { + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*i))*sinf(RF_DEG2RAD*(j * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*i))*cosf(RF_DEG2RAD*(j * 360/slices))); + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(RF_DEG2RAD*((j+1) * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(RF_DEG2RAD*((j+1) * 360/slices))); + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(RF_DEG2RAD*(j * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(RF_DEG2RAD*(j * 360/slices))); + + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*i))*sinf(RF_DEG2RAD*(j * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*i))*cosf(RF_DEG2RAD*(j * 360/slices))); + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i)))*sinf(RF_DEG2RAD*((j+1) * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*(i))), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i)))*cosf(RF_DEG2RAD*((j+1) * 360/slices))); + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(RF_DEG2RAD*((j+1) * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(RF_DEG2RAD*((j+1) * 360/slices))); + } + } + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw sphere wires +RF_API void rf_draw_sphere_wires(rf_vec3 center_pos, float radius, int rings, int slices, rf_color color) +{ + int num_vertex = (rings + 2)*slices*6; + if (rf_gfx_check_buffer_limit(num_vertex)) rf_gfx_draw(); + + rf_gfx_push_matrix(); + // NOTE: Transformation is applied in inverse order (scale -> translate) + rf_gfx_translatef(center_pos.x, center_pos.y, center_pos.z); + rf_gfx_scalef(radius, radius, radius); + + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + for (rf_int i = 0; i < (rings + 2); i++) + { + for (rf_int j = 0; j < slices; j++) + { + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*i))*sinf(RF_DEG2RAD*(j * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*i))*cosf(RF_DEG2RAD*(j * 360/slices))); + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(RF_DEG2RAD*((j+1) * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(RF_DEG2RAD*((j+1) * 360/slices))); + + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(RF_DEG2RAD*((j+1) * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(RF_DEG2RAD*((j+1) * 360/slices))); + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(RF_DEG2RAD*(j * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(RF_DEG2RAD*(j * 360/slices))); + + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(RF_DEG2RAD*(j * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1))), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(RF_DEG2RAD*(j * 360/slices))); + + rf_gfx_vertex3f(cosf(RF_DEG2RAD*(270+(180/(rings + 1))*i))*sinf(RF_DEG2RAD*(j * 360/slices)), + sinf(RF_DEG2RAD*(270+(180/(rings + 1))*i)), + cosf(RF_DEG2RAD*(270+(180/(rings + 1))*i))*cosf(RF_DEG2RAD*(j * 360/slices))); + } + } + + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw a cylinder +// NOTE: It could be also used for pyramid and cone +RF_API void rf_draw_cylinder(rf_vec3 position, float radius_top, float radius_bottom, float height, int sides, rf_color color) +{ + if (sides < 3) sides = 3; + + int num_vertex = sides*6; + if (rf_gfx_check_buffer_limit(num_vertex)) rf_gfx_draw(); + + rf_gfx_push_matrix(); + rf_gfx_translatef(position.x, position.y, position.z); + + rf_gfx_begin(RF_TRIANGLES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + if (radius_top > 0) + { + // Draw Body ------------------------------------------------------------------------------------- + for (rf_int i = 0; i < 360; i += 360 / sides) + { + rf_gfx_vertex3f(sinf(RF_DEG2RAD * i) * radius_bottom, 0, cosf(RF_DEG2RAD * i) * radius_bottom); // Bottom Left + rf_gfx_vertex3f(sinf(RF_DEG2RAD * (i + 360 / sides)) * radius_bottom, 0, cosf(RF_DEG2RAD * (i + 360 / sides)) * radius_bottom); // Bottom Right + rf_gfx_vertex3f(sinf(RF_DEG2RAD * (i + 360 / sides)) * radius_top, height, cosf(RF_DEG2RAD * (i + 360 / sides)) * radius_top); // Top Right + + rf_gfx_vertex3f(sinf(RF_DEG2RAD * i) * radius_top, height, cosf(RF_DEG2RAD * i) * radius_top); // Top Left + rf_gfx_vertex3f(sinf(RF_DEG2RAD * i) * radius_bottom, 0, cosf(RF_DEG2RAD * i) * radius_bottom); // Bottom Left + rf_gfx_vertex3f(sinf(RF_DEG2RAD * (i + 360 / sides)) * radius_top, height, cosf(RF_DEG2RAD * (i + 360 / sides)) * radius_top); // Top Right + } + + // Draw Cap -------------------------------------------------------------------------------------- + for (rf_int i = 0; i < 360; i += 360/sides) + { + rf_gfx_vertex3f(0, height, 0); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*i)*radius_top, height, cosf(RF_DEG2RAD*i)*radius_top); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*(i + 360/sides))*radius_top, height, cosf(RF_DEG2RAD*(i + 360/sides))*radius_top); + } + } + else + { + // Draw Cone ------------------------------------------------------------------------------------- + for (rf_int i = 0; i < 360; i += 360/sides) + { + rf_gfx_vertex3f(0, height, 0); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*i)*radius_bottom, 0, cosf(RF_DEG2RAD*i)*radius_bottom); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*(i + 360/sides))*radius_bottom, 0, cosf(RF_DEG2RAD*(i + 360/sides))*radius_bottom); + } + } + + // Draw Base ----------------------------------------------------------------------------------------- + for (rf_int i = 0; i < 360; i += 360/sides) + { + rf_gfx_vertex3f(0, 0, 0); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*(i + 360/sides))*radius_bottom, 0, cosf(RF_DEG2RAD*(i + 360/sides))*radius_bottom); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*i)*radius_bottom, 0, cosf(RF_DEG2RAD*i)*radius_bottom); + } + + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw a wired cylinder +// NOTE: It could be also used for pyramid and cone +RF_API void rf_draw_cylinder_wires(rf_vec3 position, float radius_top, float radius_bottom, float height, int sides, rf_color color) +{ + if (sides < 3) sides = 3; + + int num_vertex = sides*8; + if (rf_gfx_check_buffer_limit(num_vertex)) rf_gfx_draw(); + + rf_gfx_push_matrix(); + rf_gfx_translatef(position.x, position.y, position.z); + + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + for (rf_int i = 0; i < 360; i += 360/sides) + { + rf_gfx_vertex3f(sinf(RF_DEG2RAD*i)*radius_bottom, 0, cosf(RF_DEG2RAD*i)*radius_bottom); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*(i + 360/sides))*radius_bottom, 0, cosf(RF_DEG2RAD*(i + 360/sides))*radius_bottom); + + rf_gfx_vertex3f(sinf(RF_DEG2RAD*(i + 360/sides))*radius_bottom, 0, cosf(RF_DEG2RAD*(i + 360/sides))*radius_bottom); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*(i + 360/sides))*radius_top, height, cosf(RF_DEG2RAD*(i + 360/sides))*radius_top); + + rf_gfx_vertex3f(sinf(RF_DEG2RAD*(i + 360/sides))*radius_top, height, cosf(RF_DEG2RAD*(i + 360/sides))*radius_top); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*i)*radius_top, height, cosf(RF_DEG2RAD*i)*radius_top); + + rf_gfx_vertex3f(sinf(RF_DEG2RAD*i)*radius_top, height, cosf(RF_DEG2RAD*i)*radius_top); + rf_gfx_vertex3f(sinf(RF_DEG2RAD*i)*radius_bottom, 0, cosf(RF_DEG2RAD*i)*radius_bottom); + } + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw a plane +RF_API void rf_draw_plane(rf_vec3 center_pos, rf_vec2 size, rf_color color) +{ + if (rf_gfx_check_buffer_limit(4)) rf_gfx_draw(); + + // NOTE: Plane is always created on XZ ground + rf_gfx_push_matrix(); + rf_gfx_translatef(center_pos.x, center_pos.y, center_pos.z); + rf_gfx_scalef(size.x, 1.0f, size.y); + + rf_gfx_begin(RF_QUADS); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_normal3f(0.0f, 1.0f, 0.0f); + + rf_gfx_vertex3f(-0.5f, 0.0f, -0.5f); + rf_gfx_vertex3f(-0.5f, 0.0f, 0.5f); + rf_gfx_vertex3f(0.5f, 0.0f, 0.5f); + rf_gfx_vertex3f(0.5f, 0.0f, -0.5f); + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw a ray line +RF_API void rf_draw_ray(rf_ray ray, rf_color color) +{ + float scale = 10000; + + rf_gfx_begin(RF_LINES); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + rf_gfx_color4ub(color.r, color.g, color.b, color.a); + + rf_gfx_vertex3f(ray.position.x, ray.position.y, ray.position.z); + rf_gfx_vertex3f(ray.position.x + ray.direction.x*scale, ray.position.y + ray.direction.y*scale, ray.position.z + ray.direction.z*scale); + rf_gfx_end(); +} + +// Draw a grid centered at (0, 0, 0) +RF_API void rf_draw_grid(int slices, float spacing) +{ + int half_slices = slices/2; + + if (rf_gfx_check_buffer_limit(slices * 4)) rf_gfx_draw(); + + rf_gfx_begin(RF_LINES); + for (rf_int i = -half_slices; i <= half_slices; i++) + { + if (i == 0) + { + rf_gfx_color3f(0.5f, 0.5f, 0.5f); + rf_gfx_color3f(0.5f, 0.5f, 0.5f); + rf_gfx_color3f(0.5f, 0.5f, 0.5f); + rf_gfx_color3f(0.5f, 0.5f, 0.5f); + } + else + { + rf_gfx_color3f(0.75f, 0.75f, 0.75f); + rf_gfx_color3f(0.75f, 0.75f, 0.75f); + rf_gfx_color3f(0.75f, 0.75f, 0.75f); + rf_gfx_color3f(0.75f, 0.75f, 0.75f); + } + + rf_gfx_vertex3f((float)i*spacing, 0.0f, (float)-half_slices*spacing); + rf_gfx_vertex3f((float)i*spacing, 0.0f, (float)half_slices*spacing); + + rf_gfx_vertex3f((float)-half_slices*spacing, 0.0f, (float)i*spacing); + rf_gfx_vertex3f((float)half_slices*spacing, 0.0f, (float)i*spacing); + } + rf_gfx_end(); +} + +// Draw gizmo +RF_API void rf_draw_gizmo(rf_vec3 position) +{ + // NOTE: RGB = XYZ + float length = 1.0f; + + rf_gfx_push_matrix(); + rf_gfx_translatef(position.x, position.y, position.z); + rf_gfx_scalef(length, length, length); + + rf_gfx_begin(RF_LINES); + rf_gfx_color3f(1.0f, 0.0f, 0.0f); rf_gfx_vertex3f(0.0f, 0.0f, 0.0f); + rf_gfx_color3f(1.0f, 0.0f, 0.0f); rf_gfx_vertex3f(1.0f, 0.0f, 0.0f); + + rf_gfx_color3f(0.0f, 1.0f, 0.0f); rf_gfx_vertex3f(0.0f, 0.0f, 0.0f); + rf_gfx_color3f(0.0f, 1.0f, 0.0f); rf_gfx_vertex3f(0.0f, 1.0f, 0.0f); + + rf_gfx_color3f(0.0f, 0.0f, 1.0f); rf_gfx_vertex3f(0.0f, 0.0f, 0.0f); + rf_gfx_color3f(0.0f, 0.0f, 1.0f); rf_gfx_vertex3f(0.0f, 0.0f, 1.0f); + rf_gfx_end(); + rf_gfx_pop_matrix(); +} + +// Draw a model (with texture if set) +RF_API void rf_draw_model(rf_model model, rf_vec3 position, float scale, rf_color tint) +{ + rf_vec3 vScale = { scale, scale, scale }; + rf_vec3 rotationAxis = { 0.0f, 1.0f, 0.0f }; + + rf_draw_model_ex(model, position, rotationAxis, 0.0f, vScale, tint); +} + +// Draw a model with extended parameters +RF_API void rf_draw_model_ex(rf_model model, rf_vec3 position, rf_vec3 rotation_axis, float rotationAngle, rf_vec3 scale, rf_color tint) +{ + // Calculate transformation matrix from function parameters + // Get transform matrix (rotation -> scale -> translation) + rf_mat mat_scale = rf_mat_scale(scale.x, scale.y, scale.z); + rf_mat mat_rotation = rf_mat_rotate(rotation_axis, rotationAngle * RF_DEG2RAD); + rf_mat mat_translation = rf_mat_translate(position.x, position.y, position.z); + + rf_mat mat_transform = rf_mat_mul(rf_mat_mul(mat_scale, mat_rotation), mat_translation); + + // Combine model transformation matrix (model.transform) with matrix generated by function parameters (mat_transform) + model.transform = rf_mat_mul(model.transform, mat_transform); + + for (rf_int i = 0; i < model.mesh_count; i++) + { + // TODO: Review color + tint premultiplication mechanism + rf_color color = model.materials[model.mesh_material[i]].maps[RF_MAP_DIFFUSE].color; + + rf_color color_tint = RF_WHITE; + color_tint.r = (((float)color.r/255.0)*((float)tint.r/255.0))*255; + color_tint.g = (((float)color.g/255.0)*((float)tint.g/255.0))*255; + color_tint.b = (((float)color.b/255.0)*((float)tint.b/255.0))*255; + color_tint.a = (((float)color.a/255.0)*((float)tint.a/255.0))*255; + + model.materials[model.mesh_material[i]].maps[RF_MAP_DIFFUSE].color = color_tint; + rf_gfx_draw_mesh(model.meshes[i], model.materials[model.mesh_material[i]], model.transform); + model.materials[model.mesh_material[i]].maps[RF_MAP_DIFFUSE].color = color; + } +} + +// Draw a model wires (with texture if set) with extended parameters +RF_API void rf_draw_model_wires(rf_model model, rf_vec3 position, rf_vec3 rotation_axis, float rotationAngle, rf_vec3 scale, rf_color tint) +{ + rf_gfx_enable_wire_mode(); + + rf_draw_model_ex(model, position, rotation_axis, rotationAngle, scale, tint); + + rf_gfx_disable_wire_mode(); +} + +// Draw a bounding box with wires +RF_API void rf_draw_bounding_box(rf_bounding_box box, rf_color color) +{ + rf_vec3 size; + + size.x = (float)fabs(box.max.x - box.min.x); + size.y = (float)fabs(box.max.y - box.min.y); + size.z = (float)fabs(box.max.z - box.min.z); + + rf_vec3 center = {box.min.x + size.x / 2.0f, box.min.y + size.y / 2.0f, box.min.z + size.z / 2.0f }; + + rf_draw_cube_wires(center, size.x, size.y, size.z, color); +} + +// Draw a billboard +RF_API void rf_draw_billboard(rf_camera3d camera, rf_texture2d texture, rf_vec3 center, float size, rf_color tint) +{ + rf_rec source_rec = {0.0f, 0.0f, (float)texture.width, (float)texture.height }; + + rf_draw_billboard_rec(camera, texture, source_rec, center, size, tint); +} + +// Draw a billboard (part of a texture defined by a rectangle) +RF_API void rf_draw_billboard_rec(rf_camera3d camera, rf_texture2d texture, rf_rec source_rec, rf_vec3 center, float size, rf_color tint) +{ + // NOTE: Billboard size will maintain source_rec aspect ratio, size will represent billboard width + rf_vec2 size_ratio = {size, size * (float)source_rec.height / source_rec.width }; + + rf_mat mat_view = rf_mat_look_at(camera.position, camera.target, camera.up); + + rf_vec3 right = {mat_view.m0, mat_view.m4, mat_view.m8 }; + //rf_vec3 up = { mat_view.m1, mat_view.m5, mat_view.m9 }; + + // NOTE: Billboard locked on axis-Y + rf_vec3 up = {0.0f, 1.0f, 0.0f }; + /* + a-------b + | | + | * | + | | + d-------c + */ + right = rf_vec3_scale(right, size_ratio.x / 2); + up = rf_vec3_scale(up, size_ratio.y / 2); + + rf_vec3 p1 = rf_vec3_add(right, up); + rf_vec3 p2 = rf_vec3_sub(right, up); + + rf_vec3 a = rf_vec3_sub(center, p2); + rf_vec3 b = rf_vec3_add(center, p1); + rf_vec3 c = rf_vec3_add(center, p2); + rf_vec3 d = rf_vec3_sub(center, p1); + + if (rf_gfx_check_buffer_limit(4)) rf_gfx_draw(); + + rf_gfx_enable_texture(texture.id); + + rf_gfx_begin(RF_QUADS); + rf_gfx_color4ub(tint.r, tint.g, tint.b, tint.a); + + // Bottom-left corner for texture and quad + rf_gfx_tex_coord2f((float)source_rec.x/texture.width, (float)source_rec.y/texture.height); + rf_gfx_vertex3f(a.x, a.y, a.z); + + // Top-left corner for texture and quad + rf_gfx_tex_coord2f((float)source_rec.x/texture.width, (float)(source_rec.y + source_rec.height)/texture.height); + rf_gfx_vertex3f(d.x, d.y, d.z); + + // Top-right corner for texture and quad + rf_gfx_tex_coord2f((float)(source_rec.x + source_rec.width)/texture.width, (float)(source_rec.y + source_rec.height)/texture.height); + rf_gfx_vertex3f(c.x, c.y, c.z); + + // Bottom-right corner for texture and quad + rf_gfx_tex_coord2f((float)(source_rec.x + source_rec.width)/texture.width, (float)source_rec.y/texture.height); + rf_gfx_vertex3f(b.x, b.y, b.z); + rf_gfx_end(); + + rf_gfx_disable_texture(); +} +/*** End of inlined file: rayfork-drawing.c ***/ + + +/*** Start of inlined file: rayfork-ez.c ***/ +#if !defined(RAYFORK_NO_EZ_API) +#pragma region other stuff + +RF_API rf_material rf_load_default_material_ez() { return rf_load_default_material(RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_get_screen_data_ez() +{ + if (rf_ctx.render_width < 0 || rf_ctx.render_height < 0) return (rf_image) {0}; + + int size = rf_ctx.render_width * rf_ctx.render_height; + rf_color* dst = RF_ALLOC(RF_DEFAULT_ALLOCATOR, size * sizeof(rf_color)); + rf_image result = rf_get_screen_data(dst, size); + + return result; +} +RF_API rf_base64_output rf_decode_base64_ez(const unsigned char* input) { return rf_decode_base64(input, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_gfx_read_texture_pixels_ez(rf_texture2d texture) { return rf_gfx_read_texture_pixels(texture, RF_DEFAULT_ALLOCATOR); } + +#pragma endregion + +#pragma region image + +#pragma region extract image data functions +RF_API rf_color* rf_image_pixels_to_rgba32_ez(rf_image image) { return rf_image_pixels_to_rgba32(image, RF_DEFAULT_ALLOCATOR); } +RF_API rf_vec4* rf_image_compute_pixels_to_normalized_ez(rf_image image) { return rf_image_compute_pixels_to_normalized(image, RF_DEFAULT_ALLOCATOR); } +RF_API rf_palette rf_image_extract_palette_ez(rf_image image, int palette_size) { return rf_image_extract_palette(image, palette_size, RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region loading & unloading functions +RF_API rf_image rf_load_image_from_file_data_ez(const void* src, int src_size) { return rf_load_image_from_file_data(src, src_size, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_load_image_from_hdr_file_data_ez(const void* src, int src_size) { return rf_load_image_from_hdr_file_data(src, src_size, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_load_image_from_file_ez(const char* filename) { return rf_load_image_from_file(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API void rf_unload_image_ez(rf_image image) { rf_unload_image(image, RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region image manipulation +RF_API rf_image rf_image_copy_ez(rf_image image) { return rf_image_copy(image, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_image rf_image_crop_ez(rf_image image, rf_rec crop) { return rf_image_crop(image, crop, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_image rf_image_resize_ez(rf_image image, int new_width, int new_height) { return rf_image_resize(image, new_width, new_height, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_image_resize_nn_ez(rf_image image, int new_width, int new_height) { return rf_image_resize_nn(image, new_width, new_height, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_image rf_image_format_ez(rf_image image, rf_uncompressed_pixel_format new_format) { return rf_image_format(image, new_format, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_image rf_image_alpha_clear_ez(rf_image image, rf_color color, float threshold) { return rf_image_alpha_clear(image, color, threshold, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_image_alpha_premultiply_ez(rf_image image) { return rf_image_alpha_premultiply(image, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_image_alpha_crop_ez(rf_image image, float threshold) { return rf_image_alpha_crop(image, threshold, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_image_dither_ez(rf_image image, int r_bpp, int g_bpp, int b_bpp, int a_bpp) { return rf_image_dither(image, r_bpp, g_bpp, b_bpp, a_bpp, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_image rf_image_flip_vertical_ez(rf_image image) { return rf_image_flip_vertical(image, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_image_flip_horizontal_ez(rf_image image) { return rf_image_flip_horizontal(image, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_vec2 rf_get_seed_for_cellular_image_ez(int seeds_per_row, int tile_size, int i) { return rf_get_seed_for_cellular_image( + seeds_per_row, tile_size, i, RF_DEFAULT_RAND_PROC); } + +RF_API rf_image rf_gen_image_color_ez(int width, int height, rf_color color) { return rf_gen_image_color(width, height, color, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_gen_image_gradient_v_ez(int width, int height, rf_color top, rf_color bottom) { return rf_gen_image_gradient_v(width, height, top, bottom, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_gen_image_gradient_h_ez(int width, int height, rf_color left, rf_color right) { return rf_gen_image_gradient_h(width, height, left, right, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_gen_image_gradient_radial_ez(int width, int height, float density, rf_color inner, rf_color outer) { return rf_gen_image_gradient_radial(width, height, density, inner, outer, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_gen_image_checked_ez(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2) { return rf_gen_image_checked(width, height, checks_x, checks_y, col1, col2, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_gen_image_white_noise_ez(int width, int height, float factor) { return rf_gen_image_white_noise( + width, height, factor, RF_DEFAULT_RAND_PROC, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_gen_image_perlin_noise_ez(int width, int height, int offset_x, int offset_y, float scale) { return rf_gen_image_perlin_noise(width, height, offset_x, offset_y, scale, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_gen_image_cellular_ez(int width, int height, int tile_size) { return rf_gen_image_cellular(width, + height, + tile_size, + RF_DEFAULT_RAND_PROC, + RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region mipmaps +RF_API rf_mipmaps_image rf_image_gen_mipmaps_ez(rf_image image, int gen_mipmaps_count) { return rf_image_gen_mipmaps(image, gen_mipmaps_count, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API void rf_unload_mipmaps_image_ez(rf_mipmaps_image image) { rf_unload_mipmaps_image(image, RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region dds +RF_API rf_mipmaps_image rf_load_dds_image_ez(const void* src, int src_size) { return rf_load_dds_image(src, src_size, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mipmaps_image rf_load_dds_image_from_file_ez(const char* file) { return rf_load_dds_image_from_file(file, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +#pragma endregion + +#pragma region pkm +RF_API rf_image rf_load_pkm_image_ez(const void* src, int src_size) { return rf_load_pkm_image(src, src_size, RF_DEFAULT_ALLOCATOR); } +RF_API rf_image rf_load_pkm_image_from_file_ez(const char* file) { return rf_load_pkm_image_from_file(file, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +#pragma endregion + +#pragma region ktx +RF_API rf_mipmaps_image rf_load_ktx_image_ez(const void* src, int src_size) { return rf_load_ktx_image(src, src_size, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mipmaps_image rf_load_ktx_image_from_file_ez(const char* file) { return rf_load_ktx_image_from_file(file, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +#pragma endregion + +#pragma endregion + +#pragma region gif +RF_API rf_gif rf_load_animated_gif_ez(const void* data, int data_size) { return rf_load_animated_gif(data, data_size, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_gif rf_load_animated_gif_file_ez(const char* filename) { return rf_load_animated_gif_file(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API void rf_unload_gif_ez(rf_gif gif) { rf_unload_gif(gif, RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region texture +RF_API rf_texture2d rf_load_texture_from_file_ez(const char* filename) { return rf_load_texture_from_file(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API rf_texture2d rf_load_texture_from_file_data_ez(const void* dst, int dst_size) { return rf_load_texture_from_file_data(dst, dst_size, RF_DEFAULT_ALLOCATOR); } +RF_API rf_texture_cubemap rf_load_texture_cubemap_from_image_ez(rf_image image, rf_cubemap_layout_type layout_type) { return rf_load_texture_cubemap_from_image(image, layout_type, RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region font +RF_API rf_font rf_load_ttf_font_from_data_ez(const void* font_file_data, int font_size, rf_font_antialias antialias, const int* chars, int chars_count) { return rf_load_ttf_font_from_data(font_file_data, font_size, antialias, chars, chars_count, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_font rf_load_ttf_font_from_file_ez(const char* filename, int font_size, rf_font_antialias antialias) { return rf_load_ttf_font_from_file(filename, font_size, antialias, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } + +RF_API rf_font rf_load_image_font_ez(rf_image image, rf_color key) { return rf_load_image_font(image, key, RF_DEFAULT_ALLOCATOR); } +RF_API rf_font rf_load_image_font_from_file_ez(const char* path, rf_color key) { return rf_load_image_font_from_file(path, key, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } + +RF_API void rf_unload_font_ez(rf_font font) { rf_unload_font(font, RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region utf8 +RF_API rf_decoded_string rf_decode_utf8_ez(const char* text, int len) { return rf_decode_utf8(text, len, RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region drawing +RF_API void rf_image_draw_ez(rf_image* dst, rf_image src, rf_rec src_rec, rf_rec dst_rec, rf_color tint) { rf_image_draw(dst, src, src_rec, dst_rec, tint, RF_DEFAULT_ALLOCATOR); } +RF_API void rf_image_draw_rectangle_ez(rf_image* dst, rf_rec rec, rf_color color) { rf_image_draw_rectangle(dst, rec, color, RF_DEFAULT_ALLOCATOR); } +RF_API void rf_image_draw_rectangle_lines_ez(rf_image* dst, rf_rec rec, int thick, rf_color color) { rf_image_draw_rectangle_lines(dst, rec, thick, color, RF_DEFAULT_ALLOCATOR); } +#pragma endregion + +#pragma region model & materials & animations +RF_API void rf_mesh_compute_tangents_ez(rf_mesh* mesh) { rf_mesh_compute_tangents(mesh, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API void rf_unload_mesh_ez(rf_mesh mesh) { rf_unload_mesh(mesh, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_model rf_load_model_ez(const char* filename) { return rf_load_model(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API rf_model rf_load_model_from_obj_ez(const char* filename) { return rf_load_model_from_obj(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API rf_model rf_load_model_from_iqm_ez(const char* filename) { return rf_load_model_from_iqm(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API rf_model rf_load_model_from_gltf_ez(const char* filename) { return rf_load_model_from_gltf(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API rf_model rf_load_model_from_mesh_ez(rf_mesh mesh) { return rf_load_model_from_mesh(mesh, RF_DEFAULT_ALLOCATOR); } +RF_API void rf_unload_model_ez(rf_model model) { rf_unload_model(model, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_materials_array rf_load_materials_from_mtl_ez(const char* filename) { return rf_load_materials_from_mtl(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API void rf_unload_material_ez(rf_material material) { rf_unload_material(material, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_model_animation_array rf_load_model_animations_from_iqm_file_ez(const char* filename) { return rf_load_model_animations_from_iqm_file(filename, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_IO); } +RF_API rf_model_animation_array rf_load_model_animations_from_iqm_ez(const unsigned char* data, int data_size) { return rf_load_model_animations_from_iqm(data, data_size, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API void rf_unload_model_animation_ez(rf_model_animation anim) { rf_unload_model_animation(anim, RF_DEFAULT_ALLOCATOR); } + +RF_API rf_mesh rf_gen_mesh_cube_ez(float width, float height, float length) { return rf_gen_mesh_cube(width, height, length, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_poly_ez(int sides, float radius) { return rf_gen_mesh_poly(sides, radius, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_plane_ez(float width, float length, int res_x, int res_z) { return rf_gen_mesh_plane(width, length, res_x, res_z, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_sphere_ez(float radius, int rings, int slices) { return rf_gen_mesh_sphere(radius, rings, slices, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_hemi_sphere_ez(float radius, int rings, int slices) { return rf_gen_mesh_hemi_sphere(radius, rings, slices, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_cylinder_ez(float radius, float height, int slices) { return rf_gen_mesh_cylinder(radius, height, slices, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_torus_ez(float radius, float size, int rad_seg, int sides) { return rf_gen_mesh_torus(radius, size, rad_seg, sides, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_knot_ez(float radius, float size, int rad_seg, int sides) { return rf_gen_mesh_knot(radius, size, rad_seg, sides, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_heightmap_ez(rf_image heightmap, rf_vec3 size) { return rf_gen_mesh_heightmap(heightmap, size, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +RF_API rf_mesh rf_gen_mesh_cubicmap_ez(rf_image cubicmap, rf_vec3 cube_size) { return rf_gen_mesh_cubicmap(cubicmap, cube_size, RF_DEFAULT_ALLOCATOR, RF_DEFAULT_ALLOCATOR); } +#pragma endregion +#endif +/*** End of inlined file: rayfork-ez.c ***/ + +//#include "rayfork_audio_loading.c" + + +/*** Start of inlined file: rayfork-gfx-backend-opengl.c ***/ +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) || defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + +#pragma region macros + +// Use this for glClearDepth +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + #define rf_gl_ClearDepth rf_gl.ClearDepth +#else + #define rf_gl_ClearDepth rf_gl.ClearDepthf +#endif + +#pragma endregion + +#pragma region gl constants + +// Since we don't have an opengl header to include I just pasted all the constants we might need +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_VIEWPORT 0x0BA2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F +#define GL_TEXTURE 0x1702 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_REPEAT 0x2901 +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_SRC1_ALPHA 0x8589 +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_CLIP_DISTANCE0 0x3000 +#define GL_CLIP_DISTANCE1 0x3001 +#define GL_CLIP_DISTANCE2 0x3002 +#define GL_CLIP_DISTANCE3 0x3003 +#define GL_CLIP_DISTANCE4 0x3004 +#define GL_CLIP_DISTANCE5 0x3005 +#define GL_CLIP_DISTANCE6 0x3006 +#define GL_CLIP_DISTANCE7 0x3007 +#define GL_MAX_CLIP_DISTANCES 0x0D32 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_CONTEXT_FLAGS 0x821E +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 +#define GL_CLAMP_READ_COLOR 0x891C +#define GL_FIXED_ONLY 0x891D +#define GL_MAX_VARYING_COMPONENTS 0x8B4B +#define GL_TEXTURE_1D_ARRAY 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D +#define GL_R11F_G11F_B10F 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +#define GL_RGB9_E5 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E +#define GL_TEXTURE_SHARED_SIZE 0x8C3F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 +#define GL_PRIMITIVES_GENERATED 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 +#define GL_RASTERIZER_DISCARD 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B +#define GL_INTERLEAVED_ATTRIBS 0x8C8C +#define GL_SEPARATE_ATTRIBS 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F +#define GL_RGBA32UI 0x8D70 +#define GL_RGB32UI 0x8D71 +#define GL_RGBA16UI 0x8D76 +#define GL_RGB16UI 0x8D77 +#define GL_RGBA8UI 0x8D7C +#define GL_RGB8UI 0x8D7D +#define GL_RGBA32I 0x8D82 +#define GL_RGB32I 0x8D83 +#define GL_RGBA16I 0x8D88 +#define GL_RGB16I 0x8D89 +#define GL_RGBA8I 0x8D8E +#define GL_RGB8I 0x8D8F +#define GL_RED_INTEGER 0x8D94 +#define GL_GREEN_INTEGER 0x8D95 +#define GL_BLUE_INTEGER 0x8D96 +#define GL_RGB_INTEGER 0x8D98 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGR_INTEGER 0x8D9A +#define GL_BGRA_INTEGER 0x8D9B +#define GL_SAMPLER_1D_ARRAY 0x8DC0 +#define GL_SAMPLER_2D_ARRAY 0x8DC1 +#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 +#define GL_UNSIGNED_INT_VEC2 0x8DC6 +#define GL_UNSIGNED_INT_VEC3 0x8DC7 +#define GL_UNSIGNED_INT_VEC4 0x8DC8 +#define GL_INT_SAMPLER_1D 0x8DC9 +#define GL_INT_SAMPLER_2D 0x8DCA +#define GL_INT_SAMPLER_3D 0x8DCB +#define GL_INT_SAMPLER_CUBE 0x8DCC +#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF +#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 +#define GL_QUERY_WAIT 0x8E13 +#define GL_QUERY_NO_WAIT 0x8E14 +#define GL_QUERY_BY_REGION_WAIT 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 +#define GL_BUFFER_ACCESS_FLAGS 0x911F +#define GL_BUFFER_MAP_LENGTH 0x9120 +#define GL_BUFFER_MAP_OFFSET 0x9121 +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_COLOR_ATTACHMENT16 0x8CF0 +#define GL_COLOR_ATTACHMENT17 0x8CF1 +#define GL_COLOR_ATTACHMENT18 0x8CF2 +#define GL_COLOR_ATTACHMENT19 0x8CF3 +#define GL_COLOR_ATTACHMENT20 0x8CF4 +#define GL_COLOR_ATTACHMENT21 0x8CF5 +#define GL_COLOR_ATTACHMENT22 0x8CF6 +#define GL_COLOR_ATTACHMENT23 0x8CF7 +#define GL_COLOR_ATTACHMENT24 0x8CF8 +#define GL_COLOR_ATTACHMENT25 0x8CF9 +#define GL_COLOR_ATTACHMENT26 0x8CFA +#define GL_COLOR_ATTACHMENT27 0x8CFB +#define GL_COLOR_ATTACHMENT28 0x8CFC +#define GL_COLOR_ATTACHMENT29 0x8CFD +#define GL_COLOR_ATTACHMENT30 0x8CFE +#define GL_COLOR_ATTACHMENT31 0x8CFF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 +#define GL_INDEX 0x8222 +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_HALF_FLOAT 0x140B +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#define GL_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#define GL_SAMPLER_BUFFER 0x8DC2 +#define GL_INT_SAMPLER_2D_RECT 0x8DCD +#define GL_INT_SAMPLER_BUFFER 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 +#define GL_TEXTURE_BUFFER 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 +#define GL_R8_SNORM 0x8F94 +#define GL_RG8_SNORM 0x8F95 +#define GL_RGB8_SNORM 0x8F96 +#define GL_RGBA8_SNORM 0x8F97 +#define GL_R16_SNORM 0x8F98 +#define GL_RG16_SNORM 0x8F99 +#define GL_RGB16_SNORM 0x8F9A +#define GL_RGBA16_SNORM 0x8F9B +#define GL_SIGNED_NORMALIZED 0x8F9C +#define GL_PRIMITIVE_RESTART 0x8F9D +#define GL_PRIMITIVE_RESTART_INDEX 0x8F9E +#define GL_COPY_READ_BUFFER 0x8F36 +#define GL_COPY_WRITE_BUFFER 0x8F37 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 +#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 +#define GL_INVALID_INDEX 0xFFFFFFFF +#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_LINES_ADJACENCY 0x000A +#define GL_LINE_STRIP_ADJACENCY 0x000B +#define GL_TRIANGLES_ADJACENCY 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY 0x000D +#define GL_PROGRAM_POINT_SIZE 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 +#define GL_GEOMETRY_SHADER 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT 0x8916 +#define GL_GEOMETRY_INPUT_TYPE 0x8917 +#define GL_GEOMETRY_OUTPUT_TYPE 0x8918 +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 +#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 +#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +#define GL_DEPTH_CLAMP 0x864F +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION 0x8E4D +#define GL_LAST_VERTEX_CONVENTION 0x8E4E +#define GL_PROVOKING_VERTEX 0x8E4F +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 +#define GL_OBJECT_TYPE 0x9112 +#define GL_SYNC_CONDITION 0x9113 +#define GL_SYNC_STATUS 0x9114 +#define GL_SYNC_FLAGS 0x9115 +#define GL_SYNC_FENCE 0x9116 +#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 +#define GL_UNSIGNALED 0x9118 +#define GL_SIGNALED 0x9119 +#define GL_ALREADY_SIGNALED 0x911A +#define GL_TIMEOUT_EXPIRED 0x911B +#define GL_CONDITION_SATISFIED 0x911C +#define GL_WAIT_FAILED 0x911D +#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFF +#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#define GL_SAMPLE_POSITION 0x8E50 +#define GL_SAMPLE_MASK 0x8E51 +#define GL_SAMPLE_MASK_VALUE 0x8E52 +#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 +#define GL_TEXTURE_SAMPLES 0x9106 +#define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 +#define GL_SAMPLER_2D_MULTISAMPLE 0x9108 +#define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A +#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B +#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D +#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E +#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F +#define GL_MAX_INTEGER_SAMPLES 0x9110 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE +#define GL_SRC1_COLOR 0x88F9 +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB +#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC +#define GL_ANY_SAMPLES_PASSED 0x8C2F +#define GL_SAMPLER_BINDING 0x8919 +#define GL_RGB10_A2UI 0x906F +#define GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 +#define GL_TIME_ELAPSED 0x88BF +#define GL_TIMESTAMP 0x8E28 +#define GL_INT_2_10_10_10_REV 0x8D9F +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9 + +#define GL_TEXTURE_ANISOTROPIC_FILTER 0x3000 // Anisotropic filter (custom identifier) +#define GL_MIRROR_CLAMP_EXT 0x8742 // GL_MIRROR_CLAMP_EXT +#define GL_MODELVIEW 0x1700 // GL_MODELVIEW +#define GL_PROJECTION 0x1701 // GL_PROJECTION +#define GL_QUADS 0x0007 // GL_QUADS + +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#define GL_ETC1_RGB8_OES 0x8D64 +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE + +#pragma endregion + +#pragma region internal renderer functions + +// Compile custom shader and return shader id +RF_INTERNAL unsigned int rf_compile_shader(const char* shader_str, int type) +{ + unsigned int shader = rf_gl.CreateShader(type); + rf_gl.ShaderSource(shader, 1, &shader_str, NULL); + + int success = 0; + rf_gl.CompileShader(shader); + rf_gl.GetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (success != GL_TRUE) + { + RF_LOG(RF_LOG_TYPE_WARNING, "[SHDR ID %i] Failed to compile shader...", shader); + int max_len = 0; + int length; + rf_gl.GetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_len); + + //@Note: Buffer may not be big enough for some messages + char log[1024]; + + rf_gl.GetShaderInfoLog(shader, 1024, &length, log); + + RF_LOG(RF_LOG_TYPE_INFO, "%s", log); + } + else RF_LOG(RF_LOG_TYPE_INFO, "[SHDR ID %i] rf_shader compiled successfully", shader); + + return shader; +} + +// Load custom shader strings and return program id +RF_INTERNAL unsigned int rf_load_shader_program(unsigned int v_shader_id, unsigned int f_shader_id) +{ + unsigned int program = 0; + + int success = 0; + program = rf_gl.CreateProgram(); + + rf_gl.AttachShader(program, v_shader_id); + rf_gl.AttachShader(program, f_shader_id); + + // NOTE: Default attribute shader locations must be binded before linking + rf_gl.BindAttribLocation(program, 0, RF_DEFAULT_ATTRIB_POSITION_NAME); + rf_gl.BindAttribLocation(program, 1, RF_DEFAULT_ATTRIB_TEXCOORD_NAME); + rf_gl.BindAttribLocation(program, 2, RF_DEFAULT_ATTRIB_NORMAL_NAME); + rf_gl.BindAttribLocation(program, 3, RF_DEFAULT_ATTRIB_COLOR_NAME); + rf_gl.BindAttribLocation(program, 4, RF_DEFAULT_ATTRIB_TANGENT_NAME); + rf_gl.BindAttribLocation(program, 5, RF_DEFAULT_ATTRIB_TEXCOORD2_NAME); + + // NOTE: If some attrib name is no found on the shader, it locations becomes -1 + + rf_gl.LinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + rf_gl.GetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + RF_LOG(RF_LOG_TYPE_WARNING, "[SHDR ID %i] Failed to link shader program...", program); + + int max_len = 0; + int length; + + rf_gl.GetProgramiv(program, GL_INFO_LOG_LENGTH, &max_len); + + char log[1024]; + + rf_gl.GetProgramInfoLog(program, 1024, &length, log); + + RF_LOG(RF_LOG_TYPE_INFO, "%s", log); + + rf_gl.DeleteProgram(program); + + program = 0; + } + else RF_LOG(RF_LOG_TYPE_INFO, "[SHDR ID %i] rf_shader program loaded successfully", program); + + return program; +} + +// Get location handlers to for shader attributes and uniforms. Note: If any location is not found, loc point becomes -1 +RF_INTERNAL void rf_set_shader_default_locations(rf_shader* shader) +{ + // NOTE: Default shader attrib locations have been fixed before linking: + // vertex position location = 0 + // vertex texcoord location = 1 + // vertex normal location = 2 + // vertex color location = 3 + // vertex tangent location = 4 + // vertex texcoord2 location = 5 + + // Get handles to GLSL input attibute locations + shader->locs[RF_LOC_VERTEX_POSITION] = rf_gl.GetAttribLocation(shader->id, RF_DEFAULT_ATTRIB_POSITION_NAME); + shader->locs[RF_LOC_VERTEX_TEXCOORD01] = rf_gl.GetAttribLocation(shader->id, RF_DEFAULT_ATTRIB_TEXCOORD_NAME); + shader->locs[RF_LOC_VERTEX_TEXCOORD02] = rf_gl.GetAttribLocation(shader->id, RF_DEFAULT_ATTRIB_TEXCOORD2_NAME); + shader->locs[RF_LOC_VERTEX_NORMAL] = rf_gl.GetAttribLocation(shader->id, RF_DEFAULT_ATTRIB_NORMAL_NAME); + shader->locs[RF_LOC_VERTEX_TANGENT] = rf_gl.GetAttribLocation(shader->id, RF_DEFAULT_ATTRIB_TANGENT_NAME); + shader->locs[RF_LOC_VERTEX_COLOR] = rf_gl.GetAttribLocation(shader->id, RF_DEFAULT_ATTRIB_COLOR_NAME); + + // Get handles to GLSL uniform locations (vertex shader) + shader->locs[RF_LOC_MATRIX_MVP] = rf_gl.GetUniformLocation(shader->id, "mvp"); + shader->locs[RF_LOC_MATRIX_PROJECTION] = rf_gl.GetUniformLocation(shader->id, "projection"); + shader->locs[RF_LOC_MATRIX_VIEW] = rf_gl.GetUniformLocation(shader->id, "view"); + + // Get handles to GLSL uniform locations (fragment shader) + shader->locs[RF_LOC_COLOR_DIFFUSE] = rf_gl.GetUniformLocation(shader->id, "col_diffuse"); + shader->locs[RF_LOC_MAP_DIFFUSE] = rf_gl.GetUniformLocation(shader->id, "texture0"); + shader->locs[RF_LOC_MAP_SPECULAR] = rf_gl.GetUniformLocation(shader->id, "texture1"); + shader->locs[RF_LOC_MAP_NORMAL] = rf_gl.GetUniformLocation(shader->id, "texture2"); +} + +// Unload default shader +RF_INTERNAL void rf_unlock_shader_default() +{ + rf_gl.UseProgram(0); + + rf_gl.DetachShader(rf_ctx.default_shader.id, rf_ctx.default_vertex_shader_id); + rf_gl.DetachShader(rf_ctx.default_shader.id, rf_ctx.default_frag_shader_id); + rf_gl.DeleteShader(rf_ctx.default_vertex_shader_id); + rf_gl.DeleteShader(rf_ctx.default_frag_shader_id); + + rf_gl.DeleteProgram(rf_ctx.default_shader.id); +} + +// Draw default internal buffers vertex data +RF_INTERNAL void rf_draw_buffers_default() +{ + +} + +// Unload default internal buffers vertex data from CPU and GPU +RF_INTERNAL void rf_unload_buffers_default() +{ + // Unbind everything + rf_gl.BindVertexArray(0); + rf_gl.DisableVertexAttribArray(0); + rf_gl.DisableVertexAttribArray(1); + rf_gl.DisableVertexAttribArray(2); + rf_gl.DisableVertexAttribArray(3); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, 0); + rf_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + for (rf_int i = 0; i < RF_DEFAULT_BATCH_VERTEX_BUFFERS_COUNT; i++) + { + // Delete VBOs from GPU (VRAM) + rf_gl.DeleteBuffers(1, &rf_batch.vertex_buffers[i].vbo_id[0]); + rf_gl.DeleteBuffers(1, &rf_batch.vertex_buffers[i].vbo_id[1]); + rf_gl.DeleteBuffers(1, &rf_batch.vertex_buffers[i].vbo_id[2]); + rf_gl.DeleteBuffers(1, &rf_batch.vertex_buffers[i].vbo_id[3]); + + // Delete VAOs from GPU (VRAM) + rf_gl.DeleteVertexArrays(1, &rf_batch.vertex_buffers[i].vao_id); + } +} + +// Renders a 1x1 XY quad in NDC +RF_INTERNAL void rf_gen_draw_quad(void) +{ + unsigned int quad_vao = 0; + unsigned int quad_vbo = 0; + + float vertices[] = { + // Positions // rf_texture Coords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Set up plane VAO + rf_gl.GenVertexArrays(1, &quad_vao); + rf_gl.GenBuffers(1, &quad_vbo); + rf_gl.BindVertexArray(quad_vao); + + // Fill buffer + rf_gl.BindBuffer(GL_ARRAY_BUFFER, quad_vbo); + rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Link vertex attributes + rf_gl.EnableVertexAttribArray(0); + rf_gl.VertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void* )0); + rf_gl.EnableVertexAttribArray(1); + rf_gl.VertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void* )(3 * sizeof(float))); + + // Draw quad + rf_gl.BindVertexArray(quad_vao); + rf_gl.DrawArrays(GL_TRIANGLE_STRIP, 0, 4); + rf_gl.BindVertexArray(0); + + rf_gl.DeleteBuffers(1, &quad_vbo); + rf_gl.DeleteVertexArrays(1, &quad_vao); +} + +// Renders a 1x1 3D cube in NDC +RF_INTERNAL void rf_gen_draw_cube(void) +{ + unsigned int cube_vao = 0; + unsigned int cube_vbo = 0; + + static float vertices[] = { + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Set up cube VAO + rf_gl.GenVertexArrays(1, &cube_vao); + rf_gl.GenBuffers(1, &cube_vbo); + + // Fill buffer + rf_gl.BindBuffer(GL_ARRAY_BUFFER, cube_vbo); + rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Link vertex attributes + rf_gl.BindVertexArray(cube_vao); + rf_gl.EnableVertexAttribArray(0); + rf_gl.VertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void* )0); + rf_gl.EnableVertexAttribArray(1); + rf_gl.VertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void* )(3 * sizeof(float))); + rf_gl.EnableVertexAttribArray(2); + rf_gl.VertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void* )(6 * sizeof(float))); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, 0); + rf_gl.BindVertexArray(0); + + // Draw cube + rf_gl.BindVertexArray(cube_vao); + rf_gl.DrawArrays(GL_TRIANGLES, 0, 36); + rf_gl.BindVertexArray(0); + + rf_gl.DeleteBuffers(1, &cube_vbo); + rf_gl.DeleteVertexArrays(1, &cube_vao); +} + +RF_INTERNAL void rf_set_gl_extension_if_available(const char* gl_ext, int len) +{ + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (rf_match_str_cstr(gl_ext, len, "GL_OES_texture_npot")) { + rf_gfx.extensions.tex_npot_supported = true; + } + + // Check texture float support + if (rf_match_str_cstr(gl_ext, len, "GL_OES_texture_float")) { + rf_gfx.extensions.tex_float_supported = true; + } + + // Check depth texture support + if ((rf_match_str_cstr(gl_ext, len, "GL_OES_depth_texture")) || + (rf_match_str_cstr(gl_ext, len, "GL_WEBGL_depth_texture"))) { + rf_gfx.extensions.tex_depth_supported = true; + } + + if (rf_match_str_cstr(gl_ext, len, "GL_OES_depth24")) { + rf_gfx.extensions.max_depth_bits = 24; + } + + if (rf_match_str_cstr(gl_ext, len, "GL_OES_depth32")) { + rf_gfx.extensions.max_depth_bits = 32; + } + #endif + + // DDS texture compression support + if (rf_match_str_cstr(gl_ext, len, "GL_EXT_texture_compression_s3tc") || + rf_match_str_cstr(gl_ext, len, "GL_WEBGL_compressed_texture_s3tc") || + rf_match_str_cstr(gl_ext, len, "GL_WEBKIT_WEBGL_compressed_texture_s3tc")) { + rf_gfx.extensions.tex_comp_dxt_supported = true; + } + + // ETC1 texture compression support + if (rf_match_str_cstr(gl_ext, len, "GL_OES_compressed_ETC1_RGB8_texture") || + rf_match_str_cstr(gl_ext, len, "GL_WEBGL_compressed_texture_etc1")) { + rf_gfx.extensions.tex_comp_etc1_supported = true; + } + + // ETC2/EAC texture compression support + if (rf_match_str_cstr(gl_ext, len, "GL_ARB_ES3_compatibility")) { + rf_gfx.extensions.tex_comp_etc2_supported = true; + } + + // PVR texture compression support + if (rf_match_str_cstr(gl_ext, len, "GL_IMG_texture_compression_pvrtc")) { + rf_gfx.extensions.tex_comp_pvrt_supported = true; + } + + // ASTC texture compression support + if (rf_match_str_cstr(gl_ext, len, "GL_KHR_texture_compression_astc_hdr")) { + rf_gfx.extensions.tex_comp_astc_supported = true; + } + + // Anisotropic texture filter support + if (rf_match_str_cstr(gl_ext, len, "GL_EXT_texture_filter_anisotropic")) { + rf_gfx.extensions.tex_anisotropic_filter_supported = true; + rf_gl.GetFloatv(0x84FF, &rf_gfx.extensions.max_anisotropic_level); // GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + } + + // Clamp mirror wrap mode supported + if (rf_match_str_cstr(gl_ext, len, "GL_EXT_texture_mirror_clamp")) { + rf_gfx.extensions.tex_mirror_clamp_supported = true; + } + + // Debug marker support + if (rf_match_str_cstr(gl_ext, len, "GL_EXT_debug_marker")) { + rf_gfx.extensions.debug_marker_supported = true; + } +} + +#pragma endregion + +#pragma region init +RF_INTERNAL void rf_gfx_backend_internal_init(rf_gfx_backend_data* gfx_data) +{ + rf_gfx.gl = *((rf_opengl_procs*) gfx_data); + rf_gfx.extensions.max_depth_bits = 16; + + // Check for extensions + { + int num_ext = 0; + + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + { + // Multiple texture extensions supported by default + rf_gfx.extensions.tex_npot_supported = true; + rf_gfx.extensions.tex_float_supported = true; + rf_gfx.extensions.tex_depth_supported = true; + + rf_gl.GetIntegerv(GL_NUM_EXTENSIONS, &num_ext); + + for (rf_int i = 0; i < num_ext; i++) + { + const char *ext = (const char *) rf_gl.GetStringi(GL_EXTENSIONS, i); + rf_set_gl_extension_if_available(ext, strlen(ext)); + } + } + #elif defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + { + //Note: How this works is that we get one big string formated like "gl_ext_func_name1 gl_ext_func_name2 ..." + //All function names are separated by a space, so we just take a pointer to the begin and advance until we hit a space + //At which point we get the length, do the comparison, and set the ptr of the next extension to the next element. + const char* extensions_str = (const char*) rf_gl.GetString(GL_EXTENSIONS); // One big const string + if (extensions_str) + { + const char* curr_ext = extensions_str; + + while (*extensions_str) + { + //If we get to a space that means we got a new extension name + if (*extensions_str == ' ') + { + num_ext++; + const int curr_ext_len = (int) (extensions_str - curr_ext); + rf_set_gl_extension_if_available(curr_ext, curr_ext_len); + curr_ext = extensions_str + 1; + } + + extensions_str++; + } + + if (rf_gfx.extensions.tex_npot_supported) RF_LOG(RF_LOG_TYPE_INFO, "[EXTENSION] NPOT textures extension detected, full NPOT textures supported"); + else RF_LOG(RF_LOG_TYPE_WARNING, "[EXTENSION] NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + } + } + #endif + + RF_LOG(RF_LOG_TYPE_INFO, "Number of supported extensions: %i.", num_ext); + + if (rf_gfx.extensions.tex_comp_dxt_supported) + { + RF_LOG(RF_LOG_TYPE_INFO, "[GL EXTENSION] DXT compressed textures supported"); + } + + if (rf_gfx.extensions.tex_comp_etc1_supported) + { + RF_LOG(RF_LOG_TYPE_INFO, "[GL EXTENSION] ETC1 compressed textures supported"); + } + + if (rf_gfx.extensions.tex_comp_etc2_supported) + { + RF_LOG(RF_LOG_TYPE_INFO, "[GL EXTENSION] ETC2/EAC compressed textures supported"); + } + + if (rf_gfx.extensions.tex_comp_pvrt_supported) + { + RF_LOG(RF_LOG_TYPE_INFO, "[GL EXTENSION] PVRT compressed textures supported"); + } + + if (rf_gfx.extensions.tex_comp_astc_supported) + { + RF_LOG(RF_LOG_TYPE_INFO, "[GL EXTENSION] ASTC compressed textures supported"); + } + + if (rf_gfx.extensions.tex_anisotropic_filter_supported) + { + RF_LOG(RF_LOG_TYPE_INFO, "[GL EXTENSION] Anisotropic textures filtering supported (max: %.0fX)", rf_gfx.extensions.max_anisotropic_level); + } + + if (rf_gfx.extensions.tex_mirror_clamp_supported) + { + RF_LOG(RF_LOG_TYPE_INFO, "[GL EXTENSION] Mirror clamp wrap texture mode supported"); + } + + if (rf_gfx.extensions.debug_marker_supported) + { + RF_LOG(RF_LOG_TYPE_INFO, "[GL EXTENSION] Debug Marker supported"); + } + } + + // Initialize OpenGL default states + { + // Init state: Depth test + rf_gl.DepthFunc(GL_LEQUAL); // Type of depth testing to apply + rf_gl.Disable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) + + // Init state: Blending mode + rf_gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // rf_color blending function (how colors are mixed) + rf_gl.Enable(GL_BLEND); // Enable color blending (required to work with transparencies) + + // Init state: Culling + // NOTE: All shapes/models triangles are drawn CCW + rf_gl.CullFace(GL_BACK); // Cull the back face (default) + rf_gl.FrontFace(GL_CCW); // Front face are defined counter clockwise (default) + rf_gl.Enable(GL_CULL_FACE); // Enable backface culling + + // Init state: rf_color/Depth buffers clear + rf_gl.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + rf_gl_ClearDepth(1.0f); // Set clear depth value (default) + rf_gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) + } +} + +#pragma endregion + +#pragma region shader + +// Load default shader (just vertex positioning and texture coloring). Note: This shader program is used for internal buffers +RF_API rf_shader rf_load_default_shader() +{ + rf_shader shader = { 0 }; + memset(shader.locs, 0, sizeof(shader.locs)); + + // NOTE: All locations must be reseted to -1 (no location) + for (rf_int i = 0; i < RF_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + + // Vertex shader directly defined, no external file required + const char* default_vertex_shader_str = + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + "#version 100\n" + "attribute vec3 vertex_position;" + "attribute vec2 vertex_tex_coord;" + "attribute vec4 vertex_color;" + "varying vec2 frag_tex_coord;" + "varying vec4 frag_color;" + #elif defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + "#version 330\n" + "in vec3 vertex_position;" + "in vec2 vertex_tex_coord;" + "in vec4 vertex_color;" + "out vec2 frag_tex_coord;" + "out vec4 frag_color;" + #endif + "uniform mat4 mvp;" + "void main()" + "{" + " frag_tex_coord = vertex_tex_coord;" + " frag_color = vertex_color;" + " gl_Position = mvp*vec4(vertex_position, 1.0);" + "}"; + + // Fragment shader directly defined, no external file required + const char* default_fragment_shader_str = + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + "#version 100\n" + "precision mediump float;" + "varying vec2 frag_tex_coord;" + "varying vec4 frag_color;" + #elif defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + "#version 330\n" + "precision mediump float;" + "in vec2 frag_tex_coord;" + "in vec4 frag_color;" + "out vec4 final_color;" + #endif + "uniform sampler2D texture0;" + "uniform vec4 col_diffuse;" + "void main()" + "{" + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + " vec4 texel_color = texture2D(texture0, frag_tex_coord);" // NOTE: texture2D() is deprecated on OpenGL 3.3 and ES 3.0 + " frag_color = texel_color*col_diffuse*frag_color;" + #elif defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + " vec4 texel_color = texture(texture0, frag_tex_coord);" + " final_color = texel_color*col_diffuse*frag_color;" + #endif + "}"; + + // NOTE: Compiled vertex/fragment shaders are kept for re-use + rf_ctx.default_vertex_shader_id = rf_compile_shader(default_vertex_shader_str, GL_VERTEX_SHADER); // Compile default vertex shader + rf_ctx.default_frag_shader_id = rf_compile_shader(default_fragment_shader_str, GL_FRAGMENT_SHADER); // Compile default fragment shader + + shader.id = rf_load_shader_program(rf_ctx.default_vertex_shader_id, rf_ctx.default_frag_shader_id); + + if (shader.id > 0) + { + RF_LOG(RF_LOG_TYPE_INFO, "[SHDR ID %i] Default shader loaded successfully", shader.id); + + // Set default shader locations: attributes locations + shader.locs[RF_LOC_VERTEX_POSITION] = rf_gl.GetAttribLocation(shader.id, "vertex_position"); + shader.locs[RF_LOC_VERTEX_TEXCOORD01] = rf_gl.GetAttribLocation(shader.id, "vertex_tex_coord"); + shader.locs[RF_LOC_VERTEX_COLOR] = rf_gl.GetAttribLocation(shader.id, "vertex_color"); + + // Set default shader locations: uniform locations + shader.locs[RF_LOC_MATRIX_MVP] = rf_gl.GetUniformLocation(shader.id, "mvp"); + shader.locs[RF_LOC_COLOR_DIFFUSE] = rf_gl.GetUniformLocation(shader.id, "col_diffuse"); + shader.locs[RF_LOC_MAP_DIFFUSE] = rf_gl.GetUniformLocation(shader.id, "texture0"); + + // NOTE: We could also use below function but in case RF_DEFAULT_ATTRIB_* points are + // changed for external custom shaders, we just use direct bindings above + //rf_set_shader_default_locations(&shader); + } + else RF_LOG(RF_LOG_TYPE_WARNING, "[SHDR ID %i] Default shader could not be loaded", shader.id); + + return shader; +} + +// Load shader from code strings. If shader string is NULL, using default vertex/fragment shaders +RF_API rf_shader rf_gfx_load_shader(const char* vs_code, const char* fs_code) +{ + rf_shader shader = { 0 }; + memset(shader.locs, -1, sizeof(shader.locs)); + + unsigned int vertex_shader_id = rf_ctx.default_vertex_shader_id; + unsigned int fragment_shader_id = rf_ctx.default_frag_shader_id; + + if (vs_code != NULL) vertex_shader_id = rf_compile_shader(vs_code, GL_VERTEX_SHADER); + if (fs_code != NULL) fragment_shader_id = rf_compile_shader(fs_code, GL_FRAGMENT_SHADER); + + if ((vertex_shader_id == rf_ctx.default_vertex_shader_id) && (fragment_shader_id == rf_ctx.default_frag_shader_id)) shader = rf_ctx.default_shader; + else + { + shader.id = rf_load_shader_program(vertex_shader_id, fragment_shader_id); + + if (vertex_shader_id != rf_ctx.default_vertex_shader_id) rf_gl.DeleteShader(vertex_shader_id); + if (fragment_shader_id != rf_ctx.default_frag_shader_id) rf_gl.DeleteShader(fragment_shader_id); + + if (shader.id == 0) + { + RF_LOG(RF_LOG_TYPE_WARNING, "Custom shader could not be loaded"); + shader = rf_ctx.default_shader; + } + + // After shader loading, we TRY to set default location names + if (shader.id > 0) rf_set_shader_default_locations(&shader); + } + + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniform_count = -1; + + rf_gl.GetProgramiv(shader.id, GL_ACTIVE_UNIFORMS, &uniform_count); + + for (rf_int i = 0; i < uniform_count; i++) + { + int namelen = -1; + int num = -1; + char name[256]; // Assume no variable names longer than 256 + unsigned int type = GL_ZERO; + + // Get the name of the uniforms + rf_gl.GetActiveUniform(shader.id, i,sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + + // Get the location of the named uniform + unsigned int location = rf_gl.GetUniformLocation(shader.id, name); + + RF_LOG(RF_LOG_TYPE_DEBUG, "[SHDR ID %i] Active uniform [%s] set at location: %i", shader.id, name, location); + } + + return shader; +} + +// Unload shader from GPU memory (VRAM) +RF_API void rf_gfx_unload_shader(rf_shader shader) +{ + if (shader.id > 0) + { + rf_gfx_delete_shader(shader.id); + RF_LOG(RF_LOG_TYPE_INFO, "[SHDR ID %i] Unloaded shader program data", shader.id); + } +} + +// Get shader uniform location +RF_API int rf_gfx_get_shader_location(rf_shader shader, const char* uniform_name) +{ + int location = -1; + location = rf_gl.GetUniformLocation(shader.id, uniform_name); + + if (location == -1) RF_LOG(RF_LOG_TYPE_WARNING, "[SHDR ID %i][%s] rf_shader uniform could not be found", shader.id, uniform_name); + else RF_LOG(RF_LOG_TYPE_INFO, "[SHDR ID %i][%s] rf_shader uniform set at location: %i", shader.id, uniform_name, location); + + return location; +} + +// Set shader uniform value +RF_API void rf_gfx_set_shader_value(rf_shader shader, int uniform_loc, const void* value, int uniform_name) +{ + rf_gfx_set_shader_value_v(shader, uniform_loc, value, uniform_name, 1); +} + +// Set shader uniform value vector +RF_API void rf_gfx_set_shader_value_v(rf_shader shader, int uniform_loc, const void* value, int uniform_name, int count) +{ + rf_gl.UseProgram(shader.id); + + switch (uniform_name) + { + case RF_UNIFORM_FLOAT: rf_gl.Uniform1fv(uniform_loc, count, (float* )value); break; + case RF_UNIFORM_VEC2: rf_gl.Uniform2fv(uniform_loc, count, (float* )value); break; + case RF_UNIFORM_VEC3: rf_gl.Uniform3fv(uniform_loc, count, (float* )value); break; + case RF_UNIFORM_VEC4: rf_gl.Uniform4fv(uniform_loc, count, (float* )value); break; + case RF_UNIFORM_INT: rf_gl.Uniform1iv(uniform_loc, count, (int* )value); break; + case RF_UNIFORM_IVEC2: rf_gl.Uniform2iv(uniform_loc, count, (int* )value); break; + case RF_UNIFORM_IVEC3: rf_gl.Uniform3iv(uniform_loc, count, (int* )value); break; + case RF_UNIFORM_IVEC4: rf_gl.Uniform4iv(uniform_loc, count, (int* )value); break; + case RF_UNIFORM_SAMPLER2D: rf_gl.Uniform1iv(uniform_loc, count, (int* )value); break; + default: RF_LOG(RF_LOG_TYPE_WARNING, "rf_shader uniform could not be set data type not recognized"); + } + + //rf_gl.UseProgram(0); // Avoid reseting current shader program, in case other uniforms are set +} + +// Set shader uniform value (matrix 4x4) +RF_API void rf_gfx_set_shader_value_matrix(rf_shader shader, int uniform_loc, rf_mat mat) +{ + rf_gl.UseProgram(shader.id); + + rf_gl.UniformMatrix4fv(uniform_loc, 1, false, rf_mat_to_float16(mat).v); + + //rf_gl.UseProgram(0); +} + +// Set shader uniform value for texture +RF_API void rf_gfx_set_shader_value_texture(rf_shader shader, int uniform_loc, rf_texture2d texture) +{ + rf_gl.UseProgram(shader.id); + + rf_gl.Uniform1i(uniform_loc, texture.id); + + //rf_gl.UseProgram(0); +} + +// Return internal rf_ctx->gl_ctx.projection matrix +RF_API rf_mat rf_gfx_get_matrix_projection() { + return rf_ctx.projection; +} + +// Return internal rf_ctx->gl_ctx.modelview matrix +RF_API rf_mat rf_gfx_get_matrix_modelview() +{ + rf_mat matrix = rf_mat_identity(); + matrix = rf_ctx.modelview; + return matrix; +} + +// Set a custom projection matrix (replaces internal rf_ctx->gl_ctx.projection matrix) +RF_API void rf_gfx_set_matrix_projection(rf_mat proj) +{ + rf_ctx.projection = proj; +} + +// Set a custom rf_ctx->gl_ctx.modelview matrix (replaces internal rf_ctx->gl_ctx.modelview matrix) +RF_API void rf_gfx_set_matrix_modelview(rf_mat view) +{ + rf_ctx.modelview = view; +} + +#pragma endregion + +#pragma region rf_gfx + +// Choose the blending mode (alpha, additive, multiplied) +RF_API void rf_gfx_blend_mode(rf_blend_mode mode) +{ + rf_gfx_draw(); + + switch (mode) + { + case RF_BLEND_ALPHA: rf_gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + case RF_BLEND_ADDITIVE: rf_gl.BlendFunc(GL_SRC_ALPHA, GL_ONE); break; // Alternative: glBlendFunc(GL_ONE, GL_ONE); + case RF_BLEND_MULTIPLIED: rf_gl.BlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); break; + default: break; + } + + rf_ctx.blend_mode = mode; +} + +// Choose the current matrix to be transformed +RF_API void rf_gfx_matrix_mode(rf_matrix_mode mode) +{ + if (mode == GL_PROJECTION) rf_ctx.current_matrix = &rf_ctx.projection; + else if (mode == GL_MODELVIEW) rf_ctx.current_matrix = &rf_ctx.modelview; + //else if (mode == GL_TEXTURE) // Not supported + + rf_ctx.current_matrix_mode = mode; +} + +// Push the current matrix into rf_ctx->gl_ctx.stack +RF_API void rf_gfx_push_matrix() +{ + if (rf_ctx.stack_counter >= RF_MAX_MATRIX_STACK_SIZE) RF_LOG_ERROR(RF_LIMIT_REACHED, "Matrix stack limit reached."); + + if (rf_ctx.current_matrix_mode == GL_MODELVIEW) + { + rf_ctx.transform_matrix_required = true; + rf_ctx.current_matrix = &rf_ctx.transform; + } + + rf_ctx.stack[rf_ctx.stack_counter] = *rf_ctx.current_matrix; + rf_ctx.stack_counter++; +} + +// Pop lattest inserted matrix from rf_ctx->gl_ctx.stack +RF_API void rf_gfx_pop_matrix() +{ + if (rf_ctx.stack_counter > 0) + { + rf_mat mat = rf_ctx.stack[rf_ctx.stack_counter - 1]; + *rf_ctx.current_matrix = mat; + rf_ctx.stack_counter--; + } + + if ((rf_ctx.stack_counter == 0) && (rf_ctx.current_matrix_mode == GL_MODELVIEW)) + { + rf_ctx.current_matrix = &rf_ctx.modelview; + rf_ctx.transform_matrix_required = false; + } +} + +// Reset current matrix to identity matrix +RF_API void rf_gfx_load_identity() +{ + *rf_ctx.current_matrix = rf_mat_identity(); +} + +// Multiply the current matrix by a translation matrix +RF_API void rf_gfx_translatef(float x, float y, float z) +{ + rf_mat mat_translation = rf_mat_translate(x, y, z); + + // NOTE: We transpose matrix with multiplication order + *rf_ctx.current_matrix = rf_mat_mul(mat_translation, *rf_ctx.current_matrix); +} + +// Multiply the current matrix by a rotation matrix +RF_API void rf_gfx_rotatef(float angleDeg, float x, float y, float z) +{ + rf_mat mat_rotation = rf_mat_identity(); + + rf_vec3 axis = (rf_vec3){x, y, z }; + mat_rotation = rf_mat_rotate(rf_vec3_normalize(axis), angleDeg * RF_DEG2RAD); + + // NOTE: We transpose matrix with multiplication order + *rf_ctx.current_matrix = rf_mat_mul(mat_rotation, *rf_ctx.current_matrix); +} + +// Multiply the current matrix by a scaling matrix +RF_API void rf_gfx_scalef(float x, float y, float z) +{ + rf_mat mat_scale = rf_mat_scale(x, y, z); + + // NOTE: We transpose matrix with multiplication order + *rf_ctx.current_matrix = rf_mat_mul(mat_scale, *rf_ctx.current_matrix); +} + +// Multiply the current matrix by another matrix +RF_API void rf_gfx_mult_matrixf(float* matf) +{ + // rf_mat creation from array + rf_mat mat = {matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[15] }; + + *rf_ctx.current_matrix = rf_mat_mul(*rf_ctx.current_matrix, mat); +} + +// Multiply the current matrix by a perspective matrix generated by parameters +RF_API void rf_gfx_frustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + rf_mat mat_perps = rf_mat_frustum(left, right, bottom, top, znear, zfar); + + *rf_ctx.current_matrix = rf_mat_mul(*rf_ctx.current_matrix, mat_perps); +} + +// Multiply the current matrix by an orthographic matrix generated by parameters +RF_API void rf_gfx_ortho(double left, double right, double bottom, double top, double znear, double zfar) +{ + rf_mat mat_ortho = rf_mat_ortho(left, right, bottom, top, znear, zfar); + + *rf_ctx.current_matrix = rf_mat_mul(*rf_ctx.current_matrix, mat_ortho); +} + +// Set the viewport area (transformation from normalized device coordinates to window coordinates) +void rf_gfx_viewport(int x, int y, int width, int height) +{ + rf_gl.Viewport(x, y, width, height); +} + +// Initialize drawing mode (how to organize vertex) +RF_API void rf_gfx_begin(rf_drawing_mode mode) +{ + // Draw mode can be GL_LINES, GL_TRIANGLES and GL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].mode != mode) + { + if (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count > 0) + { + // Make sure current rf_ctx->gl_ctx.draws[i].vertex_count is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].mode == GL_LINES) rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment = ((rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count < 4) ? rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count : rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count % 4); + else if (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].mode == GL_TRIANGLES) rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment = ((rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count < 4) ? 1 : (4 - (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count % 4))); + + else rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment = 0; + + if (rf_gfx_check_buffer_limit(rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment)) rf_gfx_draw(); + else + { + rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter += rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment; + rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter += rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment; + rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter += rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment; + + rf_batch.draw_calls_counter++; + } + } + + if (rf_batch.draw_calls_counter >= RF_DEFAULT_BATCH_DRAW_CALLS_COUNT) rf_gfx_draw(); + + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].mode = mode; + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count = 0; + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].texture_id = rf_ctx.default_texture_id; + } +} + +// Finish vertex providing +RF_API void rf_gfx_end() +{ + // Make sure vertex_count is the same for vertices, texcoords, colors and normals + // NOTE: In OpenGL 1.1, one glColor call can be made for all the subsequent glVertex calls + + // Make sure colors count match vertex count + if (rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter != rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter) + { + int add_colors = rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter - rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter; + + for (rf_int i = 0; i < add_colors; i++) + { + rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter] = rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter - 4]; + rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter + 1] = rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter - 3]; + rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter + 2] = rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter - 2]; + rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter + 3] = rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter - 1]; + rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter++; + } + } + + // Make sure texcoords count match vertex count + if (rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter != rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter) + { + int add_tex_coords = rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter - rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter; + + for (rf_int i = 0; i < add_tex_coords; i++) + { + rf_batch.vertex_buffers[rf_batch.current_buffer].texcoords[2*rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter] = 0.0f; + rf_batch.vertex_buffers[rf_batch.current_buffer].texcoords[2*rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter + 1] = 0.0f; + rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter++; + } + } + + // TODO: Make sure normals count match vertex count... if normals support is added in a future... :P + + // NOTE: Depth increment is dependant on rf_gfx_ortho(): z-near and z-far values, + // as well as depth buffer bit-depth (16bit or 24bit or 32bit) + // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) + rf_batch.current_depth += (1.0f/20000.0f); + + // Verify internal buffers limits + // NOTE: This check is combined with usage of rf_gfx_check_buffer_limit() + if ((rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter) >= (rf_batch.vertex_buffers[rf_batch.current_buffer].elements_count * 4 - 4)) + { + // WARNING: If we are between rf_gfx_push_matrix() and rf_gfx_pop_matrix() and we need to force a rf_gfx_draw(), + // we need to call rf_gfx_pop_matrix() before to recover *rf_ctx->gl_ctx.current_matrix (rf_ctx->gl_ctx.modelview) for the next forced draw call! + // If we have multiple matrix pushed, it will require "rf_ctx->gl_ctx.stack_counter" pops before launching the draw + for (rf_int i = rf_ctx.stack_counter; i >= 0; i--) rf_gfx_pop_matrix(); + rf_gfx_draw(); + } +} + +// Define one vertex (position) +RF_API void rf_gfx_vertex2i(int x, int y) +{ + rf_gfx_vertex3f((float)x, (float)y, rf_batch.current_depth); +} + +// Define one vertex (position) +RF_API void rf_gfx_vertex2f(float x, float y) +{ + rf_gfx_vertex3f(x, y, rf_batch.current_depth); +} + +// Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing +RF_API void rf_gfx_vertex3f(float x, float y, float z) +{ + rf_vec3 vec = {x, y, z }; + + // rf_transform provided vector if required + if (rf_ctx.transform_matrix_required) vec = rf_vec3_transform(vec, rf_ctx.transform); + + // Verify that rf_max_batch_elements limit not reached + if (rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter < (rf_batch.vertex_buffers[rf_batch.current_buffer].elements_count * 4)) + { + rf_batch.vertex_buffers[rf_batch.current_buffer].vertices[3*rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter] = vec.x; + rf_batch.vertex_buffers[rf_batch.current_buffer].vertices[3*rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter + 1] = vec.y; + rf_batch.vertex_buffers[rf_batch.current_buffer].vertices[3*rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter + 2] = vec.z; + rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter++; + + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count++; + } + else RF_LOG_ERROR(RF_LIMIT_REACHED, "Render batch elements limit reached. Max bacht elements: %d", rf_batch.vertex_buffers[rf_batch.current_buffer].elements_count * 4); +} + +// Define one vertex (texture coordinate) +// NOTE: rf_texture coordinates are limited to QUADS only +RF_API void rf_gfx_tex_coord2f(float x, float y) +{ + rf_batch.vertex_buffers[rf_batch.current_buffer].texcoords[2*rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter] = x; + rf_batch.vertex_buffers[rf_batch.current_buffer].texcoords[2*rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter + 1] = y; + rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter++; +} + +// Define one vertex (normal) +// NOTE: Normals limited to TRIANGLES only? +RF_API void rf_gfx_normal3f(float x, float y, float z) +{ + // TODO: Normals usage... +} + +// Define one vertex (color) +RF_API void rf_gfx_color3f(float x, float y, float z) +{ + rf_gfx_color4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); +} + +// Define one vertex (color) +RF_API void rf_gfx_color4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) +{ + rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter] = x; + rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter + 1] = y; + rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter + 2] = z; + rf_batch.vertex_buffers[rf_batch.current_buffer].colors[4 * rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter + 3] = w; + rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter++; +} + +// Define one vertex (color) +RF_API void rf_gfx_color4f(float r, float g, float b, float a) +{ + rf_gfx_color4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); +} + +// Enable texture usage +RF_API void rf_gfx_enable_texture(unsigned int id) +{ + if (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].texture_id != id) + { + if (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count > 0) + { + // Make sure current rf_ctx->gl_ctx.draws[i].vertex_count is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].mode == RF_LINES) + { + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment = ((rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count < 4) ? rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count : rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count % 4); + } + else if (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].mode == RF_TRIANGLES) + { + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment = ((rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count < 4) ? 1 : (4 - (rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count % 4))); + } + else + { + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment = 0; + } + + if (rf_gfx_check_buffer_limit(rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment)) + { + rf_gfx_draw(); + } + else + { + rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter += rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment; + rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter += rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment; + rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter += rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_alignment; + + rf_batch.draw_calls_counter++; + } + } + + if (rf_batch.draw_calls_counter >= RF_DEFAULT_BATCH_DRAW_CALLS_COUNT) + { + rf_gfx_draw(); + } + + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].texture_id = id; + rf_batch.draw_calls[rf_batch.draw_calls_counter - 1].vertex_count = 0; + } +} + +// Disable texture usage +RF_API void rf_gfx_disable_texture() +{ + // NOTE: If quads batch limit is reached, + // we force a draw call and next batch starts + if (rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter >= (rf_batch.vertex_buffers[rf_batch.current_buffer].elements_count * 4)) + { + rf_gfx_draw(); + } +} + +// Set texture parameters (wrap mode/filter mode) +RF_API void rf_gfx_set_texture_wrap(rf_texture2d texture, rf_texture_wrap_mode wrap_mode) +{ + rf_gl.BindTexture(GL_TEXTURE_2D, texture.id); + + switch (wrap_mode) + { + case RF_WRAP_REPEAT: + { + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } break; + + case RF_WRAP_CLAMP: + { + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } break; + + case RF_WRAP_MIRROR_REPEAT: + { + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); + } break; + + case RF_WRAP_MIRROR_CLAMP: + { + if (rf_gfx.extensions.tex_mirror_clamp_supported) + { + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRROR_CLAMP_EXT); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRROR_CLAMP_EXT); + } + else + { + RF_LOG(RF_LOG_TYPE_WARNING, "Clamp mirror wrap mode not supported"); + } + } break; + + default: break; + } + + rf_gl.BindTexture(GL_TEXTURE_2D, 0); +} + +// Set filter for texture +RF_API void rf_gfx_set_texture_filter(rf_texture2d texture, rf_texture_filter_mode filter_mode) +{ + rf_gl.BindTexture(GL_TEXTURE_2D, texture.id); + + // Used only in case of anisotropic filters + float anisotropic_value = 0; + + switch (filter_mode) + { + case RF_FILTER_POINT: + { + if (texture.mipmaps > 1) + { + // GL_NEAREST_MIPMAP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + + // GL_NEAREST - tex filter: POINT (no filter), no mipmaps + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + else + { + // GL_NEAREST - tex filter: POINT (no filter), no mipmaps + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + } break; + + case RF_FILTER_BILINEAR: + { + if (texture.mipmaps > 1) + { + // GL_LINEAR_MIPMAP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) + // Alternative: GL_NEAREST_MIPMAP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + + // GL_LINEAR - tex filter: BILINEAR, no mipmaps + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + // GL_LINEAR - tex filter: BILINEAR, no mipmaps + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + } break; + + case RF_FILTER_TRILINEAR: + { + if (texture.mipmaps > 1) + { + // GL_LINEAR_MIPMAP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + // GL_LINEAR - tex filter: BILINEAR, no mipmaps + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + RF_LOG(RF_LOG_TYPE_WARNING, "No mipmaps available for TRILINEAR texture filtering. Texture id: %d", texture.id); + + // GL_LINEAR - tex filter: BILINEAR, no mipmaps + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + } break; + + case RF_FILTER_ANISOTROPIC_4x: anisotropic_value = 4; + case RF_FILTER_ANISOTROPIC_8x: anisotropic_value = 8; + case RF_FILTER_ANISOTROPIC_16x: anisotropic_value = 16; + { + if (anisotropic_value <= rf_gfx.extensions.max_anisotropic_level) + { + rf_gl.TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropic_value); + } + else if (rf_gfx.extensions.max_anisotropic_level > 0.0f) + { + RF_LOG(RF_LOG_TYPE_WARNING, "Maximum anisotropic filter level supported is %i_x. Texture id: %d", rf_gfx.extensions.max_anisotropic_level, texture.id); + rf_gl.TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropic_value); + } + else + { + RF_LOG(RF_LOG_TYPE_WARNING, "Anisotropic filtering not supported"); + } + } break; + } + + rf_gl.BindTexture(GL_TEXTURE_2D, 0); +} + +// Enable rendering to texture (fbo) +RF_API void rf_gfx_enable_render_texture(unsigned int id) +{ + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, id); + + //rf_gl.Disable(GL_CULL_FACE); // Allow double side drawing for texture flipping + //glCullFace(GL_FRONT); +} + +// Disable rendering to texture +RF_API void rf_gfx_disable_render_texture(void) +{ + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); + + //rf_gl.Enable(GL_CULL_FACE); + //glCullFace(GL_BACK); +} + +// Enable depth test +RF_API void rf_gfx_enable_depth_test(void) { rf_gl.Enable(GL_DEPTH_TEST); } + +// Disable depth test +RF_API void rf_gfx_disable_depth_test(void) { rf_gl.Disable(GL_DEPTH_TEST); } + +// Enable backface culling +RF_API void rf_gfx_enable_backface_culling(void) { rf_gl.Enable(GL_CULL_FACE); } + +// Disable backface culling +RF_API void rf_gfx_disable_backface_culling(void) { rf_gl.Disable(GL_CULL_FACE); } + +// Enable scissor test +RF_API void rf_gfx_enable_scissor_test(void) { rf_gl.Enable(GL_SCISSOR_TEST); } + +// Disable scissor test +RF_API void rf_gfx_disable_scissor_test(void) { rf_gl.Disable(GL_SCISSOR_TEST); } + +// Scissor test +RF_API void rf_gfx_scissor(int x, int y, int width, int height) { rf_gl.Scissor(x, y, width, height); } + +// Enable wire mode +RF_API void rf_gfx_enable_wire_mode(void) +{ +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + rf_gl.PolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif +} + +// Disable wire mode +RF_API void rf_gfx_disable_wire_mode(void) +{ +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + rf_gl.PolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + +// Unload texture from GPU memory +RF_API void rf_gfx_delete_textures(unsigned int id) +{ + if (id > 0) rf_gl.DeleteTextures(1, &id); +} + +// Unload render texture from GPU memory +RF_API void rf_gfx_delete_render_textures(rf_render_texture2d target) +{ + if (target.texture.id > 0) rf_gl.DeleteTextures(1, &target.texture.id); + if (target.depth.id > 0) + { + if (target.depth_texture) rf_gl.DeleteTextures(1, &target.depth.id); + else rf_gl.DeleteRenderbuffers(1, &target.depth.id); + } + + if (target.id > 0) rf_gl.DeleteFramebuffers(1, &target.id); + + RF_LOG(RF_LOG_TYPE_INFO, "[FBO ID %i] Unloaded render texture data from VRAM (GPU)", target.id); +} + +// Unload shader from GPU memory +RF_API void rf_gfx_delete_shader(unsigned int id) +{ + if (id != 0) rf_gl.DeleteProgram(id); +} + +// Unload vertex data (VAO) from GPU memory +RF_API void rf_gfx_delete_vertex_arrays(unsigned int id) +{ + if (id != 0) rf_gl.DeleteVertexArrays(1, &id); + RF_LOG(RF_LOG_TYPE_INFO, "[VAO ID %i] Unloaded model data from VRAM (GPU)", id); +} + +// Unload vertex data (VBO) from GPU memory +RF_API void rf_gfx_delete_buffers(unsigned int id) +{ + if (id != 0) + { + rf_gl.DeleteBuffers(1, &id); + RF_LOG(RF_LOG_TYPE_INFO, "[VBO ID %i] Unloaded model vertex data from VRAM (GPU)", id); + } +} + +// Clear color buffer with color +RF_API void rf_gfx_clear_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + // rf_color values clamp to 0.0f(0) and 1.0f(255) + float cr = (float)r/255; + float cg = (float)g/255; + float cb = (float)b/255; + float ca = (float)a/255; + + rf_gl.ClearColor(cr, cg, cb, ca); +} + +// Clear used screen buffers (color and depth) +RF_API void rf_gfx_clear_screen_buffers(void) +{ + rf_gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: rf_color and Depth (Depth is used for 3D) + //rf_gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... +} + +// Update GPU buffer with new data +RF_API void rf_gfx_update_buffer(int buffer_id, void* data, int data_size) +{ + rf_gl.BindBuffer(GL_ARRAY_BUFFER, buffer_id); + rf_gl.BufferSubData(GL_ARRAY_BUFFER, 0, data_size, data); +} + +// Load a new attributes buffer +RF_API unsigned int rf_gfx_load_attrib_buffer(unsigned int vao_id, int shader_loc, void* buffer, int size, bool dynamic) +{ + unsigned int id = 0; + + int draw_hint = GL_STATIC_DRAW; + if (dynamic) draw_hint = GL_DYNAMIC_DRAW; + + rf_gl.BindVertexArray(vao_id); + + rf_gl.GenBuffers(1, &id); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, id); + rf_gl.BufferData(GL_ARRAY_BUFFER, size, buffer, draw_hint); + rf_gl.VertexAttribPointer(shader_loc, 2, GL_FLOAT, 0, 0, 0); + rf_gl.EnableVertexAttribArray(shader_loc); + + rf_gl.BindVertexArray(0); + + return id; +} + +RF_API void rf_gfx_init_vertex_buffer(rf_vertex_buffer* vertex_buffer) +{ + int elements_count = vertex_buffer->elements_count; + + // Initialize Quads VAO + rf_gl.GenVertexArrays(1, &vertex_buffer->vao_id); + rf_gl.BindVertexArray(vertex_buffer->vao_id); + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + rf_gl.GenBuffers(1, &vertex_buffer->vbo_id[0]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, vertex_buffer->vbo_id[0]); + rf_gl.BufferData(GL_ARRAY_BUFFER, elements_count * RF_GFX_VERTEX_COMPONENT_COUNT, vertex_buffer->vertices, GL_DYNAMIC_DRAW); + rf_gl.EnableVertexAttribArray(rf_ctx.current_shader.locs[RF_LOC_VERTEX_POSITION]); + rf_gl.VertexAttribPointer(rf_ctx.current_shader.locs[RF_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + rf_gl.GenBuffers(1, &vertex_buffer->vbo_id[1]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, vertex_buffer->vbo_id[1]); + rf_gl.BufferData(GL_ARRAY_BUFFER, elements_count * RF_GFX_TEXCOORD_COMPONENT_COUNT, vertex_buffer->texcoords, GL_DYNAMIC_DRAW); + rf_gl.EnableVertexAttribArray(rf_ctx.current_shader.locs[RF_LOC_VERTEX_TEXCOORD01]); + rf_gl.VertexAttribPointer(rf_ctx.current_shader.locs[RF_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + rf_gl.GenBuffers(1, &vertex_buffer->vbo_id[2]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, vertex_buffer->vbo_id[2]); + rf_gl.BufferData(GL_ARRAY_BUFFER, elements_count * RF_GFX_COLOR_COMPONENT_COUNT, vertex_buffer->colors, GL_DYNAMIC_DRAW); + rf_gl.EnableVertexAttribArray(rf_ctx.current_shader.locs[RF_LOC_VERTEX_COLOR]); + rf_gl.VertexAttribPointer(rf_ctx.current_shader.locs[RF_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + rf_gl.GenBuffers(1, &vertex_buffer->vbo_id[3]); + rf_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertex_buffer->vbo_id[3]); + rf_gl.BufferData(GL_ELEMENT_ARRAY_BUFFER, elements_count * RF_GFX_VERTEX_INDEX_COMPONENT_COUNT, vertex_buffer->indices, GL_STATIC_DRAW); + + // Unbind + rf_gl.BindVertexArray(0); +} + +// Vertex Buffer Object deinitialization (memory free) +RF_API void rf_gfx_close() +{ + rf_unlock_shader_default(); // Unload default shader + rf_unload_buffers_default(); // Unload default buffers + + rf_gl.DeleteTextures(1, &rf_ctx.default_texture_id); // Unload default texture + + RF_LOG(RF_LOG_TYPE_INFO, "[TEX ID %i] Unloaded texture data (base white texture) from VRAM", rf_ctx.default_texture_id); +} + +// Update and draw internal buffers +RF_API void rf_gfx_draw() +{ + // Only process data if we have data to process + if (rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter > 0) + { + // Update default internal buffers (VAOs/VBOs) with vertex array data + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertex_count > 0) + // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) + { + // Update vertex buffers data + // Activate elements VAO + rf_gl.BindVertexArray(rf_batch.vertex_buffers[rf_batch.current_buffer].vao_id); + + // Vertex positions buffer + rf_gl.BindBuffer(GL_ARRAY_BUFFER, rf_batch.vertex_buffers[rf_batch.current_buffer].vbo_id[0]); + rf_gl.BufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 3 * rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter, rf_batch.vertex_buffers[rf_batch.current_buffer].vertices); + //rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * 4 * rf_max_batch_elements, rf_ctx->gl_ctx.memory->vertex_buffers[rf_ctx->gl_ctx.current_buffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // rf_texture coordinates buffer + rf_gl.BindBuffer(GL_ARRAY_BUFFER, rf_batch.vertex_buffers[rf_batch.current_buffer].vbo_id[1]); + rf_gl.BufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * 2 * rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter, rf_batch.vertex_buffers[rf_batch.current_buffer].texcoords); + //rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 2 * 4 * rf_max_batch_elements, rf_ctx->gl_ctx.memory->vertex_buffers[rf_ctx->gl_ctx.current_buffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + rf_gl.BindBuffer(GL_ARRAY_BUFFER, rf_batch.vertex_buffers[rf_batch.current_buffer].vbo_id[2]); + rf_gl.BufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char) * 4 * rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter, rf_batch.vertex_buffers[rf_batch.current_buffer].colors); + //rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 4 * 4 * rf_max_batch_elements, rf_ctx->gl_ctx.memory->vertex_buffers[rf_ctx->gl_ctx.current_buffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMap_buffer() causes sync issue. + // If GPU is working with this buffer, glMap_buffer() will wait(stall) until GPU to finish its job. + // To avoid waiting (idle), you can call first rf_gl.BufferData() with NULL pointer before glMap_buffer(). + // If you do that, the previous data in PBO will be discarded and glMap_buffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data. + + // Another option: map the buffer object into client's memory + // Probably this code could be moved somewhere else... + // rf_ctx->gl_ctx.memory->vertex_buffers[rf_ctx->gl_ctx.current_buffer].vertices = (float* )glMap_buffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (rf_ctx->gl_ctx.memory->vertex_buffers[rf_ctx->gl_ctx.current_buffer].vertices) + // { + // Update vertex data + // } + // glUnmap_buffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + rf_gl.BindVertexArray(0); + } + + // NOTE: Stereo rendering is checked inside + //rf_draw_buffers_default(); + { + // Draw default internal buffers vertex data + rf_mat mat_projection = rf_ctx.projection; + rf_mat mat_model_view = rf_ctx.modelview; + + // Draw buffers + if (rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter > 0) + { + // Set current shader and upload current MVP matrix + rf_gl.UseProgram(rf_ctx.current_shader.id); + + // Create rf_ctx->gl_ctx.modelview-rf_ctx->gl_ctx.projection matrix + rf_mat mat_mvp = rf_mat_mul(rf_ctx.modelview, rf_ctx.projection); + + rf_gl.UniformMatrix4fv(rf_ctx.current_shader.locs[RF_LOC_MATRIX_MVP], 1, false, rf_mat_to_float16(mat_mvp).v); + rf_gl.Uniform4f(rf_ctx.current_shader.locs[RF_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + rf_gl.Uniform1i(rf_ctx.current_shader.locs[RF_LOC_MAP_DIFFUSE], 0); // Provided value refers to the texture unit (active) + + // TODO: Support additional texture units on custom shader + //if (rf_ctx->gl_ctx.current_shader->locs[RF_LOC_MAP_SPECULAR] > 0) rf_gl.Uniform1i(rf_ctx->gl_ctx.current_shader.locs[RF_LOC_MAP_SPECULAR], 1); + //if (rf_ctx->gl_ctx.current_shader->locs[RF_LOC_MAP_NORMAL] > 0) rf_gl.Uniform1i(rf_ctx->gl_ctx.current_shader.locs[RF_LOC_MAP_NORMAL], 2); + + // NOTE: Right now additional map textures not considered for default buffers drawing + + int vertex_offset = 0; + + rf_gl.BindVertexArray(rf_batch.vertex_buffers[rf_batch.current_buffer].vao_id); + + rf_gl.ActiveTexture(GL_TEXTURE0); + + for (rf_int i = 0; i < rf_batch.draw_calls_counter; i++) + { + rf_gl.BindTexture(GL_TEXTURE_2D, rf_batch.draw_calls[i].texture_id); + + // TODO: Find some way to bind additional textures --> Use global texture IDs? Register them on draw[i]? + //if (rf_ctx->gl_ctx.current_shader->locs[RF_LOC_MAP_SPECULAR] > 0) { rf_gl.ActiveTexture(GL_TEXTURE1); rf_gl.BindTexture(GL_TEXTURE_2D, textureUnit1_id); } + //if (rf_ctx->gl_ctx.current_shader->locs[RF_LOC_MAP_SPECULAR] > 0) { rf_gl.ActiveTexture(GL_TEXTURE2); rf_gl.BindTexture(GL_TEXTURE_2D, textureUnit2_id); } + + if ((rf_batch.draw_calls[i].mode == RF_LINES) || (rf_batch.draw_calls[i].mode == RF_TRIANGLES)) + { + rf_gl.DrawArrays(rf_batch.draw_calls[i].mode, vertex_offset, rf_batch.draw_calls[i].vertex_count); + } + else + { + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + // We need to define the number of indices to be processed: quadsCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + rf_gl.DrawElements(GL_TRIANGLES, rf_batch.draw_calls[i].vertex_count / 4 * 6, GL_UNSIGNED_INT, (void*)(sizeof(unsigned int) * vertex_offset / 4 * 6)); + #elif defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + rf_gl.DrawElements(GL_TRIANGLES, rf_batch.draw_calls[i].vertex_count / 4 * 6, GL_UNSIGNED_SHORT, (void*)(sizeof(unsigned short) * vertex_offset / 4 * 6)); + #endif + } + + vertex_offset += (rf_batch.draw_calls[i].vertex_count + rf_batch.draw_calls[i].vertex_alignment); + } + + rf_gl.BindTexture(GL_TEXTURE_2D, 0); + } + + rf_gl.BindVertexArray(0); + rf_gl.UseProgram(0); + + // Reset vertex counters for next frame + rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter = 0; + rf_batch.vertex_buffers[rf_batch.current_buffer].tc_counter = 0; + rf_batch.vertex_buffers[rf_batch.current_buffer].c_counter = 0; + + // Reset depth for next draw + rf_batch.current_depth = -1.0f; + + // Restore rf_ctx->gl_ctx.projection/rf_ctx->gl_ctx.modelview matrices + rf_ctx.projection = mat_projection; + rf_ctx.modelview = mat_model_view; + + // Reset rf_ctx->gl_ctx.draws array + for (rf_int i = 0; i < RF_DEFAULT_BATCH_DRAW_CALLS_COUNT; i++) + { + rf_batch.draw_calls[i].mode = RF_QUADS; + rf_batch.draw_calls[i].vertex_count = 0; + rf_batch.draw_calls[i].texture_id = rf_ctx.default_texture_id; + } + + rf_batch.draw_calls_counter = 1; + + // Change to next buffer in the list + rf_batch.current_buffer++; + if (rf_batch.current_buffer >= RF_DEFAULT_BATCH_VERTEX_BUFFERS_COUNT) rf_batch.current_buffer = 0; + } + } +} + +// Check internal buffer overflow for a given number of vertex +RF_API bool rf_gfx_check_buffer_limit(int v_count) +{ + return (rf_batch.vertex_buffers[rf_batch.current_buffer].v_counter + v_count) >= (rf_batch.vertex_buffers[rf_batch.current_buffer].elements_count * 4); +} + +// Set debug marker +RF_API void rf_gfx_set_debug_marker(const char* text) +{ +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + //if (rf_ctx->gl_ctx.debug_marker_supported) glInsertEventMarkerEXT(0, text); +#endif +} + +// Convert image data to OpenGL texture (returns OpenGL valid Id) +RF_API unsigned int rf_gfx_load_texture(void* data, int width, int height, rf_pixel_format format, int mipmap_count) +{ + rf_gl.BindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + unsigned int id = 0; + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) + if ((!rf_gfx.extensions.tex_comp_dxt_supported) && ((format == RF_COMPRESSED_DXT1_RGB) || (format == RF_COMPRESSED_DXT1_RGBA) || (format == RF_COMPRESSED_DXT3_RGBA) || (format == RF_COMPRESSED_DXT5_RGBA))) + { + RF_LOG(RF_LOG_TYPE_WARNING, "DXT compressed texture format not supported"); + return id; + } + + if ((!rf_gfx.extensions.tex_comp_etc1_supported) && (format == RF_COMPRESSED_ETC1_RGB)) + { + RF_LOG(RF_LOG_TYPE_WARNING, "ETC1 compressed texture format not supported"); + return id; + } + + if ((!rf_gfx.extensions.tex_comp_etc2_supported) && ((format == RF_COMPRESSED_ETC2_RGB) || (format == RF_COMPRESSED_ETC2_EAC_RGBA))) + { + RF_LOG(RF_LOG_TYPE_WARNING, "ETC2 compressed texture format not supported"); + return id; + } + + if ((!rf_gfx.extensions.tex_comp_pvrt_supported) && ((format == RF_COMPRESSED_PVRT_RGB) || (format == RF_COMPRESSED_PVRT_RGBA))) + { + RF_LOG(RF_LOG_TYPE_WARNING, "PVRT compressed texture format not supported"); + return id; + } + + if ((!rf_gfx.extensions.tex_comp_astc_supported) && ((format == RF_COMPRESSED_ASTC_4x4_RGBA) || (format == RF_COMPRESSED_ASTC_8x8_RGBA))) + { + RF_LOG(RF_LOG_TYPE_WARNING, "ASTC compressed texture format not supported"); + return id; + } + + rf_gl.PixelStorei(GL_UNPACK_ALIGNMENT, 1); + + rf_gl.GenTextures(1, &id); // Generate texture id + + //rf_gl.ActiveTexture(GL_TEXTURE0); // If not defined, using GL_TEXTURE0 by default (shader texture) + + rf_gl.BindTexture(GL_TEXTURE_2D, id); + + int mip_width = width; + int mip_height = height; + int mip_offset = 0; // Mipmap data offset + + RF_LOG(RF_LOG_TYPE_DEBUG, "Load texture from data memory address: 0x%x", data); + + // Load the different mipmap levels + for (rf_int i = 0; i < mipmap_count; i++) + { + int mip_size = rf_pixel_buffer_size(mip_width, mip_height, format); + + rf_gfx_pixel_format glformat = rf_gfx_get_internal_texture_formats(format); + + RF_LOG(RF_LOG_TYPE_DEBUG, "Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mip_width, mip_height, mip_size, mip_offset); + + if (glformat.valid) + { + if (rf_is_uncompressed_format(format)) + { + rf_gl.TexImage2D(GL_TEXTURE_2D, i, glformat.internal_format, mip_width, mip_height, 0, glformat.format, glformat.type, (unsigned char* )data + mip_offset); + } + else + { + rf_gl.CompressedTexImage2D(GL_TEXTURE_2D, i, glformat.internal_format, mip_width, mip_height, 0, mip_size, (unsigned char *)data + mip_offset); + } + + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + if (format == RF_UNCOMPRESSED_GRAYSCALE) + { + int swizzle_mask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + rf_gl.TexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle_mask); + } + else if (format == RF_UNCOMPRESSED_GRAY_ALPHA) + { + int swizzle_mask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; + rf_gl.TexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle_mask); + } + #endif + } + + mip_width /= 2; + mip_height /= 2; + mip_offset += mip_size; + + // Security check for NPOT textures + if (mip_width < 1) mip_width = 1; + if (mip_height < 1) mip_height = 1; + } + + // rf_texture parameters configuration + // NOTE: rf_gl.TexParameteri does NOT affect texture uploading, just the way it's used + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + if (rf_gfx.extensions.tex_npot_supported) + { + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + } + else + { + // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis + } + #else + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + #endif + + // Magnification and minification filters + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + if (mipmap_count > 1) + { + // Activate Trilinear filtering if mipmaps are available + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + #endif + + // At this point we have the texture loaded in GPU and texture parameters configured + + // NOTE: If mipmaps were not in data, they are not generated automatically + + // Unbind current texture + rf_gl.BindTexture(GL_TEXTURE_2D, 0); + + if (id > 0) RF_LOG(RF_LOG_TYPE_INFO, "[TEX ID %i] rf_texture created successfully (%ix%i - %i mipmaps)", id, width, height, mipmap_count); + else RF_LOG(RF_LOG_TYPE_WARNING, "rf_texture could not be created"); + + return id; +} + +// Load depth texture/renderbuffer (to be attached to fbo) +// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture/WEBGL_depth_texture extensions +RF_API unsigned int rf_gfx_load_texture_depth(int width, int height, int bits, bool use_render_buffer) +{ + unsigned int id = 0; + + unsigned int glInternalFormat = GL_DEPTH_COMPONENT16; + + if ((bits != 16) && (bits != 24) && (bits != 32)) bits = 16; + + if (bits == 24) + { + glInternalFormat = GL_DEPTH_COMPONENT24; + } + + if (bits == 32 && rf_gfx.extensions.max_depth_bits == 32) // Check max_depth_bits to make sure its ok on mobile + { + glInternalFormat = GL_DEPTH_COMPONENT32; + } + + if (!use_render_buffer && rf_gfx.extensions.tex_depth_supported) + { + rf_gl.GenTextures(1, &id); + rf_gl.BindTexture(GL_TEXTURE_2D, id); + rf_gl.TexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + rf_gl.BindTexture(GL_TEXTURE_2D, 0); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + rf_gl.GenRenderbuffers(1, &id); + rf_gl.BindRenderbuffer(GL_RENDERBUFFER, id); + rf_gl.RenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + rf_gl.BindRenderbuffer(GL_RENDERBUFFER, 0); + } + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single column, +// expected the following convention: +X, -X, +Y, -Y, +Z, -Z +RF_API unsigned int rf_gfx_load_texture_cubemap(void* data, int size, rf_pixel_format format) +{ + unsigned int cubemap_id = 0; + unsigned int data_size = size * size * rf_bytes_per_pixel(format); + + rf_gl.GenTextures(1, &cubemap_id); + rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, cubemap_id); + + rf_gfx_pixel_format glformat = rf_gfx_get_internal_texture_formats(format); + + if (glformat.valid) + { + // Load cubemap faces + for (unsigned int i = 0; i < 6; i++) + { + if (rf_is_uncompressed_format(format)) rf_gl.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glformat.internal_format, size, size, 0, glformat.format, glformat.type, (unsigned char* )data + i * data_size); + else rf_gl.CompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glformat.internal_format, size, size, 0, data_size, (unsigned char* )data + i*data_size); + + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + if (format == RF_UNCOMPRESSED_GRAYSCALE) + { + int swizzle_mask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + rf_gl.TexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzle_mask); + } + else if (format == RF_UNCOMPRESSED_GRAY_ALPHA) + { + int swizzle_mask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; + rf_gl.TexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzle_mask); + } + #endif + } + } + + // Set cubemap texture sampling parameters + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 + #endif // defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + + rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, 0); + + return cubemap_id; +} + +// Update already loaded texture in GPU with new data +// NOTE: We don't know safely if internal texture format is the expected one... +RF_API void rf_gfx_update_texture(unsigned int id, int width, int height, rf_pixel_format format, const void* pixels, int pixels_size) +{ + if (width * height * rf_bytes_per_pixel(format) <= pixels_size) return; + + rf_gl.BindTexture(GL_TEXTURE_2D, id); + + rf_gfx_pixel_format gfx_format = rf_gfx_get_internal_texture_formats(format); + + if (gfx_format.valid && rf_is_uncompressed_format(format)) + { + rf_gl.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, gfx_format.internal_format, gfx_format.type, (unsigned char*) pixels); + } + else RF_LOG(RF_LOG_TYPE_WARNING, "rf_texture format updating not supported"); +} + +// Get OpenGL internal formats and data type from raylib rf_pixel_format +RF_API rf_gfx_pixel_format rf_gfx_get_internal_texture_formats(rf_pixel_format format) +{ + rf_gfx_pixel_format result = {.valid = true}; + + switch (format) + { + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + case RF_UNCOMPRESSED_GRAYSCALE: result.internal_format = GL_LUMINANCE; result.format = GL_LUMINANCE; result.type = GL_UNSIGNED_BYTE; break; + case RF_UNCOMPRESSED_GRAY_ALPHA: result.internal_format = GL_LUMINANCE_ALPHA; result.format = GL_LUMINANCE_ALPHA; result.type = GL_UNSIGNED_BYTE; break; + case RF_UNCOMPRESSED_R5G6B5: result.internal_format = GL_RGB; result.format = GL_RGB; result.type = GL_UNSIGNED_SHORT_5_6_5; break; + case RF_UNCOMPRESSED_R8G8B8: result.internal_format = GL_RGB; result.format = GL_RGB; result.type = GL_UNSIGNED_BYTE; break; + case RF_UNCOMPRESSED_R5G5B5A1: result.internal_format = GL_RGBA; result.format = GL_RGBA; result.type = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RF_UNCOMPRESSED_R4G4B4A4: result.internal_format = GL_RGBA; result.format = GL_RGBA; result.type = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RF_UNCOMPRESSED_R8G8B8A8: result.internal_format = GL_RGBA; result.format = GL_RGBA; result.type = GL_UNSIGNED_BYTE; break; + case RF_UNCOMPRESSED_R32: if (rf_gfx.extensions.tex_float_supported) result.internal_format = GL_LUMINANCE; result.format = GL_LUMINANCE; result.type = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RF_UNCOMPRESSED_R32G32B32: if (rf_gfx.extensions.tex_float_supported) result.internal_format = GL_RGB; result.format = GL_RGB; result.type = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RF_UNCOMPRESSED_R32G32B32A32: if (rf_gfx.extensions.tex_float_supported) result.internal_format = GL_RGBA; result.format = GL_RGBA; result.type = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + + #elif defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + //case RF_UNCOMPRESSED_R5G6B5: result.internal_format = GL_RGB565; result.format = GL_RGB; result.type = GL_UNSIGNED_SHORT_5_6_5; break; + case RF_UNCOMPRESSED_GRAYSCALE: result.internal_format = GL_R8; result.format = GL_RED; result.type = GL_UNSIGNED_BYTE; break; + case RF_UNCOMPRESSED_GRAY_ALPHA: result.internal_format = GL_RG8; result.format = GL_RG; result.type = GL_UNSIGNED_BYTE; break; + case RF_UNCOMPRESSED_R8G8B8: result.internal_format = GL_RGB8; result.format = GL_RGB; result.type = GL_UNSIGNED_BYTE; break; + case RF_UNCOMPRESSED_R5G5B5A1: result.internal_format = GL_RGB5_A1; result.format = GL_RGBA; result.type = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RF_UNCOMPRESSED_R4G4B4A4: result.internal_format = GL_RGBA4; result.format = GL_RGBA; result.type = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RF_UNCOMPRESSED_R8G8B8A8: result.internal_format = GL_RGBA8; result.format = GL_RGBA; result.type = GL_UNSIGNED_BYTE; break; + case RF_UNCOMPRESSED_R32: if (rf_gfx.extensions.tex_float_supported) result.internal_format = GL_R32F; result.format = GL_RED; result.type = GL_FLOAT; break; + case RF_UNCOMPRESSED_R32G32B32: if (rf_gfx.extensions.tex_float_supported) result.internal_format = GL_RGB32F; result.format = GL_RGB; result.type = GL_FLOAT; break; + case RF_UNCOMPRESSED_R32G32B32A32: if (rf_gfx.extensions.tex_float_supported) result.internal_format = GL_RGBA32F; result.format = GL_RGBA; result.type = GL_FLOAT; break; + #endif + + case RF_COMPRESSED_DXT1_RGB: if (rf_gfx.extensions.tex_comp_dxt_supported) result.internal_format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case RF_COMPRESSED_DXT1_RGBA: if (rf_gfx.extensions.tex_comp_dxt_supported) result.internal_format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case RF_COMPRESSED_DXT3_RGBA: if (rf_gfx.extensions.tex_comp_dxt_supported) result.internal_format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case RF_COMPRESSED_DXT5_RGBA: if (rf_gfx.extensions.tex_comp_dxt_supported) result.internal_format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case RF_COMPRESSED_ETC1_RGB: if (rf_gfx.extensions.tex_comp_etc1_supported) result.internal_format = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case RF_COMPRESSED_ETC2_RGB: if (rf_gfx.extensions.tex_comp_etc2_supported) result.internal_format = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RF_COMPRESSED_ETC2_EAC_RGBA: if (rf_gfx.extensions.tex_comp_etc2_supported) result.internal_format = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RF_COMPRESSED_PVRT_RGB: if (rf_gfx.extensions.tex_comp_pvrt_supported) result.internal_format = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RF_COMPRESSED_PVRT_RGBA: if (rf_gfx.extensions.tex_comp_pvrt_supported) result.internal_format = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RF_COMPRESSED_ASTC_4x4_RGBA: if (rf_gfx.extensions.tex_comp_astc_supported) result.internal_format = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case RF_COMPRESSED_ASTC_8x8_RGBA: if (rf_gfx.extensions.tex_comp_astc_supported) result.internal_format = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + + default: + RF_LOG(RF_LOG_TYPE_WARNING, "rf_texture format not supported"); + result.valid = false; + break; + } + + return result; +} + +// Unload texture from GPU memory +RF_API void rf_gfx_unload_texture(unsigned int id) +{ + if (id > 0) rf_gl.DeleteTextures(1, &id); +} + +// Generate mipmap data for selected texture +RF_API void rf_gfx_generate_mipmaps(rf_texture2d* texture) +{ + rf_gl.BindTexture(GL_TEXTURE_2D, texture->id); + + // Check if texture is power-of-two (POT) + bool tex_is_pot = false; + + if (((texture->width > 0) && ((texture->width & (texture->width - 1)) == 0)) && + ((texture->height > 0) && ((texture->height & (texture->height - 1)) == 0))) tex_is_pot = true; + + if ((tex_is_pot) || (rf_gfx.extensions.tex_npot_supported)) + { + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + rf_gl.GenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + RF_LOG(RF_LOG_TYPE_INFO, "[TEX ID %i] Mipmaps generated automatically", texture->id); + + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps + + texture->mipmaps = 1 + (int)floor(log(texture->width > texture->height ? texture->width : texture->height)/log(2)); + } + else RF_LOG(RF_LOG_TYPE_WARNING, "[TEX ID %i] Mipmaps can not be generated", texture->id); + + rf_gl.BindTexture(GL_TEXTURE_2D, 0); +} + +// Read texture pixel data +RF_API rf_image rf_gfx_read_texture_pixels_to_buffer(rf_texture2d texture, void* dst, int dst_size) +{ + if (!texture.valid || !dst || !dst_size) return (rf_image) {0}; + + rf_image result = {0}; + + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + { + rf_gl.BindTexture(GL_TEXTURE_2D, texture.id); + + /* + NOTE: Using texture.id, we can retrieve some texture info (but not on OpenGL ES 2.0) + Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + int width, height, format; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + NOTE: Each row written to or read from by OpenGL pixel operations like rf_gl.GetTexImage are aligned to a 4 unsigned char boundary by default, which may add some padding. + Use rf_gl.PixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting. + GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (rf_gl.ReadPixels, rf_gl.GetTexImage, etc.) + GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + */ + + rf_gl.PixelStorei(GL_PACK_ALIGNMENT, 1); + + rf_gfx_pixel_format format = rf_gfx_get_internal_texture_formats(texture.format); + int size = texture.width * texture.height * rf_bytes_per_pixel(texture.format); + + if (format.valid && rf_is_uncompressed_format(texture.format) && size <= dst_size) + { + rf_gl.GetTexImage(GL_TEXTURE_2D, 0, format.format, format.type, dst); + + result = (rf_image) { + .width = texture.width, + .height = texture.height, + .format = texture.format, + .data = dst, + .valid = true, + }; + } + else RF_LOG(RF_LOG_TYPE_WARNING, "rf_texture data retrieval not suported for pixel format"); + + rf_gl.BindTexture(GL_TEXTURE_2D, 0); + } + #elif defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + { + /* + rf_gl.GetTexImage() is not available on OpenGL ES 2.0 + rf_texture2d width and height are required on OpenGL ES 2.0. There is no way to get it from texture id. + Two possible Options: + 1 - Bind texture to color fbo attachment and rf_gl.ReadPixels() + 2 - Create an fbo, activate it, render quad with texture, rf_gl.ReadPixels() + We are using Option 1, just need to care for texture format on retrieval + NOTE: This behaviour could be conditioned by graphic driver... + */ + rf_render_texture2d fbo = rf_gfx_load_render_texture(texture.width, texture.height, RF_UNCOMPRESSED_R8G8B8A8, 16, false); + + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, fbo.id); + rf_gl.BindTexture(GL_TEXTURE_2D, 0); + + // Attach our texture to FBO + // NOTE: Previoust attached texture is automatically detached + rf_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0); + + // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + if (texture.width * texture.height * rf_bytes_per_pixel(RF_UNCOMPRESSED_R8G8B8A8) <= dst_size) + { + rf_gl.ReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, dst); + + result = (rf_image) { + .width = texture.width, + .height = texture.height, + .format = texture.format, + .data = dst, + .valid = true, + }; + } + + // Re-attach internal FBO color texture before deleting it + rf_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.texture.id, 0); + + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); + + // Clean up temporal fbo + rf_gfx_delete_render_textures(fbo); + } + #endif + + return result; +} + +RF_API rf_image rf_gfx_read_texture_pixels(rf_texture2d texture, rf_allocator allocator) +{ + if (!texture.valid) return (rf_image) {0}; + + int size = texture.width * texture.height * rf_bytes_per_pixel(texture.format); + void* dst = RF_ALLOC(allocator, size); + rf_image result = rf_gfx_read_texture_pixels_to_buffer(texture, dst, size); + + if (!result.valid) RF_FREE(allocator, dst); + + return result; +} + +// Read screen pixel data (color buffer) +RF_API void rf_gfx_read_screen_pixels(rf_color* dst, int width, int height) +{ + // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! + rf_gl.ReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst); + + for (rf_int y = height - 1; y >= 0; y--) + { + for (rf_int x = 0; x < width; x++) + { + dst[((height - 1) - y) * width + x] = dst[(y * width) + x]; // Flip line + + // Set alpha component value to 255 (no trasparent image retrieval) + // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + if (((x + 1) % 4) == 0) dst[((height - 1) - y) * width + x].a = 255; + } + } +} + +// Load a texture to be used for rendering (fbo with default color and depth attachments) +// NOTE: If colorFormat or depth_bits are no supported, no attachment is done +RF_API rf_render_texture2d rf_gfx_load_render_texture(int width, int height, rf_pixel_format format, int depth_bits, bool use_depth_texture) +{ + rf_render_texture2d target = { 0 }; + + if (use_depth_texture && rf_gfx.extensions.tex_depth_supported) target.depth_texture = true; + + // Create the framebuffer object + rf_gl.GenFramebuffers(1, &target.id); + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, target.id); + + // Create fbo color texture attachment + //----------------------------------------------------------------------------------------------------- + if (rf_is_uncompressed_format(format)) + { + // WARNING: Some texture formats are not supported for fbo color attachment + target.texture.id = rf_gfx_load_texture(NULL, width, height, format, 1); + target.texture.width = width; + target.texture.height = height; + target.texture.format = format; + target.texture.mipmaps = 1; + } + //----------------------------------------------------------------------------------------------------- + + // Create fbo depth renderbuffer/texture + //----------------------------------------------------------------------------------------------------- + if (depth_bits > 0) + { + target.depth.id = rf_gfx_load_texture_depth(width, height, depth_bits, !use_depth_texture); + target.depth.width = width; + target.depth.height = height; + target.depth.format = 19; //DEPTH_COMPONENT_24BIT? + target.depth.mipmaps = 1; + } + //----------------------------------------------------------------------------------------------------- + + // Attach color texture and depth renderbuffer to FBO + //----------------------------------------------------------------------------------------------------- + rf_gfx_render_texture_attach(target, target.texture.id, 0); // COLOR attachment + rf_gfx_render_texture_attach(target, target.depth.id, 1); // DEPTH attachment + //----------------------------------------------------------------------------------------------------- + + // Check if fbo is complete with attachments (valid) + //----------------------------------------------------------------------------------------------------- + if (rf_gfx_render_texture_complete(target)) RF_LOG(RF_LOG_TYPE_INFO, "[FBO ID %i] Framebuffer object created successfully", target.id); + //----------------------------------------------------------------------------------------------------- + + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); + + return target; +} + +// Attach color buffer texture to an fbo (unloads previous attachment) +// NOTE: Attach type: 0-rf_color, 1-Depth renderbuffer, 2-Depth texture +RF_API void rf_gfx_render_texture_attach(rf_render_texture2d target, unsigned int id, int attach_type) +{ + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, target.id); + + if (attach_type == 0) rf_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); + else if (attach_type == 1) + { + if (target.depth_texture) rf_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, id, 0); + else rf_gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, id); + } + + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); +} + +// Verify render texture is complete +RF_API bool rf_gfx_render_texture_complete(rf_render_texture2d target) +{ + bool result = false; + + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, target.id); + + unsigned int status = rf_gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: RF_LOG(RF_LOG_TYPE_WARNING, "Framebuffer is unsupported"); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: RF_LOG(RF_LOG_TYPE_WARNING, "Framebuffer has incomplete attachment"); break; + + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: RF_LOG(RF_LOG_TYPE_WARNING, "Framebuffer has incomplete dimensions"); break; + #endif + + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: RF_LOG(RF_LOG_TYPE_WARNING, "Framebuffer has a missing attachment"); break; + default: break; + } + } + + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); + + return result; +} + +// Upload vertex data into a VAO (if supported) and VBO +RF_API void rf_gfx_load_mesh(rf_mesh* mesh, bool dynamic) +{ + if (mesh->vao_id > 0) + { + // Check if mesh has already been loaded in GPU + RF_LOG(RF_LOG_TYPE_WARNING, "Trying to re-load an already loaded mesh"); + return; + } + + mesh->vao_id = 0; // Vertex Array Object + mesh->vbo_id[0] = 0; // Vertex positions VBO + mesh->vbo_id[1] = 0; // Vertex texcoords VBO + mesh->vbo_id[2] = 0; // Vertex normals VBO + mesh->vbo_id[3] = 0; // Vertex colors VBO + mesh->vbo_id[4] = 0; // Vertex tangents VBO + mesh->vbo_id[5] = 0; // Vertex texcoords2 VBO + mesh->vbo_id[6] = 0; // Vertex indices VBO + + int draw_hint = GL_STATIC_DRAW; + if (dynamic) draw_hint = GL_DYNAMIC_DRAW; + + // Initialize Quads VAO (Buffer A) + rf_gl.GenVertexArrays(1, &mesh->vao_id); + rf_gl.BindVertexArray(mesh->vao_id); + + // NOTE: Attributes must be uploaded considering default locations points + + // Enable vertex attributes: position (shader-location = 0) + rf_gl.GenBuffers(1, &mesh->vbo_id[0]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh->vbo_id[0]); + rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 3*mesh->vertex_count, mesh->vertices, draw_hint); + rf_gl.VertexAttribPointer(0, 3, GL_FLOAT, 0, 0, 0); + rf_gl.EnableVertexAttribArray(0); + + // Enable vertex attributes: texcoords (shader-location = 1) + rf_gl.GenBuffers(1, &mesh->vbo_id[1]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh->vbo_id[1]); + rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 2*mesh->vertex_count, mesh->texcoords, draw_hint); + rf_gl.VertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0); + rf_gl.EnableVertexAttribArray(1); + + // Enable vertex attributes: normals (shader-location = 2) + if (mesh->normals != NULL) + { + rf_gl.GenBuffers(1, &mesh->vbo_id[2]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh->vbo_id[2]); + rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 3*mesh->vertex_count, mesh->normals, draw_hint); + rf_gl.VertexAttribPointer(2, 3, GL_FLOAT, 0, 0, 0); + rf_gl.EnableVertexAttribArray(2); + } + else + { + // Default color vertex attribute set to RF_WHITE + rf_gl.VertexAttrib3f(2, 1.0f, 1.0f, 1.0f); + rf_gl.DisableVertexAttribArray(2); + } + + // Default color vertex attribute (shader-location = 3) + if (mesh->colors != NULL) + { + rf_gl.GenBuffers(1, &mesh->vbo_id[3]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh->vbo_id[3]); + rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(unsigned char) * 4 * mesh->vertex_count, mesh->colors, draw_hint); + rf_gl.VertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + rf_gl.EnableVertexAttribArray(3); + } + else + { + // Default color vertex attribute set to RF_WHITE + rf_gl.VertexAttrib4f(3, 1.0f, 1.0f, 1.0f, 1.0f); + rf_gl.DisableVertexAttribArray(3); + } + + // Default tangent vertex attribute (shader-location = 4) + if (mesh->tangents != NULL) + { + rf_gl.GenBuffers(1, &mesh->vbo_id[4]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh->vbo_id[4]); + rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 4 * mesh->vertex_count, mesh->tangents, draw_hint); + rf_gl.VertexAttribPointer(4, 4, GL_FLOAT, 0, 0, 0); + rf_gl.EnableVertexAttribArray(4); + } + else + { + // Default tangents vertex attribute + rf_gl.VertexAttrib4f(4, 0.0f, 0.0f, 0.0f, 0.0f); + rf_gl.DisableVertexAttribArray(4); + } + + // Default texcoord2 vertex attribute (shader-location = 5) + if (mesh->texcoords2 != NULL) + { + rf_gl.GenBuffers(1, &mesh->vbo_id[5]); + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh->vbo_id[5]); + rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 2*mesh->vertex_count, mesh->texcoords2, draw_hint); + rf_gl.VertexAttribPointer(5, 2, GL_FLOAT, 0, 0, 0); + rf_gl.EnableVertexAttribArray(5); + } + else + { + // Default texcoord2 vertex attribute + rf_gl.VertexAttrib2f(5, 0.0f, 0.0f); + rf_gl.DisableVertexAttribArray(5); + } + + if (mesh->indices != NULL) + { + rf_gl.GenBuffers(1, &mesh->vbo_id[6]); + rf_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->vbo_id[6]); + rf_gl.BufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*mesh->triangle_count * 3, mesh->indices, draw_hint); + } + + if (mesh->vao_id > 0) RF_LOG(RF_LOG_TYPE_INFO, "[VAO ID %i] rf_mesh uploaded successfully to VRAM (GPU)", mesh->vao_id); + else RF_LOG(RF_LOG_TYPE_WARNING, "rf_mesh could not be uploaded to VRAM (GPU)"); +} + +// Update vertex or index data on GPU (upload new data to one buffer) +RF_API void rf_gfx_update_mesh(rf_mesh mesh, int buffer, int num) +{ + rf_gfx_update_mesh_at(mesh, buffer, num, 0); +} + +// Update vertex or index data on GPU, at index +// WARNING: error checking is in place that will cause the data to not be +// updated if offset + size exceeds what the buffer can hold +RF_API void rf_gfx_update_mesh_at(rf_mesh mesh, int buffer, int num, int index) +{ + // Activate mesh VAO + rf_gl.BindVertexArray(mesh.vao_id); + + switch (buffer) + { + case 0: // Update vertices (vertex position) + { + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh.vbo_id[0]); + if (index == 0 && num >= mesh.vertex_count) rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 3*num, mesh.vertices, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertex_count) break; + else rf_gl.BufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 3*index, sizeof(float) * 3*num, mesh.vertices); + + } break; + case 1: // Update texcoords (vertex texture coordinates) + { + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh.vbo_id[1]); + if (index == 0 && num >= mesh.vertex_count) rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 2*num, mesh.texcoords, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertex_count) break; + else rf_gl.BufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 2*index, sizeof(float) * 2*num, mesh.texcoords); + + } break; + case 2: // Update normals (vertex normals) + { + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh.vbo_id[2]); + if (index == 0 && num >= mesh.vertex_count) rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 3*num, mesh.normals, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertex_count) break; + else rf_gl.BufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 3*index, sizeof(float) * 3*num, mesh.normals); + + } break; + case 3: // Update colors (vertex colors) + { + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh.vbo_id[3]); + if (index == 0 && num >= mesh.vertex_count) rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 4 * num, mesh.colors, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertex_count) break; + else rf_gl.BufferSubData(GL_ARRAY_BUFFER, sizeof(unsigned char) * 4 * index, sizeof(unsigned char) * 4 * num, mesh.colors); + + } break; + case 4: // Update tangents (vertex tangents) + { + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh.vbo_id[4]); + if (index == 0 && num >= mesh.vertex_count) rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 4 * num, mesh.tangents, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertex_count) break; + else rf_gl.BufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 4 * index, sizeof(float) * 4 * num, mesh.tangents); + } break; + case 5: // Update texcoords2 (vertex second texture coordinates) + { + rf_gl.BindBuffer(GL_ARRAY_BUFFER, mesh.vbo_id[5]); + if (index == 0 && num >= mesh.vertex_count) rf_gl.BufferData(GL_ARRAY_BUFFER, sizeof(float) * 2*num, mesh.texcoords2, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.vertex_count) break; + else rf_gl.BufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 2*index, sizeof(float) * 2*num, mesh.texcoords2); + } break; + case 6: // Update indices (triangle index buffer) + { + // the * 3 is because each triangle has 3 indices + unsigned short *indices = mesh.indices; + rf_gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.vbo_id[6]); + if (index == 0 && num >= mesh.triangle_count) + rf_gl.BufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(*indices)*num * 3, indices, GL_DYNAMIC_DRAW); + else if (index + num >= mesh.triangle_count) + break; + else + rf_gl.BufferSubData(GL_ELEMENT_ARRAY_BUFFER, sizeof(*indices)*index * 3, sizeof(*indices)*num * 3, indices); + } break; + default: break; + } + + // Unbind the current VAO + rf_gl.BindVertexArray(0); + + // Another option would be using buffer mapping... + //mesh.vertices = glMap_buffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // Now we can modify vertices + //glUnmap_buffer(GL_ARRAY_BUFFER); +} + +// Draw a 3d mesh with material and transform +RF_API void rf_gfx_draw_mesh(rf_mesh mesh, rf_material material, rf_mat transform) +{ + // Bind shader program + rf_gl.UseProgram(material.shader.id); + + // Matrices and other values required by shader + //----------------------------------------------------- + // Calculate and send to shader model matrix (used by PBR shader) + if (material.shader.locs[RF_LOC_MATRIX_MODEL] != -1) + rf_gfx_set_shader_value_matrix(material.shader, material.shader.locs[RF_LOC_MATRIX_MODEL], transform); + + // Upload to shader material.col_diffuse + if (material.shader.locs[RF_LOC_COLOR_DIFFUSE] != -1) + rf_gl.Uniform4f(material.shader.locs[RF_LOC_COLOR_DIFFUSE], (float)material.maps[RF_MAP_DIFFUSE].color.r / 255.0f, + (float)material.maps[RF_MAP_DIFFUSE].color.g / 255.0f, + (float)material.maps[RF_MAP_DIFFUSE].color.b / 255.0f, + (float)material.maps[RF_MAP_DIFFUSE].color.a / 255.0f); + + // Upload to shader material.colSpecular (if available) + if (material.shader.locs[RF_LOC_COLOR_SPECULAR] != -1) + rf_gl.Uniform4f(material.shader.locs[RF_LOC_COLOR_SPECULAR], (float)material.maps[RF_MAP_SPECULAR].color.r / 255.0f, + (float)material.maps[RF_MAP_SPECULAR].color.g / 255.0f, + (float)material.maps[RF_MAP_SPECULAR].color.b / 255.0f, + (float)material.maps[RF_MAP_SPECULAR].color.a / 255.0f); + + if (material.shader.locs[RF_LOC_MATRIX_VIEW] != -1) + rf_gfx_set_shader_value_matrix(material.shader, material.shader.locs[RF_LOC_MATRIX_VIEW], + rf_ctx.modelview); + if (material.shader.locs[RF_LOC_MATRIX_PROJECTION] != -1) + rf_gfx_set_shader_value_matrix(material.shader, material.shader.locs[RF_LOC_MATRIX_PROJECTION], + rf_ctx.projection); + + // At this point the rf_ctx->gl_ctx.modelview matrix just contains the view matrix (camera) + // That's because rf_begin_mode3d() sets it an no model-drawing function modifies it, all use rf_gfx_push_matrix() and rf_gfx_pop_matrix() + rf_mat mat_view = rf_ctx.modelview; // View matrix (camera) + rf_mat mat_projection = rf_ctx.projection; // Projection matrix (perspective) + + // TODO: Consider possible transform matrices in the rf_ctx->gl_ctx.stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //rf_mat matStackTransform = rf_mat_identity(); + //for (rf_int i = rf_ctx->gl_ctx.stack_counter; i > 0; i--) matStackTransform = rf_mat_mul(rf_ctx->gl_ctx.stack[i], matStackTransform); + + // rf_transform to camera-space coordinates + rf_mat mat_model_view = rf_mat_mul(transform, rf_mat_mul(rf_ctx.transform, mat_view)); + //----------------------------------------------------- + + // Bind active texture maps (if available) + for (rf_int i = 0; i < RF_MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + rf_gl.ActiveTexture(GL_TEXTURE0 + i); + if ((i == RF_MAP_IRRADIANCE) || (i == RF_MAP_PREFILTER) || (i == RF_MAP_CUBEMAP)) rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, material.maps[i].texture.id); + else rf_gl.BindTexture(GL_TEXTURE_2D, material.maps[i].texture.id); + + rf_gl.Uniform1i(material.shader.locs[RF_LOC_MAP_DIFFUSE + i], i); + } + } + + // Bind vertex array objects (or VBOs) + rf_gl.BindVertexArray(mesh.vao_id); + + rf_ctx.modelview = mat_model_view; + + // Calculate model-view-rf_ctx->gl_ctx.projection matrix (MVP) + rf_mat mat_mvp = rf_mat_mul(rf_ctx.modelview, rf_ctx.projection); // rf_transform to screen-space coordinates + + // Send combined model-view-rf_ctx->gl_ctx.projection matrix to shader + rf_gl.UniformMatrix4fv(material.shader.locs[RF_LOC_MATRIX_MVP], 1, false, rf_mat_to_float16(mat_mvp).v); + + // Draw call! + if (mesh.indices != NULL) rf_gl.DrawElements(GL_TRIANGLES, mesh.triangle_count * 3, GL_UNSIGNED_SHORT, 0); // Indexed vertices draw + else rf_gl.DrawArrays(GL_TRIANGLES, 0, mesh.vertex_count); + + // Unbind all binded texture maps + for (rf_int i = 0; i < RF_MAX_MATERIAL_MAPS; i++) + { + rf_gl.ActiveTexture(GL_TEXTURE0 + i); // Set shader active texture + if ((i == RF_MAP_IRRADIANCE) || (i == RF_MAP_PREFILTER) || (i == RF_MAP_CUBEMAP)) rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, 0); + else rf_gl.BindTexture(GL_TEXTURE_2D, 0); // Unbind current active texture + } + + // Unind vertex array objects (or VBOs) + rf_gl.BindVertexArray(0); + + // Unbind shader program + rf_gl.UseProgram(0); + + // Restore rf_ctx->gl_ctx.projection/rf_ctx->gl_ctx.modelview matrices + // NOTE: In stereo rendering matrices are being modified to fit every eye + rf_ctx.projection = mat_projection; + rf_ctx.modelview = mat_view; +} + +// Unload mesh data from the GPU +RF_API void rf_gfx_unload_mesh(rf_mesh mesh) +{ + rf_gfx_delete_buffers(mesh.vbo_id[0]); // vertex + rf_gfx_delete_buffers(mesh.vbo_id[1]); // texcoords + rf_gfx_delete_buffers(mesh.vbo_id[2]); // normals + rf_gfx_delete_buffers(mesh.vbo_id[3]); // colors + rf_gfx_delete_buffers(mesh.vbo_id[4]); // tangents + rf_gfx_delete_buffers(mesh.vbo_id[5]); // texcoords2 + rf_gfx_delete_buffers(mesh.vbo_id[6]); // indices + + rf_gfx_delete_vertex_arrays(mesh.vao_id); +} + +#pragma endregion + +#pragma region gen textures +// Generate cubemap texture from HDR texture +RF_API rf_texture2d rf_gen_texture_cubemap(rf_shader shader, rf_texture2d sky_hdr, rf_int size) +{ + rf_texture2d cubemap = { 0 }; + // NOTE: rf_set_shader_default_locations() already setups locations for rf_ctx->gl_ctx.projection and view rf_mat in shader + // Other locations should be setup externally in shader before calling the function + + // Set up depth face culling and cubemap seamless + rf_gl.Disable(GL_CULL_FACE); + rf_gl.Enable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Flag not supported on OpenGL ES 2.0 + + // Setup framebuffer + unsigned int fbo, rbo; + rf_gl.GenFramebuffers(1, &fbo); + rf_gl.GenRenderbuffers(1, &rbo); + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); + rf_gl.BindRenderbuffer(GL_RENDERBUFFER, rbo); + rf_gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); + rf_gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); + + // Set up cubemap to render and attach to framebuffer + // NOTE: Faces are stored as 32 bit floating point values + rf_gl.GenTextures(1, &cubemap.id); + rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); + for (unsigned int i = 0; i < 6; i++) + { + rf_gl.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB32F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + } + + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Create rf_ctx->gl_ctx.projection and different views for each face + rf_mat fbo_projection = rf_mat_perspective(90.0 * RF_DEG2RAD, 1.0, 0.01, 1000.0); + rf_mat fbo_views[6] = { + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {1.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {-1.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 1.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, 1.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, -1.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, 1.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, -1.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}) + }; + + // Convert HDR equirectangular environment map to cubemap equivalent + rf_gl.UseProgram(shader.id); + rf_gl.ActiveTexture(GL_TEXTURE0); + rf_gl.BindTexture(GL_TEXTURE_2D, sky_hdr.id); + rf_gfx_set_shader_value_matrix(shader, shader.locs[RF_LOC_MATRIX_PROJECTION], fbo_projection); + + // Note: don't forget to configure the viewport to the capture dimensions + rf_gl.Viewport(0, 0, size, size); + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); + + for (rf_int i = 0; i < 6; i++) + { + rf_gfx_set_shader_value_matrix(shader, shader.locs[RF_LOC_MATRIX_VIEW], fbo_views[i]); + rf_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap.id, 0); + rf_gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + rf_gen_draw_cube(); + } + + // Unbind framebuffer and textures + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); + + // Reset viewport dimensions to default + rf_gl.Viewport(0, 0, rf_ctx.framebuffer_width, rf_ctx.framebuffer_height); + //glEnable(GL_CULL_FACE); + + // NOTE: rf_texture2d is a GL_TEXTURE_CUBE_MAP, not a GL_TEXTURE_2D! + cubemap.width = size; + cubemap.height = size; + cubemap.mipmaps = 1; + cubemap.format = RF_UNCOMPRESSED_R32G32B32; + + return cubemap; +} + +// Generate irradiance texture using cubemap data +RF_API rf_texture2d rf_gen_texture_irradiance(rf_shader shader, rf_texture2d cubemap, rf_int size) +{ + rf_texture2d irradiance = { 0 }; + + // NOTE: rf_set_shader_default_locations() already setups locations for rf_ctx->gl_ctx.projection and view rf_mat in shader + // Other locations should be setup externally in shader before calling the function + + // Setup framebuffer + unsigned int fbo, rbo; + rf_gl.GenFramebuffers(1, &fbo); + rf_gl.GenRenderbuffers(1, &rbo); + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); + rf_gl.BindRenderbuffer(GL_RENDERBUFFER, rbo); + rf_gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); + rf_gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); + + // Create an irradiance cubemap, and re-scale capture FBO to irradiance scale + rf_gl.GenTextures(1, &irradiance.id); + rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, irradiance.id); + for (unsigned int i = 0; i < 6; i++) + { + rf_gl.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + } + + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Create rf_ctx->gl_ctx.projection (transposed) and different views for each face + rf_mat fbo_projection = rf_mat_perspective(90.0 * RF_DEG2RAD, 1.0, 0.01, 1000.0); + rf_mat fbo_views[6] = { + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {1.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {-1.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 1.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, 1.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, -1.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, 1.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, -1.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}) + }; + + // Solve diffuse integral by convolution to create an irradiance cubemap + rf_gl.UseProgram(shader.id); + rf_gl.ActiveTexture(GL_TEXTURE0); + rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); + rf_gfx_set_shader_value_matrix(shader, shader.locs[RF_LOC_MATRIX_PROJECTION], fbo_projection); + + // Note: don't forget to configure the viewport to the capture dimensions + rf_gl.Viewport(0, 0, size, size); + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); + + for (rf_int i = 0; i < 6; i++) + { + rf_gfx_set_shader_value_matrix(shader, shader.locs[RF_LOC_MATRIX_VIEW], fbo_views[i]); + rf_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradiance.id, 0); + rf_gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + rf_gen_draw_cube(); + } + + // Unbind framebuffer and textures + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); + + // Reset viewport dimensions to default + rf_gl.Viewport(0, 0, rf_ctx.framebuffer_width, rf_ctx.framebuffer_height); + + irradiance.width = size; + irradiance.height = size; + irradiance.mipmaps = 1; + //irradiance.format = UNCOMPRESSED_R16G16B16; + + return irradiance; +} + +// Generate prefilter texture using cubemap data +RF_API rf_texture2d rf_gen_texture_prefilter(rf_shader shader, rf_texture2d cubemap, rf_int size) +{ + rf_texture2d prefilter = { 0 }; + + // NOTE: rf_set_shader_default_locations() already setups locations for projection and view rf_mat in shader + // Other locations should be setup externally in shader before calling the function + // TODO: Locations should be taken out of this function... too shader dependant... + int roughness_loc = rf_gfx_get_shader_location(shader, "roughness"); + + // Setup framebuffer + unsigned int fbo, rbo; + rf_gl.GenFramebuffers(1, &fbo); + rf_gl.GenRenderbuffers(1, &rbo); + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); + rf_gl.BindRenderbuffer(GL_RENDERBUFFER, rbo); + rf_gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); + rf_gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); + + // Create a prefiltered HDR environment map + rf_gl.GenTextures(1, &prefilter.id); + rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, prefilter.id); + for (unsigned int i = 0; i < 6; i++) + { + rf_gl.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + } + + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Generate mipmaps for the prefiltered HDR texture + rf_gl.GenerateMipmap(GL_TEXTURE_CUBE_MAP); + + // Create rf_ctx->gl_ctx.projection (transposed) and different views for each face + rf_mat fbo_projection = rf_mat_perspective(90.0 * RF_DEG2RAD, 1.0, 0.01, 1000.0); + rf_mat fbo_views[6] = { + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {1.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {-1.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 1.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, 1.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, -1.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, 1.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}), + rf_mat_look_at((rf_vec3) {0.0f, 0.0f, 0.0f}, (rf_vec3) {0.0f, 0.0f, -1.0f}, (rf_vec3) {0.0f, -1.0f, 0.0f}) + }; + + // Prefilter HDR and store data into mipmap levels + rf_gl.UseProgram(shader.id); + rf_gl.ActiveTexture(GL_TEXTURE0); + rf_gl.BindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); + rf_gfx_set_shader_value_matrix(shader, shader.locs[RF_LOC_MATRIX_PROJECTION], fbo_projection); + + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); + +#define MAX_MIPMAP_LEVELS 5 // Max number of prefilter texture mipmaps + + for (rf_int mip = 0; mip < MAX_MIPMAP_LEVELS; mip++) + { + // Resize framebuffer according to mip-level size. + unsigned int mip_width = size*(int)powf(0.5f, (float)mip); + unsigned int mip_height = size*(int)powf(0.5f, (float)mip); + + rf_gl.BindRenderbuffer(GL_RENDERBUFFER, rbo); + rf_gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mip_width, mip_height); + rf_gl.Viewport(0, 0, mip_width, mip_height); + + float roughness = (float)mip/(float)(MAX_MIPMAP_LEVELS - 1); + rf_gl.Uniform1f(roughness_loc, roughness); + + for (rf_int i = 0; i < 6; i++) + { + rf_gfx_set_shader_value_matrix(shader, shader.locs[RF_LOC_MATRIX_VIEW], fbo_views[i]); + rf_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilter.id, mip); + rf_gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + rf_gen_draw_cube(); + } + } + + // Unbind framebuffer and textures + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); + + // Reset viewport dimensions to default + rf_gl.Viewport(0, 0, rf_ctx.framebuffer_width, rf_ctx.framebuffer_height); + + prefilter.width = size; + prefilter.height = size; + //prefilter.mipmaps = 1 + (int)floor(log(size)/log(2)); + //prefilter.format = UNCOMPRESSED_R16G16B16; + + return prefilter; +} + +// Generate BRDF texture using cubemap data. Todo: Review implementation: https://github.com/HectorMF/BRDFGenerator +RF_API rf_texture2d rf_gen_texture_brdf(rf_shader shader, rf_int size) +{ + rf_texture2d brdf = { 0 }; + // Generate BRDF convolution texture + rf_gl.GenTextures(1, &brdf.id); + rf_gl.BindTexture(GL_TEXTURE_2D, brdf.id); + rf_gl.TexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, size, size, 0, GL_RGB, GL_FLOAT, NULL); + + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + rf_gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Render BRDF LUT into a quad using FBO + unsigned int fbo, rbo; + rf_gl.GenFramebuffers(1, &fbo); + rf_gl.GenRenderbuffers(1, &rbo); + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); + rf_gl.BindRenderbuffer(GL_RENDERBUFFER, rbo); + rf_gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); + rf_gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdf.id, 0); + + rf_gl.Viewport(0, 0, size, size); + rf_gl.UseProgram(shader.id); + rf_gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + rf_gen_draw_quad(); + + // Unbind framebuffer and textures + rf_gl.BindFramebuffer(GL_FRAMEBUFFER, 0); + + // Unload framebuffer but keep color texture + rf_gl.DeleteRenderbuffers(1, &rbo); + rf_gl.DeleteFramebuffers(1, &fbo); + + // Reset viewport dimensions to default + rf_gl.Viewport(0, 0, rf_ctx.framebuffer_width, rf_ctx.framebuffer_height); + + brdf.width = size; + brdf.height = size; + brdf.mipmaps = 1; + brdf.format = RF_UNCOMPRESSED_R32G32B32; + + return brdf; +} + +#pragma endregion + +#endif // defined(RAYFORK_GRAPHICS_BACKEND_GL_33) || defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) +/*** End of inlined file: rayfork-gfx-backend-opengl.c ***/ + +//#include "rayfork_audio_backend_miniaudio.c" diff --git a/src/lib/rayfork.h b/src/lib/rayfork.h new file mode 100644 index 0000000..a8b9f88 --- /dev/null +++ b/src/lib/rayfork.h @@ -0,0 +1,2662 @@ +#ifndef RAYFORK_H +#define RAYFORK_H + + +/*** Start of inlined file: rayfork-std.h ***/ +#ifndef RAYFORK_STD_H +#define RAYFORK_STD_H + +#pragma region common macros + +// Libc includes +#include "stdarg.h" +#include "stdlib.h" +#include "string.h" +#include "stdint.h" +#include "stddef.h" +#include "stdio.h" +#include "float.h" +#include "math.h" + +#if defined(_MSC_VER) + #define RAYFORK_MSVC +#elif defined(__clang__) + #define RAYFORK_CLANG +#elif defined(__GNUC__) + #define RAYFORK_GNUC +#endif + +#if !defined(RAYFORK_MSVC) + #include "stdbool.h" +#elif !defined(__cplusplus) + #if _MSC_VER >= 1800 + #include + #else // stdbool.h missing prior to MSVC++ 12.0 (VS2013) + #define bool char + #define true 1 + #define false 0 + #endif +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +#define RAYFORK_PLATFORM_WINDOWS +#endif + +#if defined(__linux__) && !defined(__ANDROID__) +#define RAYFORK_PLATFORM_LINUX +#endif + +#if defined(__linux__) && defined(__ANDROID__) +#define RAYFORK_PLATFORM_ANDROID +#endif + +#if defined(__APPLE__) && !TARGET_OS_IPHONE +#define RAYFORK_PLATFORM_MACOS +#endif + +#if defined(__APPLE__) && TARGET_OS_IPHONE +#define RAYFORK_PLATFORM_IOS +#endif + +#if (defined(RAYFORK_PLATFORM_WINDOWS) + defined(RAYFORK_PLATFORM_LINUX) + defined(RAYFORK_PLATFORM_MACOS) + defined(RAYFORK_PLATFORM_ANDROID) + defined(RAYFORK_PLATFORM_IOS)) != 1 +#error rayfork: none or more than one platforms defined. +#endif + +#ifndef RF_EXTERN + #ifdef __cplusplus + #define RF_EXTERN extern "C" + #else + #define RF_EXTERN extern + #endif +#endif + +#if defined(RAYFORK_MSVC) + #define RF_DLL_IMPORT __declspec(dllimport) + #define RF_DLL_EXPORT __declspec(dllexport) + #define RF_DLL_PRIVATE static +#else + #if defined(RAYFORK_GNUC) || defined(RAYFORK_CLANG) + #define RF_DLL_IMPORT __attribute__((visibility("default"))) + #define RF_DLL_EXPORT __attribute__((visibility("default"))) + #define RF_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define RF_DLL_IMPORT + #define RF_DLL_EXPORT + #define RF_DLL_PRIVATE static + #endif +#endif + +#ifndef RF_API + #ifdef RAYFORK_DLL + #ifdef RAYFORK__IMPLEMENTATION_FLAG + #define RF_API RF_EXTERN RF_DLL_EXPORT + #else + #define RF_API RF_EXTERN RF_DLL_IMPORT + #endif + #else + #define RF_API RF_EXTERN + #endif +#endif + +#ifndef RF_INTERNAL + #ifdef RAYFORK_DLL + #define RF_INTERNAL RF_DLL_PRIVATE + #else + #define RF_INTERNAL static + #endif +#endif + +// Used to make constant literals work even in C++ mode +#ifdef __cplusplus + #define RF_LIT(type) type +#else + #define RF_LIT(type) (type) +#endif + +#ifndef RF_THREAD_LOCAL + #if __cplusplus >= 201103L + #define RF_THREAD_LOCAL thread_local + #elif __STDC_VERSION_ >= 201112L + #define RF_THREAD_LOCAL _Thread_local + #elif defined(RAYFORK_GNUC) || defined(RAYFORK_CLANG) + #define RF_THREAD_LOCAL __thread + #elif defined(RAYFORK_MSVC) + #define RF_THREAD_LOCAL __declspec(thread) + #endif +#endif + +#define RF_CONCAT_IMPL(a, b) a##b +#define RF_CONCAT(a, b) RF_CONCAT_IMPL(a, b) + +typedef ptrdiff_t rf_int; + +typedef struct rf_source_location +{ + const char* file_name; + const char* proc_name; + rf_int line_in_file; +} rf_source_location; + +#define RF_SOURCE_LOCATION (RF_LIT(rf_source_location) { __FILE__, __FUNCTION__, __LINE__ }) + +#pragma endregion + +#pragma region allocator + +#define RF_DEFAULT_ALLOCATOR (RF_LIT(rf_allocator) { NULL, rf_libc_allocator_wrapper }) +#define RF_NULL_ALLOCATOR (RF_LIT(rf_allocator) {0}) + +#define RF_CALLOC(allocator, size) rf_calloc_wrapper((allocator), 1, size) + +#define RF_ALLOC(allocator, size) ((allocator).allocator_proc(&(allocator), RF_SOURCE_LOCATION, RF_AM_ALLOC, (RF_LIT(rf_allocator_args) { 0, (size), 0 }))) +#define RF_FREE(allocator, ptr) ((allocator).allocator_proc(&(allocator), RF_SOURCE_LOCATION, RF_AM_FREE, (RF_LIT(rf_allocator_args) { (ptr), 0, 0 }))) +#define RF_REALLOC(allocator, ptr, new_size, old_size) ((allocator).allocator_proc(&(allocator), RF_SOURCE_LOCATION, RF_AM_REALLOC, (RF_LIT(rf_allocator_args) { (ptr), (new_size), (old_size) }))) + +typedef enum rf_allocator_mode +{ + RF_AM_UNKNOWN = 0, + RF_AM_ALLOC, + RF_AM_REALLOC, + RF_AM_FREE, +} rf_allocator_mode; + +typedef struct rf_allocator_args +{ + /* + * In case of RF_AM_ALLOC this argument can be ignored. + * In case of RF_AM_REALLOC this argument is the pointer to the buffer that must be reallocated. + * In case of RF_AM_FREE this argument is the pointer that needs to be freed. + */ + void* pointer_to_free_or_realloc; + + /* + * In case of RF_AM_ALLOC this is the new size that needs to be allocated. + * In case of RF_AM_REALLOC this is the new size that the buffer should have. + * In case of RF_AM_FREE this argument can be ignored. + */ + rf_int size_to_allocate_or_reallocate; + + /* + * In case of RF_AM_ALLOC this argument can be ignored. + * In case of RF_AM_REALLOC this is the old size of the buffer. + * In case of RF_AM_FREE this argument can be ignored. + */ + int old_size; +} rf_allocator_args; + +struct rf_allocator; + +typedef void* (rf_allocator_proc)(struct rf_allocator* this_allocator, rf_source_location source_location, rf_allocator_mode mode, rf_allocator_args args); + +typedef struct rf_allocator +{ + void* user_data; + rf_allocator_proc* allocator_proc; +} rf_allocator; + +RF_API void* rf_calloc_wrapper(rf_allocator allocator, rf_int amount, rf_int size); + +RF_API void* rf_libc_allocator_wrapper(struct rf_allocator* this_allocator, rf_source_location source_location, rf_allocator_mode mode, rf_allocator_args args); + +#pragma endregion + +#pragma region io + +#define RF_NULL_IO (RF_LIT(rf_io_callbacks) {0}) +#define RF_FILE_SIZE(io, filename) ((io).file_size_proc((io).user_data, filename)) +#define RF_READ_FILE(io, filename, dst, dst_size) ((io).read_file_proc((io).user_data, filename, dst, dst_size)) +#define RF_DEFAULT_IO (RF_LIT(rf_io_callbacks) { NULL, rf_libc_get_file_size, rf_libc_load_file_into_buffer }) + +typedef struct rf_io_callbacks +{ + void* user_data; + rf_int (*file_size_proc) (void* user_data, const char* filename); + bool (*read_file_proc) (void* user_data, const char* filename, void* dst, rf_int dst_size); // Returns true if operation was successful +} rf_io_callbacks; + +RF_API rf_int rf_libc_get_file_size(void* user_data, const char* filename); +RF_API bool rf_libc_load_file_into_buffer(void* user_data, const char* filename, void* dst, rf_int dst_size); + +#pragma endregion + +#pragma region error + +typedef enum rf_error_type +{ + RF_NO_ERROR, + RF_BAD_ARGUMENT, + RF_BAD_ALLOC, + RF_BAD_IO, + RF_BAD_BUFFER_SIZE, + RF_BAD_FORMAT, + RF_LIMIT_REACHED, + RF_STBI_FAILED, + RF_STBTT_FAILED, + RF_UNSUPPORTED, +} rf_error_type; + +typedef struct rf_recorded_error +{ + rf_source_location reported_source_location; + rf_error_type error_type; +} rf_recorded_error; + +RF_API rf_recorded_error rf_get_last_recorded_error(); + +#pragma endregion + +#pragma region logger + +#define RF_DEFAULT_LOGGER (RF_LIT(rf_logger) { NULL, rf_libc_printf_logger }) +#define RF_NULL_LOGGER (RF_LIT(rf_logger) { NULL, NULL }) + +typedef enum rf_log_type +{ + RF_LOG_TYPE_NONE = 0, + RF_LOG_TYPE_DEBUG = 0x1, // Useful mostly to rayfork devs + RF_LOG_TYPE_INFO = 0x2, // Information + RF_LOG_TYPE_WARNING = 0x4, // Warnings about things to be careful about + RF_LOG_TYPE_ERROR = 0x8, // Errors that prevented functions from doing everything they advertised + RF_LOG_TYPE_ALL = RF_LOG_TYPE_DEBUG | RF_LOG_TYPE_INFO | RF_LOG_TYPE_WARNING | RF_LOG_TYPE_ERROR, +} rf_log_type; + +struct rf_logger; + +typedef void (*rf_log_proc)(struct rf_logger* logger, rf_source_location source_location, rf_log_type log_type, const char* msg, rf_error_type error_type, va_list args); + +typedef struct rf_logger +{ + void* user_data; + rf_log_proc log_proc; +} rf_logger; + +RF_API const char* rf_log_type_string(rf_log_type); + +RF_API void rf_set_logger(rf_logger logger); +RF_API void rf_set_logger_filter(rf_log_type); + +RF_API void rf_libc_printf_logger(struct rf_logger* logger, rf_source_location source_location, rf_log_type log_type, const char* msg, rf_error_type error_type, va_list args); + +#pragma endregion + +#pragma region rng + +#define RF_DEFAULT_RAND_PROC (rf_libc_rand_wrapper) + +typedef rf_int (*rf_rand_proc)(rf_int min, rf_int max); + +RF_API rf_int rf_libc_rand_wrapper(rf_int min, rf_int max); + +#pragma endregion + +#endif +/*** End of inlined file: rayfork-std.h ***/ + + +/*** Start of inlined file: rayfork-unicode.h ***/ +#ifndef RAYFORK_UNICODE_H +#define RAYFORK_UNICODE_H + +#define RF_INVALID_CODEPOINT ('?') + +typedef uint32_t rf_rune; + +typedef struct rf_decoded_utf8_stats +{ + rf_int bytes_processed; + rf_int invalid_bytes; + rf_int valid_rune_count; + rf_int total_rune_count; +} rf_decoded_utf8_stats; + +typedef struct rf_decoded_rune +{ + rf_rune codepoint; + rf_int bytes_processed; + bool valid; +} rf_decoded_rune; + +typedef struct rf_decoded_string +{ + rf_rune* codepoints; + rf_int size; + rf_int invalid_bytes_count; + bool valid; +} rf_decoded_string; + +RF_API rf_decoded_rune rf_decode_utf8_char(const char* text, rf_int len); +RF_API rf_decoded_utf8_stats rf_count_utf8_chars(const char* text, rf_int len); +RF_API rf_decoded_string rf_decode_utf8_to_buffer(const char* text, rf_int len, rf_rune* dst, rf_int dst_size); +RF_API rf_decoded_string rf_decode_utf8(const char* text, rf_int len, rf_allocator allocator); + +#endif // RAYFORK_UNICODE_H +/*** End of inlined file: rayfork-unicode.h ***/ + + +/*** Start of inlined file: rayfork-color.h ***/ +#ifndef RAYFORK_COLOR_H +#define RAYFORK_COLOR_H + + +/*** Start of inlined file: rayfork-math.h ***/ +#ifndef RAYFORK_MATH_H +#define RAYFORK_MATH_H + +#define RF_PI (3.14159265358979323846f) +#define RF_DEG2RAD (RF_PI / 180.0f) +#define RF_RAD2DEG (180.0f / RF_PI) + +typedef struct rf_sizei +{ + int width; + int height; +} rf_sizei; + +typedef struct rf_sizef +{ + float width; + float height; +} rf_sizef; + +typedef struct rf_vec2 +{ + float x; + float y; +} rf_vec2; + +typedef struct rf_vec3 +{ + float x; + float y; + float z; +} rf_vec3; + +typedef struct rf_vec4 +{ + float x; + float y; + float z; + float w; +} rf_vec4, rf_quaternion; + +// The matrix is OpenGL style 4x4 - right handed, column major +typedef struct rf_mat +{ + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; +} rf_mat; + +typedef struct rf_float16 +{ + float v[16]; +} rf_float16; + +typedef struct rf_rec +{ + float x; + float y; + float width; + float height; +} rf_rec; + +typedef struct rf_ray +{ + rf_vec3 position; // position (origin) + rf_vec3 direction; // direction +} rf_ray; + +typedef struct rf_ray_hit_info +{ + bool hit; // Did the ray hit something? + float distance; // Distance to nearest hit + rf_vec3 position; // Position of nearest hit + rf_vec3 normal; // Surface normal of hit +} rf_ray_hit_info; + +typedef struct rf_bounding_box +{ + rf_vec3 min; // Minimum vertex box-corner + rf_vec3 max; // Maximum vertex box-corner +} rf_bounding_box; + +#pragma region misc +RF_API float rf_next_pot(float it); +RF_API rf_vec2 rf_center_to_screen(float w, float h); // Returns the position of an object such that it will be centered to the screen +RF_API rf_vec2 rf_center_to_object(rf_sizef center_this, rf_rec to_this); // Returns the position of an object such that it will be centered to a rectangle +RF_API float rf_clamp(float value, float min, float max); // Clamp float value +RF_API float rf_lerp(float start, float end, float amount); // Calculate linear interpolation between two floats +#pragma endregion + +#pragma region vec and matrix math + +RF_API rf_vec2 rf_vec2_add(rf_vec2 v1, rf_vec2 v2); // Add two vectors (v1 + v2) +RF_API rf_vec2 rf_vec2_sub(rf_vec2 v1, rf_vec2 v2); // Subtract two vectors (v1 - v2) +RF_API float rf_vec2_len(rf_vec2 v); // Calculate vector length +RF_API float rf_vec2_dot_product(rf_vec2 v1, rf_vec2 v2); // Calculate two vectors dot product +RF_API float rf_vec2_distance(rf_vec2 v1, rf_vec2 v2); // Calculate distance between two vectors +RF_API float rf_vec2_angle(rf_vec2 v1, rf_vec2 v2); // Calculate angle from two vectors in X-axis +RF_API rf_vec2 rf_vec2_scale(rf_vec2 v, float scale); // Scale vector (multiply by value) +RF_API rf_vec2 rf_vec2_mul_v(rf_vec2 v1, rf_vec2 v2); // Multiply vector by vector +RF_API rf_vec2 rf_vec2_negate(rf_vec2 v); // Negate vector +RF_API rf_vec2 rf_vec2_div(rf_vec2 v, float div); // Divide vector by a float value +RF_API rf_vec2 rf_vec2_div_v(rf_vec2 v1, rf_vec2 v2); // Divide vector by vector +RF_API rf_vec2 rf_vec2_normalize(rf_vec2 v); // Normalize provided vector +RF_API rf_vec2 rf_vec2_lerp(rf_vec2 v1, rf_vec2 v2, float amount); // Calculate linear interpolation between two vectors + +RF_API rf_vec3 rf_vec3_add(rf_vec3 v1, rf_vec3 v2); // Add two vectors +RF_API rf_vec3 rf_vec3_sub(rf_vec3 v1, rf_vec3 v2); // Subtract two vectors +RF_API rf_vec3 rf_vec3_mul(rf_vec3 v, float scalar); // Multiply vector by scalar +RF_API rf_vec3 rf_vec3_mul_v(rf_vec3 v1, rf_vec3 v2); // Multiply vector by vector +RF_API rf_vec3 rf_vec3_cross_product(rf_vec3 v1, rf_vec3 v2); // Calculate two vectors cross product +RF_API rf_vec3 rf_vec3_perpendicular(rf_vec3 v); // Calculate one vector perpendicular vector +RF_API float rf_vec3_len(rf_vec3 v); // Calculate vector length +RF_API float rf_vec3_dot_product(rf_vec3 v1, rf_vec3 v2); // Calculate two vectors dot product +RF_API float rf_vec3_distance(rf_vec3 v1, rf_vec3 v2); // Calculate distance between two vectors +RF_API rf_vec3 rf_vec3_scale(rf_vec3 v, float scale); // Scale provided vector +RF_API rf_vec3 rf_vec3_negate(rf_vec3 v); // Negate provided vector (invert direction) +RF_API rf_vec3 rf_vec3_div(rf_vec3 v, float div); // Divide vector by a float value +RF_API rf_vec3 rf_vec3_div_v(rf_vec3 v1, rf_vec3 v2); // Divide vector by vector +RF_API rf_vec3 rf_vec3_normalize(rf_vec3 v); // Normalize provided vector +RF_API void rf_vec3_ortho_normalize(rf_vec3* v1, rf_vec3* v2); // Orthonormalize provided vectors. Makes vectors normalized and orthogonal to each other. Gram-Schmidt function implementation +RF_API rf_vec3 rf_vec3_transform(rf_vec3 v, rf_mat mat); // Transforms a rf_vec3 by a given rf_mat +RF_API rf_vec3 rf_vec3_rotate_by_quaternion(rf_vec3 v, rf_quaternion q); // rf_transform a vector by quaternion rotation +RF_API rf_vec3 rf_vec3_lerp(rf_vec3 v1, rf_vec3 v2, float amount); // Calculate linear interpolation between two vectors +RF_API rf_vec3 rf_vec3_reflect(rf_vec3 v, rf_vec3 normal); // Calculate reflected vector to normal +RF_API rf_vec3 rf_vec3_min(rf_vec3 v1, rf_vec3 v2); // Return min value for each pair of components +RF_API rf_vec3 rf_vec3_max(rf_vec3 v1, rf_vec3 v2); // Return max value for each pair of components +RF_API rf_vec3 rf_vec3_barycenter(rf_vec3 p, rf_vec3 a, rf_vec3 b, rf_vec3 c); // Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) NOTE: Assumes P is on the plane of the triangle + +RF_API float rf_mat_determinant(rf_mat mat); // Compute matrix determinant +RF_API float rf_mat_trace(rf_mat mat); // Returns the trace of the matrix (sum of the values along the diagonal) +RF_API rf_mat rf_mat_transpose(rf_mat mat); // Transposes provided matrix +RF_API rf_mat rf_mat_invert(rf_mat mat); // Invert provided matrix +RF_API rf_mat rf_mat_normalize(rf_mat mat); // Normalize provided matrix +RF_API rf_mat rf_mat_identity(void); // Returns identity matrix +RF_API rf_mat rf_mat_add(rf_mat left, rf_mat right); // Add two matrices +RF_API rf_mat rf_mat_sub(rf_mat left, rf_mat right); // Subtract two matrices (left - right) +RF_API rf_mat rf_mat_translate(float x, float y, float z); // Returns translation matrix +RF_API rf_mat rf_mat_rotate(rf_vec3 axis, float angle); // Create rotation matrix from axis and angle. NOTE: Angle should be provided in radians +RF_API rf_mat rf_mat_rotate_xyz(rf_vec3 ang); // Returns xyz-rotation matrix (angles in radians) +RF_API rf_mat rf_mat_rotate_x(float angle); // Returns x-rotation matrix (angle in radians) +RF_API rf_mat rf_mat_rotate_y(float angle); // Returns y-rotation matrix (angle in radians) +RF_API rf_mat rf_mat_rotate_z(float angle); // Returns z-rotation matrix (angle in radians) +RF_API rf_mat rf_mat_scale(float x, float y, float z); // Returns scaling matrix +RF_API rf_mat rf_mat_mul(rf_mat left, rf_mat right); // Returns two matrix multiplication. NOTE: When multiplying matrices... the order matters! +RF_API rf_mat rf_mat_frustum(double left, double right, double bottom, double top, double near_val, double far_val); // Returns perspective GL_PROJECTION matrix +RF_API rf_mat rf_mat_perspective(double fovy, double aspect, double near_val, double far_val); // Returns perspective GL_PROJECTION matrix. NOTE: Angle should be provided in radians +RF_API rf_mat rf_mat_ortho(double left, double right, double bottom, double top, double near_val, double far_val); // Returns orthographic GL_PROJECTION matrix +RF_API rf_mat rf_mat_look_at(rf_vec3 eye, rf_vec3 target, rf_vec3 up); // Returns camera look-at matrix (view matrix) +RF_API rf_float16 rf_mat_to_float16(rf_mat mat); // Returns the matrix as an array of 16 floats + +RF_API rf_quaternion rf_quaternion_identity(void); // Returns identity quaternion +RF_API float rf_quaternion_len(rf_quaternion q); // Computes the length of a quaternion +RF_API rf_quaternion rf_quaternion_normalize(rf_quaternion q); // Normalize provided quaternion +RF_API rf_quaternion rf_quaternion_invert(rf_quaternion q); // Invert provided quaternion +RF_API rf_quaternion rf_quaternion_mul(rf_quaternion q1, rf_quaternion q2); // Calculate two quaternion multiplication +RF_API rf_quaternion rf_quaternion_lerp(rf_quaternion q1, rf_quaternion q2, float amount); // Calculate linear interpolation between two quaternions +RF_API rf_quaternion rf_quaternion_nlerp(rf_quaternion q1, rf_quaternion q2, float amount); // Calculate slerp-optimized interpolation between two quaternions +RF_API rf_quaternion rf_quaternion_slerp(rf_quaternion q1, rf_quaternion q2, float amount); // Calculates spherical linear interpolation between two quaternions +RF_API rf_quaternion rf_quaternion_from_vector3_to_vector3(rf_vec3 from, rf_vec3 to); // Calculate quaternion based on the rotation from one vector to another +RF_API rf_quaternion rf_quaternion_from_matrix(rf_mat mat); // Returns a quaternion for a given rotation matrix +RF_API rf_mat rf_quaternion_to_matrix(rf_quaternion q); // Returns a matrix for a given quaternion +RF_API rf_quaternion rf_quaternion_from_axis_angle(rf_vec3 axis, float angle); // Returns rotation quaternion for an angle and axis. NOTE: angle must be provided in radians +RF_API void rf_quaternion_to_axis_angle(rf_quaternion q, rf_vec3* outAxis, float* outAngle); // Returns the rotation angle and axis for a given quaternion +RF_API rf_quaternion rf_quaternion_from_euler(float roll, float pitch, float yaw); // Returns he quaternion equivalent to Euler angles +RF_API rf_vec3 rf_quaternion_to_euler(rf_quaternion q); // Return the Euler angles equivalent to quaternion (roll, pitch, yaw). NOTE: Angles are returned in a rf_vec3 struct in degrees +RF_API rf_quaternion rf_quaternion_transform(rf_quaternion q, rf_mat mat); // rf_transform a quaternion given a transformation matrix + +#pragma endregion + +#pragma region collision detection + +RF_API bool rf_rec_match(rf_rec a, rf_rec b); +RF_API bool rf_check_collision_recs(rf_rec rec1, rf_rec rec2); // Check collision between two rectangles +RF_API bool rf_check_collision_circles(rf_vec2 center1, float radius1, rf_vec2 center2, float radius2); // Check collision between two circles +RF_API bool rf_check_collision_circle_rec(rf_vec2 center, float radius, rf_rec rec); // Check collision between circle and rectangle +RF_API bool rf_check_collision_point_rec(rf_vec2 point, rf_rec rec); // Check if point is inside rectangle +RF_API bool rf_check_collision_point_circle(rf_vec2 point, rf_vec2 center, float radius); // Check if point is inside circle +RF_API bool rf_check_collision_point_triangle(rf_vec2 point, rf_vec2 p1, rf_vec2 p2, rf_vec2 p3); // Check if point is inside a triangle + +RF_API rf_rec rf_get_collision_rec(rf_rec rec1, rf_rec rec2); // Get collision rectangle for two rectangles collision + +RF_API bool rf_check_collision_spheres(rf_vec3 center_a, float radius_a, rf_vec3 center_b, float radius_b); // Detect collision between two spheres +RF_API bool rf_check_collision_boxes(rf_bounding_box box1, rf_bounding_box box2); // Detect collision between two bounding boxes +RF_API bool rf_check_collision_box_sphere(rf_bounding_box box, rf_vec3 center, float radius); // Detect collision between box and sphere +RF_API bool rf_check_collision_ray_sphere(rf_ray ray, rf_vec3 center, float radius); // Detect collision between ray and sphere +RF_API bool rf_check_collision_ray_sphere_ex(rf_ray ray, rf_vec3 center, float radius, rf_vec3* collision_point); // Detect collision between ray and sphere, returns collision point +RF_API bool rf_check_collision_ray_box(rf_ray ray, rf_bounding_box box); // Detect collision between ray and box + +struct rf_model; + +RF_API rf_ray_hit_info rf_collision_ray_model(rf_ray ray, struct rf_model model); // Get collision info between ray and model +RF_API rf_ray_hit_info rf_collision_ray_triangle(rf_ray ray, rf_vec3 p1, rf_vec3 p2, rf_vec3 p3); // Get collision info between ray and triangle +RF_API rf_ray_hit_info rf_collision_ray_ground(rf_ray ray, float ground_height); // Get collision info between ray and ground plane (Y-normal plane) + +#pragma endregion + +#pragma region base64 + +typedef struct rf_base64_output +{ + int size; + unsigned char* buffer; +} rf_base64_output; + +RF_API int rf_get_size_base64(const unsigned char* input); +RF_API rf_base64_output rf_decode_base64(const unsigned char* input, rf_allocator allocator); + +#pragma endregion + +#endif // RAYFORK_MATH_H +/*** End of inlined file: rayfork-math.h ***/ + +#define RF_LIGHTGRAY (RF_LIT(rf_color) { 200, 200, 200, 255 }) +#define RF_GRAY (RF_LIT(rf_color) { 130, 130, 130, 255 }) +#define RF_DARKGRAY (RF_LIT(rf_color) { 80, 80, 80, 255 }) +#define RF_YELLOW (RF_LIT(rf_color) { 253, 249, 0, 255 }) +#define RF_GOLD (RF_LIT(rf_color) { 255, 203, 0, 255 }) +#define RF_ORANGE (RF_LIT(rf_color) { 255, 161, 0, 255 }) +#define RF_PINK (RF_LIT(rf_color) { 255, 109, 194, 255 }) +#define RF_RED (RF_LIT(rf_color) { 230, 41, 55, 255 }) +#define RF_MAROON (RF_LIT(rf_color) { 190, 33, 55, 255 }) +#define RF_GREEN (RF_LIT(rf_color) { 0, 228, 48, 255 }) +#define RF_LIME (RF_LIT(rf_color) { 0, 158, 47, 255 }) +#define RF_DARKGREEN (RF_LIT(rf_color) { 0, 117, 44, 255 }) +#define RF_SKYBLUE (RF_LIT(rf_color) { 102, 191, 255, 255 }) +#define RF_BLUE (RF_LIT(rf_color) { 0, 121, 241, 255 }) +#define RF_DARKBLUE (RF_LIT(rf_color) { 0, 82, 172, 255 }) +#define RF_PURPLE (RF_LIT(rf_color) { 200, 122, 255, 255 }) +#define RF_VIOLET (RF_LIT(rf_color) { 135, 60, 190, 255 }) +#define RF_DARKPURPLE (RF_LIT(rf_color) { 112, 31, 126, 255 }) +#define RF_BEIGE (RF_LIT(rf_color) { 211, 176, 131, 255 }) +#define RF_BROWN (RF_LIT(rf_color) { 127, 106, 79, 255 }) +#define RF_DARKBROWN (RF_LIT(rf_color) { 76, 63, 47, 255 }) + +#define RF_WHITE (RF_LIT(rf_color) { 255, 255, 255, 255 }) +#define RF_BLACK (RF_LIT(rf_color) { 0, 0, 0, 255 }) +#define RF_BLANK (RF_LIT(rf_color) { 0, 0, 0, 0 }) +#define RF_MAGENTA (RF_LIT(rf_color) { 255, 0, 255, 255 }) +#define RF_RAYWHITE (RF_LIT(rf_color) { 245, 245, 245, 255 }) + +#define RF_DEFAULT_KEY_COLOR (RF_MAGENTA) + +typedef enum rf_pixel_format +{ + RF_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + RF_UNCOMPRESSED_GRAY_ALPHA, // 8 * 2 bpp (2 channels) + RF_UNCOMPRESSED_R5G6B5, // 16 bpp + RF_UNCOMPRESSED_R8G8B8, // 24 bpp + RF_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + RF_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + RF_UNCOMPRESSED_R8G8B8A8, // 32 bpp + RF_UNCOMPRESSED_RGBA32 = RF_UNCOMPRESSED_R8G8B8A8, // 32 bpp + RF_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + RF_UNCOMPRESSED_R32G32B32, // 32 * 3 bpp (3 channels - float) + RF_UNCOMPRESSED_R32G32B32A32, // 32 * 4 bpp (4 channels - float) + RF_UNCOMPRESSED_NORMALIZED = RF_UNCOMPRESSED_R32G32B32A32, // 32 * 4 bpp (4 channels - float) + RF_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + RF_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + RF_COMPRESSED_DXT3_RGBA, // 8 bpp + RF_COMPRESSED_DXT5_RGBA, // 8 bpp + RF_COMPRESSED_ETC1_RGB, // 4 bpp + RF_COMPRESSED_ETC2_RGB, // 4 bpp + RF_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + RF_COMPRESSED_PVRT_RGB, // 4 bpp + RF_COMPRESSED_PVRT_RGBA, // 4 bpp + RF_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + RF_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} rf_pixel_format; + +typedef enum rf_pixel_format rf_compressed_pixel_format; +typedef enum rf_pixel_format rf_uncompressed_pixel_format; + +// R8G8B8A8 format +typedef struct rf_color +{ + unsigned char r, g, b, a; +} rf_color; + +typedef struct rf_palette +{ + rf_color* colors; + int count; +} rf_palette; + +#pragma region pixel format +RF_API const char* rf_pixel_format_string(rf_pixel_format format); +RF_API bool rf_is_uncompressed_format(rf_pixel_format format); +RF_API bool rf_is_compressed_format(rf_pixel_format format); +RF_API int rf_bits_per_pixel(rf_pixel_format format); +RF_API int rf_bytes_per_pixel(rf_uncompressed_pixel_format format); +RF_API int rf_pixel_buffer_size(int width, int height, rf_pixel_format format); + +RF_API bool rf_format_pixels_to_normalized(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, rf_vec4* dst, rf_int dst_size); +RF_API bool rf_format_pixels_to_rgba32(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, rf_color* dst, rf_int dst_size); +RF_API bool rf_format_pixels(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format); + +RF_API rf_vec4 rf_format_one_pixel_to_normalized(const void* src, rf_uncompressed_pixel_format src_format); +RF_API rf_color rf_format_one_pixel_to_rgba32(const void* src, rf_uncompressed_pixel_format src_format); +RF_API void rf_format_one_pixel(const void* src, rf_uncompressed_pixel_format src_format, void* dst, rf_uncompressed_pixel_format dst_format); +#pragma endregion + +#pragma region color +RF_API bool rf_color_match_rgb(rf_color a, rf_color b); // Returns true if the two colors have the same values for the rgb components +RF_API bool rf_color_match(rf_color a, rf_color b); // Returns true if the two colors have the same values +RF_API int rf_color_to_int(rf_color color); // Returns hexadecimal value for a rf_color +RF_API rf_vec4 rf_color_normalize(rf_color color); // Returns color normalized as float [0..1] +RF_API rf_color rf_color_from_normalized(rf_vec4 normalized); // Returns color from normalized values [0..1] +RF_API rf_vec3 rf_color_to_hsv(rf_color color); // Returns HSV values for a rf_color. Hue is returned as degrees [0..360] +RF_API rf_color rf_color_from_hsv(rf_vec3 hsv); // Returns a rf_color from HSV values. rf_color->HSV->rf_color conversion will not yield exactly the same color due to rounding errors. Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion +RF_API rf_color rf_color_from_int(int hex_value); // Returns a rf_color struct from hexadecimal value +RF_API rf_color rf_fade(rf_color color, float alpha); // rf_color fade-in or fade-out, alpha goes from 0.0f to 1.0f +#pragma endregion + +#endif // RAYFORK_COLOR_H +/*** End of inlined file: rayfork-color.h ***/ + + +/*** Start of inlined file: rayfork-camera.h ***/ +#ifndef RAYFORK_CAMERA_H +#define RAYFORK_CAMERA_H + +// Camera projection modes +typedef enum rf_camera_type +{ + RF_CAMERA_PERSPECTIVE = 0, + RF_CAMERA_ORTHOGRAPHIC +} rf_camera_type; + +typedef struct rf_camera2d +{ + rf_vec2 offset; // Camera offset (displacement from target) + rf_vec2 target; // Camera target (rotation and zoom origin) + float rotation; // Camera rotation in degrees + float zoom; // Camera zoom (scaling), should be 1.0f by default +} rf_camera2d; + +typedef struct rf_camera3d +{ + rf_camera_type type; // Camera type, defines projection types: RF_CAMERA_PERSPECTIVE or RF_CAMERA_ORTHOGRAPHIC + rf_vec3 position; // Camera position + rf_vec3 target; // Camera target it looks-at + rf_vec3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view apperture in Y (degrees) in perspective, used as near plane width in orthographic +} rf_camera3d; + +RF_API rf_vec3 rf_unproject(rf_vec3 source, rf_mat proj, rf_mat view); // Get world coordinates from screen coordinates +RF_API rf_ray rf_get_mouse_ray(rf_sizei screen_size, rf_vec2 mouse_position, rf_camera3d camera); // Returns a ray trace from mouse position +RF_API rf_mat rf_get_camera_matrix(rf_camera3d camera); // Get transform matrix for camera +RF_API rf_mat rf_get_camera_matrix2d(rf_camera2d camera); // Returns camera 2d transform matrix +RF_API rf_vec2 rf_get_world_to_screen(rf_sizei screen_size, rf_vec3 position, rf_camera3d camera); // Returns the screen space position from a 3d world space position +RF_API rf_vec2 rf_get_world_to_screen2d(rf_vec2 position, rf_camera2d camera); // Returns the screen space position for a 2d camera world space position +RF_API rf_vec2 rf_get_screen_to_world2d(rf_vec2 position, rf_camera2d camera); // Returns the world space position for a 2d camera screen space position + +#pragma region builtin camera + +// Camera system modes +typedef enum rf_builtin_camera3d_mode +{ + RF_CAMERA_CUSTOM = 0, + RF_CAMERA_FREE, + RF_CAMERA_ORBITAL, + RF_CAMERA_FIRST_PERSON, + RF_CAMERA_THIRD_PERSON +} rf_builtin_camera3d_mode; + +typedef struct rf_camera3d_state +{ + rf_vec2 camera_angle; // rf_camera3d angle in plane XZ + float camera_target_distance; // rf_camera3d distance from position to target + float player_eyes_position; + rf_builtin_camera3d_mode camera_mode; // Current camera mode + int swing_counter; // Used for 1st person swinging movement + rf_vec2 previous_mouse_position; +} rf_camera3d_state; + +typedef struct rf_input_state_for_update_camera +{ + rf_vec2 mouse_position; + int mouse_wheel_move; // Mouse wheel movement Y + bool is_camera_pan_control_key_down; // Middle mouse button + bool is_camera_alt_control_key_down; // Left Alt Key + bool is_camera_smooth_zoom_control_key; // Left Control Key + bool direction_keys[6]; // 'W', 'S', 'D', 'A', 'E', 'Q' +} rf_input_state_for_update_camera; + +RF_API void rf_set_camera3d_mode(rf_camera3d_state* state, rf_camera3d camera, rf_builtin_camera3d_mode mode); +RF_API void rf_update_camera3d(rf_camera3d* camera, rf_camera3d_state* state, rf_input_state_for_update_camera input_state); + +#pragma endregion + +#endif // RAYFORK_CAMERA_H +/*** End of inlined file: rayfork-camera.h ***/ + + +/*** Start of inlined file: rayfork-image.h ***/ +#ifndef RAYFORK_IMAGE_H +#define RAYFORK_IMAGE_H + +typedef enum rf_desired_channels +{ + RF_ANY_CHANNELS = 0, + RF_1BYTE_GRAYSCALE = 1, + RF_2BYTE_GRAY_ALPHA = 2, + RF_3BYTE_R8G8B8 = 3, + RF_4BYTE_R8G8B8A8 = 4, +} rf_desired_channels; + +typedef struct rf_image +{ + void* data; // image raw data + int width; // image base width + int height; // image base height + rf_pixel_format format; // Data format (rf_pixel_format type) + bool valid; // True if the image is valid and can be used +} rf_image; + +typedef struct rf_mipmaps_stats +{ + int possible_mip_counts; + int mipmaps_buffer_size; +} rf_mipmaps_stats; + +typedef struct rf_mipmaps_image +{ + union + { + rf_image image; + struct + { + void* data; // image raw data + int width; // image base width + int height; // image base height + rf_pixel_format format; // Data format (rf_pixel_format type) + bool valid; + }; + }; + + int mipmaps; // Mipmap levels, 1 by default +} rf_mipmaps_image; + +typedef struct rf_gif +{ + int frames_count; + int* frame_delays; + + union + { + rf_image image; + + struct + { + void* data; // rf_image raw data + int width; // rf_image base width + int height; // rf_image base height + rf_pixel_format format; // Data format (rf_pixel_format type) + bool valid; + }; + }; +} rf_gif; + +#pragma region extract image data functions +RF_API int rf_image_size(rf_image image); +RF_API int rf_image_size_in_format(rf_image image, rf_pixel_format format); + +RF_API bool rf_image_get_pixels_as_rgba32_to_buffer(rf_image image, rf_color* dst, rf_int dst_size); +RF_API bool rf_image_get_pixels_as_normalized_to_buffer(rf_image image, rf_vec4* dst, rf_int dst_size); + +RF_API rf_color* rf_image_pixels_to_rgba32(rf_image image, rf_allocator allocator); +RF_API rf_vec4* rf_image_compute_pixels_to_normalized(rf_image image, rf_allocator allocator); + +RF_API void rf_image_extract_palette_to_buffer(rf_image image, rf_color* palette_dst, rf_int palette_size); +RF_API rf_palette rf_image_extract_palette(rf_image image, rf_int palette_size, rf_allocator allocator); +RF_API rf_rec rf_image_alpha_border(rf_image image, float threshold); +#pragma endregion + +#pragma region loading & unloading functions +RF_API bool rf_supports_image_file_type(const char* filename); + +RF_API rf_image rf_load_image_from_file_data_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size, rf_desired_channels channels, rf_allocator temp_allocator); +RF_API rf_image rf_load_image_from_file_data(const void* src, rf_int src_size, rf_allocator allocator, rf_allocator temp_allocator); + +RF_API rf_image rf_load_image_from_hdr_file_data_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size, rf_desired_channels channels, rf_allocator temp_allocator); +RF_API rf_image rf_load_image_from_hdr_file_data(const void* src, rf_int src_size, rf_allocator allocator, rf_allocator temp_allocator); + +RF_API rf_image rf_load_image_from_format_to_buffer(const void* src, rf_int src_size, int src_width, int src_height, rf_uncompressed_pixel_format src_format, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format); +RF_API rf_image rf_load_image_from_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); + +RF_API void rf_unload_image(rf_image image, rf_allocator allocator); +#pragma endregion + +#pragma region mipmaps +RF_API int rf_mipmaps_image_size(rf_mipmaps_image image); +RF_API rf_mipmaps_stats rf_compute_mipmaps_stats(rf_image image, int desired_mipmaps_count); +RF_API rf_mipmaps_image rf_image_gen_mipmaps_to_buffer(rf_image image, int gen_mipmaps_count, void* dst, rf_int dst_size, rf_allocator temp_allocator); // Generate all mipmap levels for a provided image. image.data is scaled to include mipmap levels. Mipmaps format is the same as base image +RF_API rf_mipmaps_image rf_image_gen_mipmaps(rf_image image, int desired_mipmaps_count, rf_allocator allocator, rf_allocator temp_allocator); +RF_API void rf_unload_mipmaps_image(rf_mipmaps_image image, rf_allocator allocator); +#pragma endregion + +#pragma region dds +RF_API rf_int rf_get_dds_image_size(const void* src, rf_int src_size); +RF_API rf_mipmaps_image rf_load_dds_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size); +RF_API rf_mipmaps_image rf_load_dds_image(const void* src, rf_int src_size, rf_allocator allocator); +RF_API rf_mipmaps_image rf_load_dds_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +#pragma endregion + +#pragma region pkm +RF_API rf_int rf_get_pkm_image_size(const void* src, rf_int src_size); +RF_API rf_image rf_load_pkm_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size); +RF_API rf_image rf_load_pkm_image(const void* src, rf_int src_size, rf_allocator allocator); +RF_API rf_image rf_load_pkm_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +#pragma endregion + +#pragma region ktx +RF_API rf_int rf_get_ktx_image_size(const void* src, rf_int src_size); +RF_API rf_mipmaps_image rf_load_ktx_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size); +RF_API rf_mipmaps_image rf_load_ktx_image(const void* src, rf_int src_size, rf_allocator allocator); +RF_API rf_mipmaps_image rf_load_ktx_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +#pragma endregion + +#pragma region gif +RF_API rf_gif rf_load_animated_gif(const void* data, rf_int data_size, rf_allocator allocator, rf_allocator temp_allocator); +RF_API rf_gif rf_load_animated_gif_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +RF_API rf_sizei rf_gif_frame_size(rf_gif gif); +RF_API rf_image rf_get_frame_from_gif(rf_gif gif, int frame); +RF_API void rf_unload_gif(rf_gif gif, rf_allocator allocator); +#pragma endregion + +#pragma region image gen +RF_API rf_vec2 rf_get_seed_for_cellular_image(int seeds_per_row, int tile_size, int i, rf_rand_proc rand); + +RF_API rf_image rf_gen_image_color_to_buffer(int width, int height, rf_color color, rf_color* dst, rf_int dst_size); +RF_API rf_image rf_gen_image_color(int width, int height, rf_color color, rf_allocator allocator); +RF_API rf_image rf_gen_image_gradient_v_to_buffer(int width, int height, rf_color top, rf_color bottom, rf_color* dst, rf_int dst_size); +RF_API rf_image rf_gen_image_gradient_v(int width, int height, rf_color top, rf_color bottom, rf_allocator allocator); +RF_API rf_image rf_gen_image_gradient_h_to_buffer(int width, int height, rf_color left, rf_color right, rf_color* dst, rf_int dst_size); +RF_API rf_image rf_gen_image_gradient_h(int width, int height, rf_color left, rf_color right, rf_allocator allocator); +RF_API rf_image rf_gen_image_gradient_radial_to_buffer(int width, int height, float density, rf_color inner, rf_color outer, rf_color* dst, rf_int dst_size); +RF_API rf_image rf_gen_image_gradient_radial(int width, int height, float density, rf_color inner, rf_color outer, rf_allocator allocator); +RF_API rf_image rf_gen_image_checked_to_buffer(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2, rf_color* dst, rf_int dst_size); +RF_API rf_image rf_gen_image_checked(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2, rf_allocator allocator); +RF_API rf_image rf_gen_image_white_noise_to_buffer(int width, int height, float factor, rf_rand_proc rand, rf_color* dst, rf_int dst_size); +RF_API rf_image rf_gen_image_white_noise(int width, int height, float factor, rf_rand_proc rand, rf_allocator allocator); +RF_API rf_image rf_gen_image_perlin_noise_to_buffer(int width, int height, int offset_x, int offset_y, float scale, rf_color* dst, rf_int dst_size); +RF_API rf_image rf_gen_image_perlin_noise(int width, int height, int offset_x, int offset_y, float scale, rf_allocator allocator); +RF_API rf_image rf_gen_image_cellular_to_buffer(int width, int height, int tile_size, rf_rand_proc rand, rf_color* dst, rf_int dst_size); +RF_API rf_image rf_gen_image_cellular(int width, int height, int tile_size, rf_rand_proc rand, rf_allocator allocator); +#pragma endregion + +#pragma region image manipulation +RF_API rf_image rf_image_copy_to_buffer(rf_image image, void* dst, rf_int dst_size); +RF_API rf_image rf_image_copy(rf_image image, rf_allocator allocator); + +RF_API rf_image rf_image_crop_to_buffer(rf_image image, rf_rec crop, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format); +RF_API rf_image rf_image_crop(rf_image image, rf_rec crop, rf_allocator allocator); + +RF_API rf_image rf_image_resize_to_buffer(rf_image image, int new_width, int new_height, void* dst, rf_int dst_size, rf_allocator temp_allocator); +RF_API rf_image rf_image_resize(rf_image image, int new_width, int new_height, rf_allocator allocator, rf_allocator temp_allocator); +RF_API rf_image rf_image_resize_nn_to_buffer(rf_image image, int new_width, int new_height, void* dst, rf_int dst_size); +RF_API rf_image rf_image_resize_nn(rf_image image, int new_width, int new_height, rf_allocator allocator); + +RF_API rf_image rf_image_format_to_buffer(rf_image image, rf_uncompressed_pixel_format dst_format, void* dst, rf_int dst_size); +RF_API rf_image rf_image_format(rf_image image, rf_uncompressed_pixel_format new_format, rf_allocator allocator); + +RF_API rf_image rf_image_alpha_mask_to_buffer(rf_image image, rf_image alpha_mask, void* dst, rf_int dst_size); +RF_API rf_image rf_image_alpha_clear(rf_image image, rf_color color, float threshold, rf_allocator allocator, rf_allocator temp_allocator); +RF_API rf_image rf_image_alpha_premultiply(rf_image image, rf_allocator allocator, rf_allocator temp_allocator); + +RF_API rf_rec rf_image_alpha_crop_rec(rf_image image, float threshold); +RF_API rf_image rf_image_alpha_crop(rf_image image, float threshold, rf_allocator allocator); + +RF_API rf_image rf_image_dither(rf_image image, int r_bpp, int g_bpp, int b_bpp, int a_bpp, rf_allocator allocator, rf_allocator temp_allocator); + +RF_API void rf_image_flip_vertical_in_place(rf_image* image); +RF_API rf_image rf_image_flip_vertical_to_buffer(rf_image image, void* dst, rf_int dst_size); +RF_API rf_image rf_image_flip_vertical(rf_image image, rf_allocator allocator); + +RF_API void rf_image_flip_horizontal_in_place(rf_image* image); +RF_API rf_image rf_image_flip_horizontal_to_buffer(rf_image image, void* dst, rf_int dst_size); +RF_API rf_image rf_image_flip_horizontal(rf_image image, rf_allocator allocator); + +RF_API rf_image rf_image_rotate_cw_to_buffer(rf_image image, void* dst, rf_int dst_size); +RF_API rf_image rf_image_rotate_cw(rf_image image); +RF_API rf_image rf_image_rotate_ccw_to_buffer(rf_image image, void* dst, rf_int dst_size); +RF_API rf_image rf_image_rotate_ccw(rf_image image); + +RF_API rf_image rf_image_color_tint_to_buffer(rf_image image, rf_color color, void* dst, rf_int dst_size); +RF_API rf_image rf_image_color_tint(rf_image image, rf_color color); +RF_API rf_image rf_image_color_invert_to_buffer(rf_image image, void* dst, rf_int dst_size); +RF_API rf_image rf_image_color_invert(rf_image image); +RF_API rf_image rf_image_color_grayscale_to_buffer(rf_image image, void* dst, rf_int dst_size); +RF_API rf_image rf_image_color_grayscale(rf_image image); +RF_API rf_image rf_image_color_contrast_to_buffer(rf_image image, float contrast, void* dst, rf_int dst_size); +RF_API rf_image rf_image_color_contrast(rf_image image, int contrast); +RF_API rf_image rf_image_color_brightness_to_buffer(rf_image image, int brightness, void* dst, rf_int dst_size); +RF_API rf_image rf_image_color_brightness(rf_image image, int brightness); +RF_API rf_image rf_image_color_replace_to_buffer(rf_image image, rf_color color, rf_color replace, void* dst, rf_int dst_size); +RF_API rf_image rf_image_color_replace(rf_image image, rf_color color, rf_color replace); + +RF_API void rf_image_draw(rf_image* dst, rf_image src, rf_rec src_rec, rf_rec dst_rec, rf_color tint, rf_allocator temp_allocator); +RF_API void rf_image_draw_rectangle(rf_image* dst, rf_rec rec, rf_color color, rf_allocator temp_allocator); +RF_API void rf_image_draw_rectangle_lines(rf_image* dst, rf_rec rec, int thick, rf_color color, rf_allocator temp_allocator); +#pragma endregion + +#endif // RAYFORK_IMAGE_H +/*** End of inlined file: rayfork-image.h ***/ + + +/*** Start of inlined file: rayfork-audio.h ***/ +#ifndef RAYFORK_AUDIO_H +#define RAYFORK_AUDIO_H + +/* + This buffer size is defined by number of samples, independent of sample size and channels number + After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a + standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough + In case of music-stalls, just increase this number. +*/ +#define RF_DEFAULT_AUDIO_BUFFER_SIZE (4096) +#define RF_DEFAULT_AUDIO_DEVICE_FORMAT uint16_t +#define RF_DEFAULT_DEVICE_SAMPLE_RATE (44100) +#define RF_DEFAULT_AUDIO_DEVICE_CHANNELS (RF_STEREO) + +typedef enum rf_audio_format +{ + RF_AUDIO_FORMAT_UNKNOWN = 0, + RF_AUDIO_FORMAT_WAV, + RF_AUDIO_FORMAT_OGG, + RF_AUDIO_FORMAT_FLAC, + RF_AUDIO_FORMAT_MP3, + RF_AUDIO_FORMAT_XM, + RF_AUDIO_FORMAT_MOD, +} rf_audio_format; + +typedef enum rf_audio_data_type +{ + RF_DECODED_AUDIO_DATA, + RF_ENCODED_AUDIO_DATA +} rf_audio_data_type; + +typedef enum rf_audio_player_type +{ + RF_STREAMING_AUDIO_PLAYER, + RF_STATIC_AUDIO_PLAYER, +} rf_audio_player_type; + +typedef enum rf_audio_channels +{ + RF_MONO = 1, + RF_STEREO = 2, +} rf_audio_channels; + +typedef struct rf_audio_buffer rf_audio_buffer; + +typedef struct rf_audio_data +{ + rf_audio_data_type type; + + void* data; + int data_size; + + int size_in_frames; + int sample_count; // Total number of samples + int sample_rate; // Frequency (samples per second) + int sample_size; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + rf_audio_channels channels; + bool valid; +} rf_audio_data; + +typedef struct rf_audio_player +{ + rf_audio_player_type type; + rf_audio_data source; + float volume; + bool looping; + bool valid; +} rf_audio_player; + +typedef struct rf_audio_device +{ + int id; +} rf_audio_device; + +#pragma region audio device +RF_API rf_audio_device rf_audio_device_count(); +RF_API rf_audio_device rf_default_audio_device(); +RF_API bool rf_start_audio_device(rf_audio_device); +RF_API void rf_close_audio_device(rf_audio_device); +RF_API bool rf_is_audio_device_ready(rf_audio_device); + +RF_API void rf_set_master_volume(float); +#pragma endregion + +#pragma region audio loading +RF_API rf_audio_format rf_audio_format_from_filename_extension(const char* text); +RF_API rf_audio_format rf_audio_format_from_filename_extension_string(const char* string, int string_len); + +RF_API rf_audio_data rf_fully_decode_wav (const void* src, int src_size, rf_allocator allocator); +RF_API rf_audio_data rf_fully_decode_ogg (const void* src, int src_size, rf_allocator allocator); +RF_API rf_audio_data rf_fully_decode_flac(const void* src, int src_size, rf_allocator allocator); +RF_API rf_audio_data rf_fully_decode_mp3 (const void* src, int src_size, rf_allocator allocator); +RF_API rf_audio_data rf_fully_decode_xm (const void* src, int src_size, rf_allocator allocator); +RF_API rf_audio_data rf_fully_decode_mod (const void* src, int src_size, rf_allocator allocator); + +RF_API rf_audio_data rf_fully_decode_audio_from_buffer(const void* src, int src_size, rf_audio_format format, rf_allocator allocator); +RF_API rf_audio_data rf_fully_decode_audio_from_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); + +RF_API void rf_unload_audio_data(rf_audio_data audio_data, rf_allocator allocator); + +RF_API rf_audio_data rf_load_encoded_audio_from_buffer(const void* src, int src_size, rf_audio_format format, rf_allocator allocator); +RF_API rf_audio_data rf_load_encoded_audio_from_file(const char* filename, rf_allocator allocator, rf_io_callbacks io); +#pragma endregion + +#pragma region audio controls +RF_API void rf_audio_play(rf_audio_player* audio); +RF_API void rf_audio_stop(rf_audio_player* audio); +RF_API void rf_audio_pause(rf_audio_player* audio); +RF_API void rf_audio_resume(rf_audio_player* audio); +RF_API void rf_audio_update(rf_audio_player* audio); + +RF_API void rf_audio_set_volume(rf_audio_player* audio); +RF_API void rf_audio_set_pitch(rf_audio_player* audio); + +RF_API float rf_audio_time_len(rf_audio_player audio); +RF_API float rf_audio_time_played(rf_audio_player audio); +RF_API bool rf_audio_is_playing(rf_audio_player audio); +RF_API int rf_audio_volume(rf_audio_player audio); +RF_API int rf_audio_pitch(rf_audio_player audio); +#pragma endregion + +#endif // RAYFORK_AUDIO_H +/*** End of inlined file: rayfork-audio.h ***/ + + +/*** Start of inlined file: rayfork-gfx.h ***/ +#ifndef RAYFORK_GFX_CORE_H +#define RAYFORK_GFX_CORE_H + +#if !defined(RAYFORK_GRAPHICS_BACKEND_GL_33) && !defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) && !defined(RAYFORK_GRAPHICS_BACKEND_METAL) && !defined(RAYFORK_GRAPHICS_BACKEND_DIRECTX) + #define RF_NO_GRAPHICS_BACKEND_SELECTED_BY_THE_USER (1) +#endif + +// If no graphics backend was set, choose OpenGL33 on desktop and OpenGL ES3 on mobile +#if RF_NO_GRAPHICS_BACKEND_SELECTED_BY_THE_USER + #if defined(RAYFORK_PLATFORM_WINDOWS) || defined(RAYFORK_PLATFORM_LINUX) || defined(RAYFORK_PLATFORM_MACOS) + #define RAYFORK_GRAPHICS_BACKEND_GL_33 (1) + #else // if on mobile + #define RAYFORK_GRAPHICS_BACKEND_GL_ES3 (1) + #endif +#endif + +// Check to make sure only one graphics backend was selected +#if (defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) + defined(RAYFORK_GRAPHICS_BACKEND_METAL) + defined(RAYFORK_GRAPHICS_BACKEND_DIRECTX)) != 1 + #error rayfork error: you can only set one graphics backend but 2 or more were detected. +#endif + +#ifndef RF_DEFAULT_BATCH_ELEMENTS_COUNT + #if defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3) || defined(RAYFORK_GRAPHICS_BACKEND_METAL) + #define RF_DEFAULT_BATCH_ELEMENTS_COUNT (2048) + #else + #define RF_DEFAULT_BATCH_ELEMENTS_COUNT (8192) + #endif +#endif + +#if !defined(RF_DEFAULT_BATCH_VERTEX_BUFFERS_COUNT) + #define RF_DEFAULT_BATCH_VERTEX_BUFFERS_COUNT (1) // Max number of buffers for batching (multi-buffering) +#endif + +#if !defined(RF_MAX_MATRIX_STACK_SIZE) + #define RF_MAX_MATRIX_STACK_SIZE (32) // Max size of rf_mat rf__ctx->gl_ctx.stack +#endif + +#if !defined(RF_DEFAULT_BATCH_DRAW_CALLS_COUNT) + #define RF_DEFAULT_BATCH_DRAW_CALLS_COUNT (256) // Max rf__ctx->gl_ctx.draws by state changes (mode, texture) +#endif + +// Shader and material limits +#if !defined(RF_MAX_SHADER_LOCATIONS) + #define RF_MAX_SHADER_LOCATIONS (32) // Maximum number of predefined locations stored in shader struct +#endif + +#if !defined(RF_MAX_MATERIAL_MAPS) + #define RF_MAX_MATERIAL_MAPS (12) // Maximum number of texture maps stored in shader struct +#endif + +#if !defined(RF_MAX_TEXT_BUFFER_LENGTH) + #define RF_MAX_TEXT_BUFFER_LENGTH (1024) // Size of internal RF_INTERNAL buffers used on some functions: +#endif + +#if !defined(RF_MAX_MESH_VBO) + #define RF_MAX_MESH_VBO (7) // Maximum number of vbo per mesh +#endif + +// Default vertex attribute names on shader to set location points +#define RF_DEFAULT_ATTRIB_POSITION_NAME "vertex_position" // shader-location = 0 +#define RF_DEFAULT_ATTRIB_TEXCOORD_NAME "vertex_tex_coord" // shader-location = 1 +#define RF_DEFAULT_ATTRIB_NORMAL_NAME "vertex_normal" // shader-location = 2 +#define RF_DEFAULT_ATTRIB_COLOR_NAME "vertex_color" // shader-location = 3 +#define RF_DEFAULT_ATTRIB_TANGENT_NAME "vertex_tangent" // shader-location = 4 +#define RF_DEFAULT_ATTRIB_TEXCOORD2_NAME "vertex_tex_coord2" // shader-location = 5 + +// Matrix modes (equivalent to OpenGL) +typedef enum rf_matrix_mode +{ + RF_MODELVIEW = 0x1700, // GL_MODELVIEW + RF_PROJECTION = 0x1701, // GL_PROJECTION + RF_TEXTURE = 0x1702, // GL_TEXTURE +} rf_matrix_mode; + +// Drawing modes (equivalent to OpenGL) +typedef enum rf_drawing_mode +{ + RF_LINES = 0x0001, // GL_LINES + RF_TRIANGLES = 0x0004, // GL_TRIANGLES + RF_QUADS = 0x0007, // GL_QUADS +} rf_drawing_mode; + +// Shader location point type +typedef enum rf_shader_location_index +{ + RF_LOC_VERTEX_POSITION = 0, + RF_LOC_VERTEX_TEXCOORD01 = 1, + RF_LOC_VERTEX_TEXCOORD02 = 2, + RF_LOC_VERTEX_NORMAL = 3, + RF_LOC_VERTEX_TANGENT = 4, + RF_LOC_VERTEX_COLOR = 5, + RF_LOC_MATRIX_MVP = 6, + RF_LOC_MATRIX_MODEL = 7, + RF_LOC_MATRIX_VIEW = 8, + RF_LOC_MATRIX_PROJECTION = 9, + RF_LOC_VECTOR_VIEW = 10, + RF_LOC_COLOR_DIFFUSE = 11, + RF_LOC_COLOR_SPECULAR = 12, + RF_LOC_COLOR_AMBIENT = 13, + + // These 2 are intentionally the same + RF_LOC_MAP_ALBEDO = 14, + RF_LOC_MAP_DIFFUSE = 14, + + // These 2 are intentionally the same + RF_LOC_MAP_METALNESS = 15, + RF_LOC_MAP_SPECULAR = 15, + + RF_LOC_MAP_NORMAL = 16, + RF_LOC_MAP_ROUGHNESS = 17, + RF_LOC_MAP_OCCLUSION = 18, + RF_LOC_MAP_EMISSION = 19, + RF_LOC_MAP_HEIGHT = 20, + RF_LOC_MAP_CUBEMAP = 21, + RF_LOC_MAP_IRRADIANCE = 22, + RF_LOC_MAP_PREFILTER = 23, + RF_LOC_MAP_BRDF = 24, +} rf_shader_location_index; + +// rf_shader uniform data types +typedef enum rf_shader_uniform_data_type +{ + RF_UNIFORM_FLOAT = 0, + RF_UNIFORM_VEC2, + RF_UNIFORM_VEC3, + RF_UNIFORM_VEC4, + RF_UNIFORM_INT, + RF_UNIFORM_IVEC2, + RF_UNIFORM_IVEC3, + RF_UNIFORM_IVEC4, + RF_UNIFORM_SAMPLER2D +} rf_shader_uniform_data_type; + +// rf_texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum rf_texture_filter_mode +{ + RF_FILTER_POINT = 0, // No filter, just pixel aproximation + RF_FILTER_BILINEAR, // Linear filtering + RF_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + RF_FILTER_ANISOTROPIC_4x, // Anisotropic filtering 4x + RF_FILTER_ANISOTROPIC_8x, // Anisotropic filtering 8x + RF_FILTER_ANISOTROPIC_16x, // Anisotropic filtering 16x +} rf_texture_filter_mode; + +// Cubemap layout type +typedef enum rf_cubemap_layout_type +{ + RF_CUBEMAP_AUTO_DETECT = 0, // Automatically detect layout type + RF_CUBEMAP_LINE_VERTICAL, // Layout is defined by a vertical line with faces + RF_CUBEMAP_LINE_HORIZONTAL, // Layout is defined by an horizontal line with faces + RF_CUBEMAP_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + RF_CUBEMAP_CROSS_FOUR_BY_TREE, // Layout is defined by a 4x3 cross with cubemap faces + RF_CUBEMAP_PANORAMA // Layout is defined by a panorama image (equirectangular map) +} rf_cubemap_layout_type; + +// rf_texture parameters: wrap mode +typedef enum rf_texture_wrap_mode +{ + RF_WRAP_REPEAT = 0, // Repeats texture in tiled mode + RF_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + RF_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + RF_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} rf_texture_wrap_mode; + +// rf_color blending modes (pre-defined) +typedef enum rf_blend_mode +{ + RF_BLEND_ALPHA = 0, // Blend textures considering alpha (default) + RF_BLEND_ADDITIVE, // Blend textures adding colors + RF_BLEND_MULTIPLIED // Blend textures multiplying colors +} rf_blend_mode; + +typedef struct rf_shader +{ + unsigned int id; // rf_shader program id + int locs[RF_MAX_SHADER_LOCATIONS]; // rf_shader locations array (RF_MAX_SHADER_LOCATIONS) +} rf_shader; + +typedef struct rf_gfx_pixel_format +{ + unsigned int internal_format; + unsigned int format; + unsigned int type; + bool valid; +} rf_gfx_pixel_format; + +typedef struct rf_texture2d +{ + unsigned int id; // OpenGL texture id + int width; // rf_texture base width + int height; // rf_texture base height + int mipmaps; // Mipmap levels, 1 by default + rf_pixel_format format; // Data format (rf_pixel_format type) + bool valid; +} rf_texture2d, rf_texture_cubemap; + +typedef struct rf_render_texture2d +{ + unsigned int id; // OpenGL Framebuffer Object (FBO) id + rf_texture2d texture; // rf_color buffer attachment texture + rf_texture2d depth; // Depth buffer attachment texture + int depth_texture; // Track if depth attachment is a texture or renderbuffer +} rf_render_texture2d; + +struct rf_vertex_buffer; +struct rf_mesh; +struct rf_material; +typedef void rf_gfx_backend_data; +typedef void rf_audio_backend_data; + +#pragma region shader +RF_API rf_shader rf_gfx_load_shader(const char* vs_code, const char* fs_code); // Load shader from code strings. If shader string is NULL, using default vertex/fragment shaders +RF_API void rf_gfx_unload_shader(rf_shader shader); // Unload shader from GPU memory (VRAM) +RF_API int rf_gfx_get_shader_location(rf_shader shader, const char* uniform_name); // Get shader uniform location +RF_API void rf_gfx_set_shader_value(rf_shader shader, int uniform_loc, const void* value, int uniform_name); // Set shader uniform value +RF_API void rf_gfx_set_shader_value_v(rf_shader shader, int uniform_loc, const void* value, int uniform_name, int count); // Set shader uniform value vector +RF_API void rf_gfx_set_shader_value_matrix(rf_shader shader, int uniform_loc, rf_mat mat); // Set shader uniform value (matrix 4x4) +RF_API void rf_gfx_set_shader_value_texture(rf_shader shader, int uniform_loc, rf_texture2d texture); // Set shader uniform value for texture +#pragma endregion + +RF_API rf_mat rf_gfx_get_matrix_projection(); // Return internal rf__ctx->gl_ctx.projection matrix +RF_API rf_mat rf_gfx_get_matrix_modelview(); // Return internal rf__ctx->gl_ctx.modelview matrix +RF_API void rf_gfx_set_matrix_projection(rf_mat proj); // Set a custom projection matrix (replaces internal rf__ctx->gl_ctx.projection matrix) +RF_API void rf_gfx_set_matrix_modelview(rf_mat view); // Set a custom rf__ctx->gl_ctx.modelview matrix (replaces internal rf__ctx->gl_ctx.modelview matrix) + +RF_API void rf_gfx_blend_mode(rf_blend_mode mode); // Choose the blending mode (alpha, additive, multiplied) +RF_API void rf_gfx_matrix_mode(rf_matrix_mode mode); // Choose the current matrix to be transformed +RF_API void rf_gfx_push_matrix(); // Push the current matrix to rf_global_gl_stack +RF_API void rf_gfx_pop_matrix(); // Pop lattest inserted matrix from rf_global_gl_stack +RF_API void rf_gfx_load_identity(); // Reset current matrix to identity matrix +RF_API void rf_gfx_translatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RF_API void rf_gfx_rotatef(float angleDeg, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RF_API void rf_gfx_scalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RF_API void rf_gfx_mult_matrixf(float* matf); // Multiply the current matrix by another matrix +RF_API void rf_gfx_frustum(double left, double right, double bottom, double top, double znear, double zfar); +RF_API void rf_gfx_ortho(double left, double right, double bottom, double top, double znear, double zfar); +RF_API void rf_gfx_viewport(int x, int y, int width, int height); // Set the viewport area + +// Functions Declaration - Vertex level operations +RF_API void rf_gfx_begin(rf_drawing_mode mode); // Initialize drawing mode (how to organize vertex) +RF_API void rf_gfx_end(); // Finish vertex providing +RF_API void rf_gfx_vertex2i(int x, int y); // Define one vertex (position) - 2 int +RF_API void rf_gfx_vertex2f(float x, float y); // Define one vertex (position) - 2 float +RF_API void rf_gfx_vertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RF_API void rf_gfx_tex_coord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RF_API void rf_gfx_normal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RF_API void rf_gfx_color4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 unsigned char +RF_API void rf_gfx_color3f(float x, float y, float z); // Define one vertex (color) - 3 float +RF_API void rf_gfx_color4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float + +RF_API void rf_gfx_enable_texture(unsigned int id); // Enable texture usage +RF_API void rf_gfx_disable_texture(); // Disable texture usage +RF_API void rf_gfx_set_texture_wrap(rf_texture2d texture, rf_texture_wrap_mode wrap_mode); // Set texture parameters (wrap mode/filter mode) +RF_API void rf_gfx_set_texture_filter(rf_texture2d texture, rf_texture_filter_mode filter_mode); // Set filter for texture +RF_API void rf_gfx_enable_render_texture(unsigned int id); // Enable render texture (fbo) +RF_API void rf_gfx_disable_render_texture(void); // Disable render texture (fbo), return to default framebuffer +RF_API void rf_gfx_enable_depth_test(void); // Enable depth test +RF_API void rf_gfx_disable_depth_test(void); // Disable depth test +RF_API void rf_gfx_enable_backface_culling(void); // Enable backface culling +RF_API void rf_gfx_disable_backface_culling(void); // Disable backface culling +RF_API void rf_gfx_enable_scissor_test(void); // Enable scissor test +RF_API void rf_gfx_disable_scissor_test(void); // Disable scissor test +RF_API void rf_gfx_scissor(int x, int y, int width, int height); // Scissor test +RF_API void rf_gfx_enable_wire_mode(void); // Enable wire mode +RF_API void rf_gfx_disable_wire_mode(void); // Disable wire mode +RF_API void rf_gfx_delete_textures(unsigned int id); // Delete OpenGL texture from GPU +RF_API void rf_gfx_delete_render_textures(rf_render_texture2d target); // Delete render textures (fbo) from GPU +RF_API void rf_gfx_delete_shader(unsigned int id); // Delete OpenGL shader program from GPU +RF_API void rf_gfx_delete_vertex_arrays(unsigned int id); // Unload vertex data (VAO) from GPU memory +RF_API void rf_gfx_delete_buffers(unsigned int id); // Unload vertex data (VBO) from GPU memory +RF_API void rf_gfx_clear_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RF_API void rf_gfx_clear_screen_buffers(void); // Clear used screen buffers (color and depth) +RF_API void rf_gfx_update_buffer(int buffer_id, void* data, int data_size); // Update GPU buffer with new data +RF_API unsigned int rf_gfx_load_attrib_buffer(unsigned int vao_id, int shader_loc, void* buffer, int size, bool dynamic); // Load a new attributes buffer +RF_API void rf_gfx_init_vertex_buffer(struct rf_vertex_buffer* vertex_buffer); + +RF_API void rf_gfx_close(); // De-inititialize rf gfx (buffers, shaders, textures) +RF_API void rf_gfx_draw(); // Update and draw default internal buffers + +RF_API bool rf_gfx_check_buffer_limit(int v_count); // Check internal buffer overflow for a given number of vertex +RF_API void rf_gfx_set_debug_marker(const char* text); // Set debug marker for analysis + +// Textures data management +RF_API unsigned int rf_gfx_load_texture(void* data, int width, int height, rf_pixel_format format, int mipmap_count); // Load texture in GPU +RF_API unsigned int rf_gfx_load_texture_depth(int width, int height, int bits, bool use_render_buffer); // Load depth texture/renderbuffer (to be attached to fbo) +RF_API unsigned int rf_gfx_load_texture_cubemap(void* data, int size, rf_pixel_format format); // Load texture cubemap +RF_API void rf_gfx_update_texture(unsigned int id, int width, int height, rf_pixel_format format, const void* pixels, int pixels_size); // Update GPU texture with new data +RF_API rf_gfx_pixel_format rf_gfx_get_internal_texture_formats(rf_pixel_format format); // Get OpenGL internal formats +RF_API void rf_gfx_unload_texture(unsigned int id); // Unload texture from GPU memory + +RF_API void rf_gfx_generate_mipmaps(rf_texture2d* texture); // Generate mipmap data for selected texture +RF_API rf_image rf_gfx_read_texture_pixels_to_buffer(rf_texture2d texture, void* dst, int dst_size); +RF_API rf_image rf_gfx_read_texture_pixels(rf_texture2d texture, rf_allocator allocator); +RF_API void rf_gfx_read_screen_pixels(rf_color* dst, int width, int height); // Read screen pixel data (color buffer) + +// Render texture management (fbo) +RF_API rf_render_texture2d rf_gfx_load_render_texture(int width, int height, rf_pixel_format format, int depth_bits, bool use_depth_texture); // Load a render texture (with color and depth attachments) +RF_API void rf_gfx_render_texture_attach(rf_render_texture2d target, unsigned int id, int attach_type); // Attach texture/renderbuffer to an fbo +RF_API bool rf_gfx_render_texture_complete(rf_render_texture2d target); // Verify render texture is complete + +// Vertex data management +RF_API void rf_gfx_load_mesh(struct rf_mesh* mesh, bool dynamic); // Upload vertex data into GPU and provided VAO/VBO ids +RF_API void rf_gfx_update_mesh(struct rf_mesh mesh, int buffer, int num); // Update vertex or index data on GPU (upload new data to one buffer) +RF_API void rf_gfx_update_mesh_at(struct rf_mesh mesh, int buffer, int num, int index); // Update vertex or index data on GPU, at index +RF_API void rf_gfx_draw_mesh(struct rf_mesh mesh, struct rf_material material, rf_mat transform); // Draw a 3d mesh with material and transform +RF_API void rf_gfx_unload_mesh(struct rf_mesh mesh); // Unload mesh data from CPU and GPU + +#endif // RAYFORK_GFX_CORE_H +/*** End of inlined file: rayfork-gfx.h ***/ + + +/*** Start of inlined file: rayfork-gfx-backend-opengl.h ***/ +#if !defined(RAYFORK_GFX_BACKEND_OPENGL_H) && (defined(RAYFORK_GRAPHICS_BACKEND_GL_33) || defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3)) +#define RAYFORK_GFX_BACKEND_OPENGL_H + +#if !defined(RF_GL_CALLING_CONVENTION) && (defined(RAYFORK_PLATFORM_WINDOWS) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)) + #define RF_GL_CALLING_CONVENTION __stdcall * +#else + #define RF_GL_CALLING_CONVENTION * +#endif + +typedef struct rf_opengl_procs +{ + void (RF_GL_CALLING_CONVENTION Viewport) (int x, int y, int width, int height); + void (RF_GL_CALLING_CONVENTION BindTexture) (unsigned int target, unsigned int texture); + void (RF_GL_CALLING_CONVENTION TexParameteri) (unsigned int target, unsigned int pname, int param); + void (RF_GL_CALLING_CONVENTION TexParameterf) (unsigned int target, unsigned int pname, float param); + void (RF_GL_CALLING_CONVENTION TexParameteriv) (unsigned int target, unsigned int pname, const int* params); + void (RF_GL_CALLING_CONVENTION BindFramebuffer) (unsigned int target, unsigned int framebuffer); + void (RF_GL_CALLING_CONVENTION Enable) (unsigned int cap); + void (RF_GL_CALLING_CONVENTION Disable) (unsigned int cap); + void (RF_GL_CALLING_CONVENTION Scissor) (int x, int y, int width, int height); + void (RF_GL_CALLING_CONVENTION DeleteTextures) (int n, const unsigned int* textures); + void (RF_GL_CALLING_CONVENTION DeleteRenderbuffers) (int n, const unsigned int* renderbuffers); + void (RF_GL_CALLING_CONVENTION DeleteFramebuffers) (int n, const unsigned int* framebuffers); + void (RF_GL_CALLING_CONVENTION DeleteVertexArrays) (int n, const unsigned int* arrays); + void (RF_GL_CALLING_CONVENTION DeleteBuffers) (int n, const unsigned int* buffers); + void (RF_GL_CALLING_CONVENTION ClearColor) (float red, float green, float blue, float alpha); + void (RF_GL_CALLING_CONVENTION Clear) (unsigned int mask); + void (RF_GL_CALLING_CONVENTION BindBuffer) (unsigned int target, unsigned int buffer); + void (RF_GL_CALLING_CONVENTION BufferSubData) (unsigned int target, ptrdiff_t offset, ptrdiff_t size, const void* data); + void (RF_GL_CALLING_CONVENTION BindVertexArray) (unsigned int array); + void (RF_GL_CALLING_CONVENTION GenBuffers) (int n, unsigned int* buffers); + void (RF_GL_CALLING_CONVENTION BufferData) (unsigned int target, ptrdiff_t size, const void* data, unsigned int usage); + void (RF_GL_CALLING_CONVENTION VertexAttribPointer) (unsigned int index, int size, unsigned int type, unsigned char normalized, int stride, const void* pointer); + void (RF_GL_CALLING_CONVENTION EnableVertexAttribArray) (unsigned int index); + void (RF_GL_CALLING_CONVENTION GenVertexArrays) (int n, unsigned int* arrays); + void (RF_GL_CALLING_CONVENTION VertexAttrib3f) (unsigned int index, float x, float y, float z); + void (RF_GL_CALLING_CONVENTION DisableVertexAttribArray) (unsigned int index); + void (RF_GL_CALLING_CONVENTION VertexAttrib4f) (unsigned int index, float x, float y, float z, float w); + void (RF_GL_CALLING_CONVENTION VertexAttrib2f) (unsigned int index, float x, float y); + void (RF_GL_CALLING_CONVENTION UseProgram) (unsigned int program); + void (RF_GL_CALLING_CONVENTION Uniform4f) (int location, float v0, float v1, float v2, float v3); + void (RF_GL_CALLING_CONVENTION ActiveTexture) (unsigned int texture); + void (RF_GL_CALLING_CONVENTION Uniform1i) (int location, int v0); + void (RF_GL_CALLING_CONVENTION UniformMatrix4fv) (int location, int count, unsigned char transpose, const float* value); + void (RF_GL_CALLING_CONVENTION DrawElements) (unsigned int mode, int count, unsigned int type, const void* indices); + void (RF_GL_CALLING_CONVENTION DrawArrays) (unsigned int mode, int first, int count); + void (RF_GL_CALLING_CONVENTION PixelStorei) (unsigned int pname, int param); + void (RF_GL_CALLING_CONVENTION GenTextures) (int n, unsigned int* textures); + void (RF_GL_CALLING_CONVENTION TexImage2D) (unsigned int target, int level, int internalformat, int width, int height, int border, unsigned int format, unsigned int type, const void* pixels); + void (RF_GL_CALLING_CONVENTION GenRenderbuffers) (int n, unsigned int* renderbuffers); + void (RF_GL_CALLING_CONVENTION BindRenderbuffer) (unsigned int target, unsigned int renderbuffer); + void (RF_GL_CALLING_CONVENTION RenderbufferStorage) (unsigned int target, unsigned int internalformat, int width, int height); + void (RF_GL_CALLING_CONVENTION CompressedTexImage2D) (unsigned int target, int level, unsigned int internalformat, int width, int height, int border, int imageSize, const void* data); + void (RF_GL_CALLING_CONVENTION TexSubImage2D) (unsigned int target, int level, int txoffset, int yoffset, int width, int height, unsigned int format, unsigned int type, const void* pixels); + void (RF_GL_CALLING_CONVENTION GenerateMipmap) (unsigned int target); + void (RF_GL_CALLING_CONVENTION ReadPixels) (int x, int y, int width, int height, unsigned int format, unsigned int type, void* pixels); + void (RF_GL_CALLING_CONVENTION GenFramebuffers) (int n, unsigned int* framebuffers); + void (RF_GL_CALLING_CONVENTION FramebufferTexture2D) (unsigned int target, unsigned int attachment, unsigned int textarget, unsigned int texture, int level); + void (RF_GL_CALLING_CONVENTION FramebufferRenderbuffer) (unsigned int target, unsigned int attachment, unsigned int renderbuffertarget, unsigned int renderbuffer); + unsigned int (RF_GL_CALLING_CONVENTION CheckFramebufferStatus) (unsigned int target); + unsigned int (RF_GL_CALLING_CONVENTION CreateShader) (unsigned int type); + void (RF_GL_CALLING_CONVENTION ShaderSource) (unsigned int shader, int count, const char** string, const int* length); + void (RF_GL_CALLING_CONVENTION CompileShader) (unsigned int shader); + void (RF_GL_CALLING_CONVENTION GetShaderiv) (unsigned int shader, unsigned int pname, int* params); + void (RF_GL_CALLING_CONVENTION GetShaderInfoLog) (unsigned int shader, int bufSize, int* length, char* infoLog); + unsigned int (RF_GL_CALLING_CONVENTION CreateProgram) (); + void (RF_GL_CALLING_CONVENTION AttachShader) (unsigned int program, unsigned int shader); + void (RF_GL_CALLING_CONVENTION BindAttribLocation) (unsigned int program, unsigned int index, const char* name); + void (RF_GL_CALLING_CONVENTION LinkProgram) (unsigned int program); + void (RF_GL_CALLING_CONVENTION GetProgramiv) (unsigned int program, unsigned int pname, int* params); + void (RF_GL_CALLING_CONVENTION GetProgramInfoLog) (unsigned int program, int bufSize, int* length, char* infoLog); + void (RF_GL_CALLING_CONVENTION DeleteProgram) (unsigned int program); + int (RF_GL_CALLING_CONVENTION GetAttribLocation) (unsigned int program, const char* name); + int (RF_GL_CALLING_CONVENTION GetUniformLocation) (unsigned int program, const char* name); + void (RF_GL_CALLING_CONVENTION DetachShader) (unsigned int program, unsigned int shader); + void (RF_GL_CALLING_CONVENTION DeleteShader) (unsigned int shader); + void (RF_GL_CALLING_CONVENTION GetActiveUniform) (unsigned int program, unsigned int index, int bufSize, int* length, int* size, unsigned int* type, char* name); + void (RF_GL_CALLING_CONVENTION Uniform1f) (int location, float v0); + void (RF_GL_CALLING_CONVENTION Uniform1fv) (int location, int count, const float* value); + void (RF_GL_CALLING_CONVENTION Uniform2fv) (int location, int count, const float* value); + void (RF_GL_CALLING_CONVENTION Uniform3fv) (int location, int count, const float* value); + void (RF_GL_CALLING_CONVENTION Uniform4fv) (int location, int count, const float* value); + void (RF_GL_CALLING_CONVENTION Uniform1iv) (int location, int count, const int* value); + void (RF_GL_CALLING_CONVENTION Uniform2iv) (int location, int count, const int* value); + void (RF_GL_CALLING_CONVENTION Uniform3iv) (int location, int count, const int* value); + void (RF_GL_CALLING_CONVENTION Uniform4iv) (int location, int count, const int* value); + const unsigned char* (RF_GL_CALLING_CONVENTION GetString) (unsigned int name); + void (RF_GL_CALLING_CONVENTION GetFloatv) (unsigned int pname, float* data); + void (RF_GL_CALLING_CONVENTION DepthFunc) (unsigned int func); + void (RF_GL_CALLING_CONVENTION BlendFunc) (unsigned int sfactor, unsigned int dfactor); + void (RF_GL_CALLING_CONVENTION CullFace) (unsigned int mode); + void (RF_GL_CALLING_CONVENTION FrontFace) (unsigned int mode); + const unsigned char* (RF_GL_CALLING_CONVENTION GetStringi) (unsigned int name, unsigned int index); + void (RF_GL_CALLING_CONVENTION GetTexImage) (unsigned int target, int level, unsigned int format, unsigned int type, void* pixels); // OpenGL ES3 ONLY + void (RF_GL_CALLING_CONVENTION ClearDepth) (double depth); // OpenGL 33 ONLY + void (RF_GL_CALLING_CONVENTION ClearDepthf) (float depth); // OpenGL ES3 ONLY + void (RF_GL_CALLING_CONVENTION GetIntegerv) (unsigned int pname, int* data); // OpenGL 33 ONLY + void (RF_GL_CALLING_CONVENTION PolygonMode) (unsigned int face, unsigned int mode); // OpenGL 33 ONLY +} rf_opengl_procs; + +#if defined(__cplusplus) + #define RF__GL_PROC_DEFN(ext, proc) (void*) RF_CONCAT(ext, proc) + #define RF__GL_PROC_NULL(ext, proc) NULL +#else + #define RF__GL_PROC_DEFN(ext, proc) .proc = (void*) RF_CONCAT(ext, proc) + #define RF__GL_PROC_NULL(ext, proc) .proc = NULL +#endif + +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + #define RF__GL_PROC_GL33(ext, proc) RF__GL_PROC_DEFN(ext, proc) + #define RF__GL_PROC_GLES(ext, proc) RF__GL_PROC_NULL(ext, proc) +#else + #define RF__GL_PROC_GL33(ext, proc) RF__GL_PROC_NULL(ext, proc) + #define RF__GL_PROC_GLES(ext, proc) RF__GL_PROC_DEFN(ext, proc) +#endif + +#define RF_DEFAULT_GFX_BACKEND_INIT_DATA (RF_DEFAULT_OPENGL_PROCS_EXT(gl)) +#define RF_DEFAULT_OPENGL_PROCS_EXT(ext) &(rf_opengl_procs) {\ + RF__GL_PROC_DEFN(ext, Viewport),\ + RF__GL_PROC_DEFN(ext, BindTexture),\ + RF__GL_PROC_DEFN(ext, TexParameteri),\ + RF__GL_PROC_DEFN(ext, TexParameterf),\ + RF__GL_PROC_DEFN(ext, TexParameteriv),\ + RF__GL_PROC_DEFN(ext, BindFramebuffer),\ + RF__GL_PROC_DEFN(ext, Enable),\ + RF__GL_PROC_DEFN(ext, Disable),\ + RF__GL_PROC_DEFN(ext, Scissor),\ + RF__GL_PROC_DEFN(ext, DeleteTextures),\ + RF__GL_PROC_DEFN(ext, DeleteRenderbuffers),\ + RF__GL_PROC_DEFN(ext, DeleteFramebuffers),\ + RF__GL_PROC_DEFN(ext, DeleteVertexArrays),\ + RF__GL_PROC_DEFN(ext, DeleteBuffers),\ + RF__GL_PROC_DEFN(ext, ClearColor),\ + RF__GL_PROC_DEFN(ext, Clear),\ + RF__GL_PROC_DEFN(ext, BindBuffer),\ + RF__GL_PROC_DEFN(ext, BufferSubData),\ + RF__GL_PROC_DEFN(ext, BindVertexArray),\ + RF__GL_PROC_DEFN(ext, GenBuffers),\ + RF__GL_PROC_DEFN(ext, BufferData),\ + RF__GL_PROC_DEFN(ext, VertexAttribPointer),\ + RF__GL_PROC_DEFN(ext, EnableVertexAttribArray),\ + RF__GL_PROC_DEFN(ext, GenVertexArrays),\ + RF__GL_PROC_DEFN(ext, VertexAttrib3f),\ + RF__GL_PROC_DEFN(ext, DisableVertexAttribArray),\ + RF__GL_PROC_DEFN(ext, VertexAttrib4f),\ + RF__GL_PROC_DEFN(ext, VertexAttrib2f),\ + RF__GL_PROC_DEFN(ext, UseProgram),\ + RF__GL_PROC_DEFN(ext, Uniform4f),\ + RF__GL_PROC_DEFN(ext, ActiveTexture),\ + RF__GL_PROC_DEFN(ext, Uniform1i),\ + RF__GL_PROC_DEFN(ext, UniformMatrix4fv),\ + RF__GL_PROC_DEFN(ext, DrawElements),\ + RF__GL_PROC_DEFN(ext, DrawArrays),\ + RF__GL_PROC_DEFN(ext, PixelStorei),\ + RF__GL_PROC_DEFN(ext, GenTextures),\ + RF__GL_PROC_DEFN(ext, TexImage2D),\ + RF__GL_PROC_DEFN(ext, GenRenderbuffers),\ + RF__GL_PROC_DEFN(ext, BindRenderbuffer),\ + RF__GL_PROC_DEFN(ext, RenderbufferStorage),\ + RF__GL_PROC_DEFN(ext, CompressedTexImage2D),\ + RF__GL_PROC_DEFN(ext, TexSubImage2D),\ + RF__GL_PROC_DEFN(ext, GenerateMipmap),\ + RF__GL_PROC_DEFN(ext, ReadPixels),\ + RF__GL_PROC_DEFN(ext, GenFramebuffers),\ + RF__GL_PROC_DEFN(ext, FramebufferTexture2D),\ + RF__GL_PROC_DEFN(ext, FramebufferRenderbuffer),\ + RF__GL_PROC_DEFN(ext, CheckFramebufferStatus),\ + RF__GL_PROC_DEFN(ext, CreateShader),\ + RF__GL_PROC_DEFN(ext, ShaderSource),\ + RF__GL_PROC_DEFN(ext, CompileShader),\ + RF__GL_PROC_DEFN(ext, GetShaderiv),\ + RF__GL_PROC_DEFN(ext, GetShaderInfoLog),\ + RF__GL_PROC_DEFN(ext, CreateProgram),\ + RF__GL_PROC_DEFN(ext, AttachShader),\ + RF__GL_PROC_DEFN(ext, BindAttribLocation),\ + RF__GL_PROC_DEFN(ext, LinkProgram),\ + RF__GL_PROC_DEFN(ext, GetProgramiv),\ + RF__GL_PROC_DEFN(ext, GetProgramInfoLog),\ + RF__GL_PROC_DEFN(ext, DeleteProgram),\ + RF__GL_PROC_DEFN(ext, GetAttribLocation),\ + RF__GL_PROC_DEFN(ext, GetUniformLocation),\ + RF__GL_PROC_DEFN(ext, DetachShader),\ + RF__GL_PROC_DEFN(ext, DeleteShader),\ + RF__GL_PROC_DEFN(ext, GetActiveUniform),\ + RF__GL_PROC_DEFN(ext, Uniform1f),\ + RF__GL_PROC_DEFN(ext, Uniform1fv),\ + RF__GL_PROC_DEFN(ext, Uniform2fv),\ + RF__GL_PROC_DEFN(ext, Uniform3fv),\ + RF__GL_PROC_DEFN(ext, Uniform4fv),\ + RF__GL_PROC_DEFN(ext, Uniform1iv),\ + RF__GL_PROC_DEFN(ext, Uniform2iv),\ + RF__GL_PROC_DEFN(ext, Uniform3iv),\ + RF__GL_PROC_DEFN(ext, Uniform4iv),\ + RF__GL_PROC_DEFN(ext, GetString),\ + RF__GL_PROC_DEFN(ext, GetFloatv),\ + RF__GL_PROC_DEFN(ext, DepthFunc),\ + RF__GL_PROC_DEFN(ext, BlendFunc),\ + RF__GL_PROC_DEFN(ext, CullFace),\ + RF__GL_PROC_DEFN(ext, FrontFace),\ + RF__GL_PROC_DEFN(ext, GetStringi),\ + RF__GL_PROC_GL33(ext, GetTexImage), /* OpenGL 33 ONLY */ \ + RF__GL_PROC_GL33(ext, ClearDepth), /* OpenGL 33 ONLY */ \ + RF__GL_PROC_GLES(ext, ClearDepthf), /* OpenGL ES3 ONLY */ \ + RF__GL_PROC_GL33(ext, GetIntegerv), /* OpenGL 33 ONLY */ \ + RF__GL_PROC_GL33(ext, PolygonMode), /* OpenGL 33 ONLY */ \ +} + +typedef float rf_gfx_vertex_data_type; +typedef float rf_gfx_texcoord_data_type; +typedef unsigned char rf_gfx_color_data_type; +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) +typedef unsigned int rf_gfx_vertex_index_data_type; +#else +typedef unsigned short rf_gfx_vertex_index_data_type; +#endif + +#define RF_GFX_VERTEX_COMPONENT_COUNT (3 * 4) // 3 float by vertex, 4 vertex by quad +#define RF_GFX_TEXCOORD_COMPONENT_COUNT (2 * 4) // 2 float by texcoord, 4 texcoord by quad +#define RF_GFX_COLOR_COMPONENT_COUNT (4 * 4) // 4 float by color, 4 colors by quad +#define RF_GFX_VERTEX_INDEX_COMPONENT_COUNT (6) // 6 int by quad (indices) + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct rf_vertex_buffer +{ + int elements_count; // Number of elements in the buffer (QUADS) + int v_counter; // Vertex position counter to process (and draw) from full buffer + int tc_counter; // Vertex texcoord counter to process (and draw) from full buffer + int c_counter; // Vertex color counter to process (and draw) from full buffer + + unsigned int vao_id; // OpenGL Vertex Array Object id + unsigned int vbo_id[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data) + + rf_gfx_vertex_data_type* vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + rf_gfx_texcoord_data_type* texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + rf_gfx_color_data_type* colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + rf_gfx_vertex_index_data_type* indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +} rf_vertex_buffer; + +typedef struct rf_draw_call +{ + rf_drawing_mode mode; // Drawing mode: RF_LINES, RF_TRIANGLES, RF_QUADS + int vertex_count; // Number of vertex of the draw + int vertex_alignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vao_id; // Vertex array id to be used on the draw + //unsigned int shaderId; // rf_shader id to be used on the draw + unsigned int texture_id; // rf_texture id to be used on the draw + // TODO: Support additional texture units? + + //rf_mat projection; // Projection matrix for this draw + //rf_mat modelview; // Modelview matrix for this draw +} rf_draw_call; + +typedef struct rf_gfx_context +{ + rf_opengl_procs gl; + + struct { + bool tex_comp_dxt_supported; // DDS texture compression support + bool tex_comp_etc1_supported; // ETC1 texture compression support + bool tex_comp_etc2_supported; // ETC2/EAC texture compression support + bool tex_comp_pvrt_supported; // PVR texture compression support + bool tex_comp_astc_supported; // ASTC texture compression support + bool tex_npot_supported; // NPOT textures full support + bool tex_float_supported; // float textures support (32 bit per channel) + bool tex_depth_supported; // Depth textures supported + int max_depth_bits; // Maximum bits for depth component + bool tex_mirror_clamp_supported; // Clamp mirror wrap mode supported + bool tex_anisotropic_filter_supported; // Anisotropic texture filtering support + float max_anisotropic_level; // Maximum anisotropy level supported (minimum is 2.0f) + bool debug_marker_supported; // Debug marker support + } extensions; +} rf_gfx_context; + +#endif // !defined(RAYFORK_GFX_BACKEND_OPENGL_H) && (defined(RAYFORK_GRAPHICS_BACKEND_GL_33) || defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3)) +/*** End of inlined file: rayfork-gfx-backend-opengl.h ***/ + + +/*** Start of inlined file: rayfork-render-batch.h ***/ +#ifndef RAYFORK_GFX_H +#define RAYFORK_GFX_H + + +/*** Start of inlined file: rayfork-gfx-backend-opengl.h ***/ +#if !defined(RAYFORK_GFX_BACKEND_OPENGL_H) && (defined(RAYFORK_GRAPHICS_BACKEND_GL_33) || defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3)) +#define RAYFORK_GFX_BACKEND_OPENGL_H + +#if !defined(RF_GL_CALLING_CONVENTION) && (defined(RAYFORK_PLATFORM_WINDOWS) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)) + #define RF_GL_CALLING_CONVENTION __stdcall * +#else + #define RF_GL_CALLING_CONVENTION * +#endif + +typedef struct rf_opengl_procs +{ + void (RF_GL_CALLING_CONVENTION Viewport) (int x, int y, int width, int height); + void (RF_GL_CALLING_CONVENTION BindTexture) (unsigned int target, unsigned int texture); + void (RF_GL_CALLING_CONVENTION TexParameteri) (unsigned int target, unsigned int pname, int param); + void (RF_GL_CALLING_CONVENTION TexParameterf) (unsigned int target, unsigned int pname, float param); + void (RF_GL_CALLING_CONVENTION TexParameteriv) (unsigned int target, unsigned int pname, const int* params); + void (RF_GL_CALLING_CONVENTION BindFramebuffer) (unsigned int target, unsigned int framebuffer); + void (RF_GL_CALLING_CONVENTION Enable) (unsigned int cap); + void (RF_GL_CALLING_CONVENTION Disable) (unsigned int cap); + void (RF_GL_CALLING_CONVENTION Scissor) (int x, int y, int width, int height); + void (RF_GL_CALLING_CONVENTION DeleteTextures) (int n, const unsigned int* textures); + void (RF_GL_CALLING_CONVENTION DeleteRenderbuffers) (int n, const unsigned int* renderbuffers); + void (RF_GL_CALLING_CONVENTION DeleteFramebuffers) (int n, const unsigned int* framebuffers); + void (RF_GL_CALLING_CONVENTION DeleteVertexArrays) (int n, const unsigned int* arrays); + void (RF_GL_CALLING_CONVENTION DeleteBuffers) (int n, const unsigned int* buffers); + void (RF_GL_CALLING_CONVENTION ClearColor) (float red, float green, float blue, float alpha); + void (RF_GL_CALLING_CONVENTION Clear) (unsigned int mask); + void (RF_GL_CALLING_CONVENTION BindBuffer) (unsigned int target, unsigned int buffer); + void (RF_GL_CALLING_CONVENTION BufferSubData) (unsigned int target, ptrdiff_t offset, ptrdiff_t size, const void* data); + void (RF_GL_CALLING_CONVENTION BindVertexArray) (unsigned int array); + void (RF_GL_CALLING_CONVENTION GenBuffers) (int n, unsigned int* buffers); + void (RF_GL_CALLING_CONVENTION BufferData) (unsigned int target, ptrdiff_t size, const void* data, unsigned int usage); + void (RF_GL_CALLING_CONVENTION VertexAttribPointer) (unsigned int index, int size, unsigned int type, unsigned char normalized, int stride, const void* pointer); + void (RF_GL_CALLING_CONVENTION EnableVertexAttribArray) (unsigned int index); + void (RF_GL_CALLING_CONVENTION GenVertexArrays) (int n, unsigned int* arrays); + void (RF_GL_CALLING_CONVENTION VertexAttrib3f) (unsigned int index, float x, float y, float z); + void (RF_GL_CALLING_CONVENTION DisableVertexAttribArray) (unsigned int index); + void (RF_GL_CALLING_CONVENTION VertexAttrib4f) (unsigned int index, float x, float y, float z, float w); + void (RF_GL_CALLING_CONVENTION VertexAttrib2f) (unsigned int index, float x, float y); + void (RF_GL_CALLING_CONVENTION UseProgram) (unsigned int program); + void (RF_GL_CALLING_CONVENTION Uniform4f) (int location, float v0, float v1, float v2, float v3); + void (RF_GL_CALLING_CONVENTION ActiveTexture) (unsigned int texture); + void (RF_GL_CALLING_CONVENTION Uniform1i) (int location, int v0); + void (RF_GL_CALLING_CONVENTION UniformMatrix4fv) (int location, int count, unsigned char transpose, const float* value); + void (RF_GL_CALLING_CONVENTION DrawElements) (unsigned int mode, int count, unsigned int type, const void* indices); + void (RF_GL_CALLING_CONVENTION DrawArrays) (unsigned int mode, int first, int count); + void (RF_GL_CALLING_CONVENTION PixelStorei) (unsigned int pname, int param); + void (RF_GL_CALLING_CONVENTION GenTextures) (int n, unsigned int* textures); + void (RF_GL_CALLING_CONVENTION TexImage2D) (unsigned int target, int level, int internalformat, int width, int height, int border, unsigned int format, unsigned int type, const void* pixels); + void (RF_GL_CALLING_CONVENTION GenRenderbuffers) (int n, unsigned int* renderbuffers); + void (RF_GL_CALLING_CONVENTION BindRenderbuffer) (unsigned int target, unsigned int renderbuffer); + void (RF_GL_CALLING_CONVENTION RenderbufferStorage) (unsigned int target, unsigned int internalformat, int width, int height); + void (RF_GL_CALLING_CONVENTION CompressedTexImage2D) (unsigned int target, int level, unsigned int internalformat, int width, int height, int border, int imageSize, const void* data); + void (RF_GL_CALLING_CONVENTION TexSubImage2D) (unsigned int target, int level, int txoffset, int yoffset, int width, int height, unsigned int format, unsigned int type, const void* pixels); + void (RF_GL_CALLING_CONVENTION GenerateMipmap) (unsigned int target); + void (RF_GL_CALLING_CONVENTION ReadPixels) (int x, int y, int width, int height, unsigned int format, unsigned int type, void* pixels); + void (RF_GL_CALLING_CONVENTION GenFramebuffers) (int n, unsigned int* framebuffers); + void (RF_GL_CALLING_CONVENTION FramebufferTexture2D) (unsigned int target, unsigned int attachment, unsigned int textarget, unsigned int texture, int level); + void (RF_GL_CALLING_CONVENTION FramebufferRenderbuffer) (unsigned int target, unsigned int attachment, unsigned int renderbuffertarget, unsigned int renderbuffer); + unsigned int (RF_GL_CALLING_CONVENTION CheckFramebufferStatus) (unsigned int target); + unsigned int (RF_GL_CALLING_CONVENTION CreateShader) (unsigned int type); + void (RF_GL_CALLING_CONVENTION ShaderSource) (unsigned int shader, int count, const char** string, const int* length); + void (RF_GL_CALLING_CONVENTION CompileShader) (unsigned int shader); + void (RF_GL_CALLING_CONVENTION GetShaderiv) (unsigned int shader, unsigned int pname, int* params); + void (RF_GL_CALLING_CONVENTION GetShaderInfoLog) (unsigned int shader, int bufSize, int* length, char* infoLog); + unsigned int (RF_GL_CALLING_CONVENTION CreateProgram) (); + void (RF_GL_CALLING_CONVENTION AttachShader) (unsigned int program, unsigned int shader); + void (RF_GL_CALLING_CONVENTION BindAttribLocation) (unsigned int program, unsigned int index, const char* name); + void (RF_GL_CALLING_CONVENTION LinkProgram) (unsigned int program); + void (RF_GL_CALLING_CONVENTION GetProgramiv) (unsigned int program, unsigned int pname, int* params); + void (RF_GL_CALLING_CONVENTION GetProgramInfoLog) (unsigned int program, int bufSize, int* length, char* infoLog); + void (RF_GL_CALLING_CONVENTION DeleteProgram) (unsigned int program); + int (RF_GL_CALLING_CONVENTION GetAttribLocation) (unsigned int program, const char* name); + int (RF_GL_CALLING_CONVENTION GetUniformLocation) (unsigned int program, const char* name); + void (RF_GL_CALLING_CONVENTION DetachShader) (unsigned int program, unsigned int shader); + void (RF_GL_CALLING_CONVENTION DeleteShader) (unsigned int shader); + void (RF_GL_CALLING_CONVENTION GetActiveUniform) (unsigned int program, unsigned int index, int bufSize, int* length, int* size, unsigned int* type, char* name); + void (RF_GL_CALLING_CONVENTION Uniform1f) (int location, float v0); + void (RF_GL_CALLING_CONVENTION Uniform1fv) (int location, int count, const float* value); + void (RF_GL_CALLING_CONVENTION Uniform2fv) (int location, int count, const float* value); + void (RF_GL_CALLING_CONVENTION Uniform3fv) (int location, int count, const float* value); + void (RF_GL_CALLING_CONVENTION Uniform4fv) (int location, int count, const float* value); + void (RF_GL_CALLING_CONVENTION Uniform1iv) (int location, int count, const int* value); + void (RF_GL_CALLING_CONVENTION Uniform2iv) (int location, int count, const int* value); + void (RF_GL_CALLING_CONVENTION Uniform3iv) (int location, int count, const int* value); + void (RF_GL_CALLING_CONVENTION Uniform4iv) (int location, int count, const int* value); + const unsigned char* (RF_GL_CALLING_CONVENTION GetString) (unsigned int name); + void (RF_GL_CALLING_CONVENTION GetFloatv) (unsigned int pname, float* data); + void (RF_GL_CALLING_CONVENTION DepthFunc) (unsigned int func); + void (RF_GL_CALLING_CONVENTION BlendFunc) (unsigned int sfactor, unsigned int dfactor); + void (RF_GL_CALLING_CONVENTION CullFace) (unsigned int mode); + void (RF_GL_CALLING_CONVENTION FrontFace) (unsigned int mode); + const unsigned char* (RF_GL_CALLING_CONVENTION GetStringi) (unsigned int name, unsigned int index); + void (RF_GL_CALLING_CONVENTION GetTexImage) (unsigned int target, int level, unsigned int format, unsigned int type, void* pixels); // OpenGL ES3 ONLY + void (RF_GL_CALLING_CONVENTION ClearDepth) (double depth); // OpenGL 33 ONLY + void (RF_GL_CALLING_CONVENTION ClearDepthf) (float depth); // OpenGL ES3 ONLY + void (RF_GL_CALLING_CONVENTION GetIntegerv) (unsigned int pname, int* data); // OpenGL 33 ONLY + void (RF_GL_CALLING_CONVENTION PolygonMode) (unsigned int face, unsigned int mode); // OpenGL 33 ONLY +} rf_opengl_procs; + +#if defined(__cplusplus) + #define RF__GL_PROC_DEFN(ext, proc) (void*) RF_CONCAT(ext, proc) + #define RF__GL_PROC_NULL(ext, proc) NULL +#else + #define RF__GL_PROC_DEFN(ext, proc) .proc = (void*) RF_CONCAT(ext, proc) + #define RF__GL_PROC_NULL(ext, proc) .proc = NULL +#endif + +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) + #define RF__GL_PROC_GL33(ext, proc) RF__GL_PROC_DEFN(ext, proc) + #define RF__GL_PROC_GLES(ext, proc) RF__GL_PROC_NULL(ext, proc) +#else + #define RF__GL_PROC_GL33(ext, proc) RF__GL_PROC_NULL(ext, proc) + #define RF__GL_PROC_GLES(ext, proc) RF__GL_PROC_DEFN(ext, proc) +#endif + +#define RF_DEFAULT_GFX_BACKEND_INIT_DATA (RF_DEFAULT_OPENGL_PROCS_EXT(gl)) +#define RF_DEFAULT_OPENGL_PROCS_EXT(ext) &(rf_opengl_procs) {\ + RF__GL_PROC_DEFN(ext, Viewport),\ + RF__GL_PROC_DEFN(ext, BindTexture),\ + RF__GL_PROC_DEFN(ext, TexParameteri),\ + RF__GL_PROC_DEFN(ext, TexParameterf),\ + RF__GL_PROC_DEFN(ext, TexParameteriv),\ + RF__GL_PROC_DEFN(ext, BindFramebuffer),\ + RF__GL_PROC_DEFN(ext, Enable),\ + RF__GL_PROC_DEFN(ext, Disable),\ + RF__GL_PROC_DEFN(ext, Scissor),\ + RF__GL_PROC_DEFN(ext, DeleteTextures),\ + RF__GL_PROC_DEFN(ext, DeleteRenderbuffers),\ + RF__GL_PROC_DEFN(ext, DeleteFramebuffers),\ + RF__GL_PROC_DEFN(ext, DeleteVertexArrays),\ + RF__GL_PROC_DEFN(ext, DeleteBuffers),\ + RF__GL_PROC_DEFN(ext, ClearColor),\ + RF__GL_PROC_DEFN(ext, Clear),\ + RF__GL_PROC_DEFN(ext, BindBuffer),\ + RF__GL_PROC_DEFN(ext, BufferSubData),\ + RF__GL_PROC_DEFN(ext, BindVertexArray),\ + RF__GL_PROC_DEFN(ext, GenBuffers),\ + RF__GL_PROC_DEFN(ext, BufferData),\ + RF__GL_PROC_DEFN(ext, VertexAttribPointer),\ + RF__GL_PROC_DEFN(ext, EnableVertexAttribArray),\ + RF__GL_PROC_DEFN(ext, GenVertexArrays),\ + RF__GL_PROC_DEFN(ext, VertexAttrib3f),\ + RF__GL_PROC_DEFN(ext, DisableVertexAttribArray),\ + RF__GL_PROC_DEFN(ext, VertexAttrib4f),\ + RF__GL_PROC_DEFN(ext, VertexAttrib2f),\ + RF__GL_PROC_DEFN(ext, UseProgram),\ + RF__GL_PROC_DEFN(ext, Uniform4f),\ + RF__GL_PROC_DEFN(ext, ActiveTexture),\ + RF__GL_PROC_DEFN(ext, Uniform1i),\ + RF__GL_PROC_DEFN(ext, UniformMatrix4fv),\ + RF__GL_PROC_DEFN(ext, DrawElements),\ + RF__GL_PROC_DEFN(ext, DrawArrays),\ + RF__GL_PROC_DEFN(ext, PixelStorei),\ + RF__GL_PROC_DEFN(ext, GenTextures),\ + RF__GL_PROC_DEFN(ext, TexImage2D),\ + RF__GL_PROC_DEFN(ext, GenRenderbuffers),\ + RF__GL_PROC_DEFN(ext, BindRenderbuffer),\ + RF__GL_PROC_DEFN(ext, RenderbufferStorage),\ + RF__GL_PROC_DEFN(ext, CompressedTexImage2D),\ + RF__GL_PROC_DEFN(ext, TexSubImage2D),\ + RF__GL_PROC_DEFN(ext, GenerateMipmap),\ + RF__GL_PROC_DEFN(ext, ReadPixels),\ + RF__GL_PROC_DEFN(ext, GenFramebuffers),\ + RF__GL_PROC_DEFN(ext, FramebufferTexture2D),\ + RF__GL_PROC_DEFN(ext, FramebufferRenderbuffer),\ + RF__GL_PROC_DEFN(ext, CheckFramebufferStatus),\ + RF__GL_PROC_DEFN(ext, CreateShader),\ + RF__GL_PROC_DEFN(ext, ShaderSource),\ + RF__GL_PROC_DEFN(ext, CompileShader),\ + RF__GL_PROC_DEFN(ext, GetShaderiv),\ + RF__GL_PROC_DEFN(ext, GetShaderInfoLog),\ + RF__GL_PROC_DEFN(ext, CreateProgram),\ + RF__GL_PROC_DEFN(ext, AttachShader),\ + RF__GL_PROC_DEFN(ext, BindAttribLocation),\ + RF__GL_PROC_DEFN(ext, LinkProgram),\ + RF__GL_PROC_DEFN(ext, GetProgramiv),\ + RF__GL_PROC_DEFN(ext, GetProgramInfoLog),\ + RF__GL_PROC_DEFN(ext, DeleteProgram),\ + RF__GL_PROC_DEFN(ext, GetAttribLocation),\ + RF__GL_PROC_DEFN(ext, GetUniformLocation),\ + RF__GL_PROC_DEFN(ext, DetachShader),\ + RF__GL_PROC_DEFN(ext, DeleteShader),\ + RF__GL_PROC_DEFN(ext, GetActiveUniform),\ + RF__GL_PROC_DEFN(ext, Uniform1f),\ + RF__GL_PROC_DEFN(ext, Uniform1fv),\ + RF__GL_PROC_DEFN(ext, Uniform2fv),\ + RF__GL_PROC_DEFN(ext, Uniform3fv),\ + RF__GL_PROC_DEFN(ext, Uniform4fv),\ + RF__GL_PROC_DEFN(ext, Uniform1iv),\ + RF__GL_PROC_DEFN(ext, Uniform2iv),\ + RF__GL_PROC_DEFN(ext, Uniform3iv),\ + RF__GL_PROC_DEFN(ext, Uniform4iv),\ + RF__GL_PROC_DEFN(ext, GetString),\ + RF__GL_PROC_DEFN(ext, GetFloatv),\ + RF__GL_PROC_DEFN(ext, DepthFunc),\ + RF__GL_PROC_DEFN(ext, BlendFunc),\ + RF__GL_PROC_DEFN(ext, CullFace),\ + RF__GL_PROC_DEFN(ext, FrontFace),\ + RF__GL_PROC_DEFN(ext, GetStringi),\ + RF__GL_PROC_GL33(ext, GetTexImage), /* OpenGL 33 ONLY */ \ + RF__GL_PROC_GL33(ext, ClearDepth), /* OpenGL 33 ONLY */ \ + RF__GL_PROC_GLES(ext, ClearDepthf), /* OpenGL ES3 ONLY */ \ + RF__GL_PROC_GL33(ext, GetIntegerv), /* OpenGL 33 ONLY */ \ + RF__GL_PROC_GL33(ext, PolygonMode), /* OpenGL 33 ONLY */ \ +} + +typedef float rf_gfx_vertex_data_type; +typedef float rf_gfx_texcoord_data_type; +typedef unsigned char rf_gfx_color_data_type; +#if defined(RAYFORK_GRAPHICS_BACKEND_GL_33) +typedef unsigned int rf_gfx_vertex_index_data_type; +#else +typedef unsigned short rf_gfx_vertex_index_data_type; +#endif + +#define RF_GFX_VERTEX_COMPONENT_COUNT (3 * 4) // 3 float by vertex, 4 vertex by quad +#define RF_GFX_TEXCOORD_COMPONENT_COUNT (2 * 4) // 2 float by texcoord, 4 texcoord by quad +#define RF_GFX_COLOR_COMPONENT_COUNT (4 * 4) // 4 float by color, 4 colors by quad +#define RF_GFX_VERTEX_INDEX_COMPONENT_COUNT (6) // 6 int by quad (indices) + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct rf_vertex_buffer +{ + int elements_count; // Number of elements in the buffer (QUADS) + int v_counter; // Vertex position counter to process (and draw) from full buffer + int tc_counter; // Vertex texcoord counter to process (and draw) from full buffer + int c_counter; // Vertex color counter to process (and draw) from full buffer + + unsigned int vao_id; // OpenGL Vertex Array Object id + unsigned int vbo_id[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data) + + rf_gfx_vertex_data_type* vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + rf_gfx_texcoord_data_type* texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + rf_gfx_color_data_type* colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + rf_gfx_vertex_index_data_type* indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +} rf_vertex_buffer; + +typedef struct rf_draw_call +{ + rf_drawing_mode mode; // Drawing mode: RF_LINES, RF_TRIANGLES, RF_QUADS + int vertex_count; // Number of vertex of the draw + int vertex_alignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vao_id; // Vertex array id to be used on the draw + //unsigned int shaderId; // rf_shader id to be used on the draw + unsigned int texture_id; // rf_texture id to be used on the draw + // TODO: Support additional texture units? + + //rf_mat projection; // Projection matrix for this draw + //rf_mat modelview; // Modelview matrix for this draw +} rf_draw_call; + +typedef struct rf_gfx_context +{ + rf_opengl_procs gl; + + struct { + bool tex_comp_dxt_supported; // DDS texture compression support + bool tex_comp_etc1_supported; // ETC1 texture compression support + bool tex_comp_etc2_supported; // ETC2/EAC texture compression support + bool tex_comp_pvrt_supported; // PVR texture compression support + bool tex_comp_astc_supported; // ASTC texture compression support + bool tex_npot_supported; // NPOT textures full support + bool tex_float_supported; // float textures support (32 bit per channel) + bool tex_depth_supported; // Depth textures supported + int max_depth_bits; // Maximum bits for depth component + bool tex_mirror_clamp_supported; // Clamp mirror wrap mode supported + bool tex_anisotropic_filter_supported; // Anisotropic texture filtering support + float max_anisotropic_level; // Maximum anisotropy level supported (minimum is 2.0f) + bool debug_marker_supported; // Debug marker support + } extensions; +} rf_gfx_context; + +#endif // !defined(RAYFORK_GFX_BACKEND_OPENGL_H) && (defined(RAYFORK_GRAPHICS_BACKEND_GL_33) || defined(RAYFORK_GRAPHICS_BACKEND_GL_ES3)) +/*** End of inlined file: rayfork-gfx-backend-opengl.h ***/ + +typedef struct rf_render_batch +{ + rf_int vertex_buffers_count; + rf_int current_buffer; + rf_vertex_buffer* vertex_buffers; + + rf_int draw_calls_size; + rf_int draw_calls_counter; + rf_draw_call* draw_calls; + float current_depth; // Current depth value for next draw + + bool valid; +} rf_render_batch; + +typedef struct rf_one_element_vertex_buffer +{ + rf_gfx_vertex_data_type vertices [1 * RF_GFX_VERTEX_COMPONENT_COUNT ]; + rf_gfx_texcoord_data_type texcoords [1 * RF_GFX_TEXCOORD_COMPONENT_COUNT ]; + rf_gfx_color_data_type colors [1 * RF_GFX_COLOR_COMPONENT_COUNT ]; + rf_gfx_vertex_index_data_type indices [1 * RF_GFX_VERTEX_INDEX_COMPONENT_COUNT]; +} rf_one_element_vertex_buffer; + +typedef struct rf_default_vertex_buffer +{ + rf_gfx_vertex_data_type vertices [RF_DEFAULT_BATCH_ELEMENTS_COUNT * RF_GFX_VERTEX_COMPONENT_COUNT ]; + rf_gfx_texcoord_data_type texcoords [RF_DEFAULT_BATCH_ELEMENTS_COUNT * RF_GFX_TEXCOORD_COMPONENT_COUNT ]; + rf_gfx_color_data_type colors [RF_DEFAULT_BATCH_ELEMENTS_COUNT * RF_GFX_COLOR_COMPONENT_COUNT ]; + rf_gfx_vertex_index_data_type indices [RF_DEFAULT_BATCH_ELEMENTS_COUNT * RF_GFX_VERTEX_INDEX_COMPONENT_COUNT]; +} rf_default_vertex_buffer; + +typedef struct rf_default_render_batch +{ + rf_vertex_buffer vertex_buffers [RF_DEFAULT_BATCH_VERTEX_BUFFERS_COUNT]; + rf_draw_call draw_calls [RF_DEFAULT_BATCH_DRAW_CALLS_COUNT ]; + rf_default_vertex_buffer vertex_buffers_memory [RF_DEFAULT_BATCH_VERTEX_BUFFERS_COUNT]; +} rf_default_render_batch; + +RF_API rf_render_batch rf_create_custom_render_batch_from_buffers(rf_vertex_buffer* vertex_buffers, rf_int vertex_buffers_count, rf_draw_call* draw_calls, rf_int draw_calls_count); +RF_API rf_render_batch rf_create_custom_render_batch(rf_int vertex_buffers_count, rf_int draw_calls_count, rf_int vertex_buffer_elements_count, rf_allocator allocator); +RF_API rf_render_batch rf_create_default_render_batch(rf_allocator allocator); + +RF_API void rf_set_active_render_batch(rf_render_batch* batch); +RF_API void rf_unload_render_batch(rf_render_batch batch, rf_allocator allocator); + +#endif // RAYFORK_GFX_H +/*** End of inlined file: rayfork-render-batch.h ***/ + + +/*** Start of inlined file: rayfork-texture.h ***/ +#ifndef RAYFORK_TEXTURE_H +#define RAYFORK_TEXTURE_H + +RF_API rf_texture2d rf_load_texture_from_file(const char* filename, rf_allocator temp_allocator, rf_io_callbacks io); // Load texture from file into GPU memory (VRAM) +RF_API rf_texture2d rf_load_texture_from_file_data(const void* data, rf_int dst_size, rf_allocator temp_allocator); // Load texture from an image file data using stb +RF_API rf_texture2d rf_load_texture_from_image(rf_image image); // Load texture from image data +RF_API rf_texture2d rf_load_texture_from_image_with_mipmaps(rf_mipmaps_image image); // Load texture from image data +RF_API rf_texture_cubemap rf_load_texture_cubemap_from_image(rf_image image, rf_cubemap_layout_type layout_type, rf_allocator temp_allocator); // Load cubemap from image, multiple image cubemap layouts supported +RF_API rf_render_texture2d rf_load_render_texture(int width, int height); // Load texture for rendering (framebuffer) + +RF_API void rf_update_texture(rf_texture2d texture, const void* pixels, rf_int pixels_size); // Update GPU texture with new data. Pixels data must match texture.format +RF_API void rf_gen_texture_mipmaps(rf_texture2d* texture); // Generate GPU mipmaps for a texture +RF_API void rf_set_texture_filter(rf_texture2d texture, rf_texture_filter_mode filter_mode); // Set texture scaling filter mode +RF_API void rf_set_texture_wrap(rf_texture2d texture, rf_texture_wrap_mode wrap_mode); // Set texture wrapping mode +RF_API void rf_unload_texture(rf_texture2d texture); // Unload texture from GPU memory (VRAM) +RF_API void rf_unload_render_texture(rf_render_texture2d target); // Unload render texture from GPU memory (VRAM) + +RF_API rf_texture2d rf_gen_texture_cubemap(rf_shader shader, rf_texture2d sky_hdr, rf_int size); // Generate cubemap texture from HDR texture +RF_API rf_texture2d rf_gen_texture_irradiance(rf_shader shader, rf_texture2d cubemap, rf_int size); // Generate irradiance texture using cubemap data +RF_API rf_texture2d rf_gen_texture_prefilter(rf_shader shader, rf_texture2d cubemap, rf_int size); // Generate prefilter texture using cubemap data +RF_API rf_texture2d rf_gen_texture_brdf(rf_shader shader, rf_int size); // Generate BRDF texture using cubemap data. + +#endif // RAYFORK_TEXTURE_H +/*** End of inlined file: rayfork-texture.h ***/ + + +/*** Start of inlined file: rayfork-font.h ***/ +#ifndef RAYFORK_FONT_H +#define RAYFORK_FONT_H + +#define RF_SDF_CHAR_PADDING (4) +#define RF_SDF_ON_EDGE_VALUE (128) +#define RF_SDF_PIXEL_DIST_SCALE (64.0f) + +#define RF_BITMAP_ALPHA_THRESHOLD (80) +#define RF_DEFAULT_FONT_SIZE (64) + +#define RF_BUILTIN_FONT_CHARS { ' ','!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/','0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?','@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_','`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','{','|','}','~', } +#define RF_BUILTIN_FONT_FIRST_CHAR (32) +#define RF_BUILTIN_FONT_LAST_CHAR (126) +#define RF_BUILTIN_CODEPOINTS_COUNT (96) // ASCII 32 up to 126 is 96 glyphs (note that the range is inclusive) +#define RF_BUILTIN_FONT_PADDING (2) + +#define RF_GLYPH_NOT_FOUND (-1) + +#define RF_BUILTIN_FONT_CHARS_COUNT (224) // Number of characters in the raylib font + +typedef enum rf_font_antialias +{ + RF_FONT_ANTIALIAS = 0, // Default font generation, anti-aliased + RF_FONT_NO_ANTIALIAS, // Bitmap font generation, no anti-aliasing +} rf_font_antialias; + +typedef struct rf_glyph_info +{ + union + { + rf_rec rec; // Characters rectangles in texture + struct { float x, y, width, height; }; + }; + + int codepoint; // Character value (Unicode) + int offset_x; // Character offset X when drawing + int offset_y; // Character offset Y when drawing + int advance_x; // Character advance position X +} rf_glyph_info; + +typedef struct rf_ttf_font_info +{ + // Font details + const void* ttf_data; + int font_size; + int largest_glyph_size; + + // Font metrics + float scale_factor; + int ascent; + int descent; + int line_gap; + + // Take directly from stb_truetype because we don't want to include it's header in our public API + struct + { + void* userdata; + unsigned char* data; + int fontstart; + int numGlyphs; + int loca, head, glyf, hhea, hmtx, kern, gpos, svg; + int index_map; + int indexToLocFormat; + + struct + { + unsigned char* data; + int cursor; + int size; + } cff, charstrings, gsubrs, subrs, fontdicts, fdselect; + } internal_stb_font_info; + + bool valid; +} rf_ttf_font_info; + +typedef struct rf_font +{ + int base_size; + rf_texture2d texture; + rf_glyph_info* glyphs; + rf_int glyphs_count; + bool valid; +} rf_font; + +typedef int rf_glyph_index; + +#pragma region ttf font +RF_API rf_ttf_font_info rf_parse_ttf_font(const void* ttf_data, rf_int font_size); +RF_API void rf_compute_ttf_font_glyph_metrics(rf_ttf_font_info* font_info, const int* codepoints, rf_int codepoints_count, rf_glyph_info* dst, rf_int dst_count); +RF_API int rf_compute_ttf_font_atlas_width(int padding, rf_glyph_info* glyph_metrics, rf_int glyphs_count); +RF_API rf_image rf_generate_ttf_font_atlas(rf_ttf_font_info* font_info, int atlas_width, int padding, rf_glyph_info* glyphs, rf_int glyphs_count, rf_font_antialias antialias, unsigned short* dst, rf_int dst_count, rf_allocator temp_allocator); +RF_API rf_font rf_ttf_font_from_atlas(int font_size, rf_image atlas, rf_glyph_info* glyph_metrics, rf_int glyphs_count); + +RF_API rf_font rf_load_ttf_font_from_data(const void* font_file_data, int font_size, rf_font_antialias antialias, const int* chars, rf_int char_count, rf_allocator allocator, rf_allocator temp_allocator); +RF_API rf_font rf_load_ttf_font_from_file(const char* filename, int font_size, rf_font_antialias antialias, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +#pragma endregion + +#pragma region image font +RF_API bool rf_compute_glyph_metrics_from_image(rf_image image, rf_color key, const int* codepoints, rf_glyph_info* dst, rf_int codepoints_and_dst_count); +RF_API rf_font rf_load_image_font_from_data(rf_image image, rf_glyph_info* glyphs, rf_int glyphs_count); +RF_API rf_font rf_load_image_font(rf_image image, rf_color key, rf_allocator allocator); +RF_API rf_font rf_load_image_font_from_file(const char* path, rf_color key, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +#pragma endregion + +#pragma region font utils +RF_API void rf_unload_font(rf_font font, rf_allocator allocator); +RF_API rf_glyph_index rf_get_glyph_index(rf_font font, int character); +RF_API int rf_font_height(rf_font font, float font_size); + +RF_API rf_sizef rf_measure_text(rf_font font, const char* text, float font_size, float extra_spacing); +RF_API rf_sizef rf_measure_text_rec(rf_font font, const char* text, rf_rec rec, float font_size, float extra_spacing, bool wrap); + +RF_API rf_sizef rf_measure_string(rf_font font, const char* text, int len, float font_size, float extra_spacing); +RF_API rf_sizef rf_measure_string_rec(rf_font font, const char* text, int text_len, rf_rec rec, float font_size, float extra_spacing, bool wrap); +#pragma endregion + +#endif // RAYFORK_FONT_H +/*** End of inlined file: rayfork-font.h ***/ + + +/*** Start of inlined file: rayfork-drawing.h ***/ +#ifndef RAYFORK_DRAWING_H +#define RAYFORK_DRAWING_H + +typedef struct rf_model rf_model; + +typedef enum rf_text_wrap_mode +{ + RF_CHAR_WRAP, + RF_WORD_WRAP, +} rf_text_wrap_mode; + +typedef enum rf_ninepatch_type +{ + RF_NPT_9PATCH = 0, // Npatch defined by 3x3 tiles + RF_NPT_3PATCH_VERTICAL, // Npatch defined by 1x3 tiles + RF_NPT_3PATCH_HORIZONTAL // Npatch defined by 3x1 tiles +} rf_ninepatch_type; + +typedef struct rf_npatch_info +{ + rf_rec source_rec; // Region in the texture + int left; // left border offset + int top; // top border offset + int right; // right border offset + int bottom; // bottom border offset + int type; // layout of the n-patch: 3x3, 1x3 or 3x1 +} rf_npatch_info; + +RF_API void rf_clear(rf_color color); // Set background color (framebuffer clear color) + +RF_API void rf_begin(); // Setup canvas (framebuffer) to start drawing +RF_API void rf_end(); // End canvas drawing and swap buffers (double buffering) + +RF_API void rf_begin_2d(rf_camera2d camera); // Initialize 2D mode with custom camera (2D) +RF_API void rf_end_2d(); // Ends 2D mode with custom camera + +RF_API void rf_begin_3d(rf_camera3d camera); // Initializes 3D mode with custom camera (3D) +RF_API void rf_end_3d(); // Ends 3D mode and returns to default 2D orthographic mode + +RF_API void rf_begin_render_to_texture(rf_render_texture2d target); // Initializes render texture for drawing +RF_API void rf_end_render_to_texture(); // Ends drawing to render texture + +RF_API void rf_begin_scissor_mode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RF_API void rf_end_scissor_mode(); // End scissor mode + +RF_API void rf_begin_shader(rf_shader shader); // Begin custom shader drawing +RF_API void rf_end_shader(); // End custom shader drawing (use default shader) + +RF_API void rf_begin_blend_mode(rf_blend_mode mode); // Begin blending mode (alpha, additive, multiplied) +RF_API void rf_end_blend_mode(); // End blending mode (reset to default: alpha blending) + +RF_API void rf_draw_pixel(int pos_x, int pos_y, rf_color color); // Draw a pixel +RF_API void rf_draw_pixel_v(rf_vec2 position, rf_color color); // Draw a pixel (Vector version) + +RF_API void rf_draw_line(int startPosX, int startPosY, int endPosX, int endPosY, rf_color color); // Draw a line +RF_API void rf_draw_line_v(rf_vec2 startPos, rf_vec2 endPos, rf_color color); // Draw a line (Vector version) +RF_API void rf_draw_line_ex(rf_vec2 startPos, rf_vec2 endPos, float thick, rf_color color); // Draw a line defining thickness +RF_API void rf_draw_line_bezier(rf_vec2 start_pos, rf_vec2 end_pos, float thick, rf_color color); // Draw a line using cubic-bezier curves in-out +RF_API void rf_draw_line_strip(rf_vec2* points, int num_points, rf_color color); // Draw lines sequence + +RF_API void rf_draw_circle(int center_x, int center_y, float radius, rf_color color); // Draw a color-filled circle +RF_API void rf_draw_circle_v(rf_vec2 center, float radius, rf_color color); // Draw a color-filled circle (Vector version) +RF_API void rf_draw_circle_sector(rf_vec2 center, float radius, int start_angle, int end_angle, int segments, rf_color color); // Draw a piece of a circle +RF_API void rf_draw_circle_sector_lines(rf_vec2 center, float radius, int start_angle, int end_angle, int segments, rf_color color); // Draw circle sector outline +RF_API void rf_draw_circle_gradient(int center_x, int center_y, float radius, rf_color color1, rf_color color2); // Draw a gradient-filled circle +RF_API void rf_draw_circle_lines(int center_x, int center_y, float radius, rf_color color); // Draw circle outline + +RF_API void rf_draw_ring(rf_vec2 center, float inner_radius, float outer_radius, int start_angle, int end_angle, int segments, rf_color color); // Draw ring +RF_API void rf_draw_ring_lines(rf_vec2 center, float inner_radius, float outer_radius, int start_angle, int end_angle, int segments, rf_color color); // Draw ring outline + +RF_API void rf_draw_rectangle(int posX, int posY, int width, int height, rf_color color); // Draw a color-filled rectangle +RF_API void rf_draw_rectangle_v(rf_vec2 position, rf_vec2 size, rf_color color); // Draw a color-filled rectangle (Vector version) +RF_API void rf_draw_rectangle_rec(rf_rec rec, rf_color color); // Draw a color-filled rectangle +RF_API void rf_draw_rectangle_pro(rf_rec rec, rf_vec2 origin, float rotation, rf_color color); // Draw a color-filled rectangle with pro parameters + +RF_API void rf_draw_rectangle_gradient_v(int pos_x, int pos_y, int width, int height, rf_color color1, rf_color color2);// Draw a vertical-gradient-filled rectangle +RF_API void rf_draw_rectangle_gradient_h(int pos_x, int pos_y, int width, int height, rf_color color1, rf_color color2);// Draw a horizontal-gradient-filled rectangle +RF_API void rf_draw_rectangle_gradient(rf_rec rec, rf_color col1, rf_color col2, rf_color col3, rf_color col4); // Draw a gradient-filled rectangle with custom vertex colors + +RF_API void rf_draw_rectangle_outline(rf_rec rec, int line_thick, rf_color color); // Draw rectangle outline with extended parameters +RF_API void rf_draw_rectangle_rounded(rf_rec rec, float roundness, int segments, rf_color color); // Draw rectangle with rounded edges +RF_API void rf_draw_rectangle_rounded_lines(rf_rec rec, float roundness, int segments, int line_thick, rf_color color); // Draw rectangle with rounded edges outline + +RF_API void rf_draw_triangle(rf_vec2 v1, rf_vec2 v2, rf_vec2 v3, rf_color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RF_API void rf_draw_triangle_lines(rf_vec2 v1, rf_vec2 v2, rf_vec2 v3, rf_color color); // Draw triangle outline (vertex in counter-clockwise order!) +RF_API void rf_draw_triangle_fan(rf_vec2* points, int num_points, rf_color color); // Draw a triangle fan defined by points (first vertex is the center) +RF_API void rf_draw_triangle_strip(rf_vec2* points, int points_count, rf_color color); // Draw a triangle strip defined by points +RF_API void rf_draw_poly(rf_vec2 center, int sides, float radius, float rotation, rf_color color); // Draw a regular polygon (Vector version) + +// rf_texture2d drawing functions + +RF_API void rf_draw_texture(rf_texture2d texture, int x, int y, rf_color tint); // Draw a rf_texture2d with extended parameters +RF_API void rf_draw_texture_ex(rf_texture2d texture, int x, int y, int w, int h, float rotation, rf_color tint); // Draw a rf_texture2d with extended parameters +RF_API void rf_draw_texture_region(rf_texture2d texture, rf_rec source_rec, rf_rec dest_rec, rf_vec2 origin, float rotation, rf_color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RF_API void rf_draw_texture_npatch(rf_texture2d texture, rf_npatch_info n_patch_info, rf_rec dest_rec, rf_vec2 origin, float rotation, rf_color tint); // Draws a texture (or part of it) that stretches or shrinks nicely + +// Text drawing functions + +RF_API void rf_draw_string(const char* string, int string_len, int posX, int posY, int font_size, rf_color color); // Draw text (using default font) +RF_API void rf_draw_string_ex(rf_font font, const char* string, int string_len, rf_vec2 position, float fontSize, float spacing, rf_color tint); // Draw text using font and additional parameters +RF_API void rf_draw_string_wrap(rf_font font, const char* string, int string_len, rf_vec2 position, float font_size, float spacing, rf_color tint, float wrap_width, rf_text_wrap_mode mode); // Draw text and wrap at a specific width +RF_API void rf_draw_string_rec(rf_font font, const char* string, int string_len, rf_rec rec, float font_size, float spacing, rf_text_wrap_mode wrap, rf_color tint); // Draw text using font inside rectangle limits + +RF_API void rf_draw_text(const char* text, int posX, int posY, int font_size, rf_color color); // Draw text (using default font) +RF_API void rf_draw_text_ex(rf_font font, const char* text, rf_vec2 position, float fontSize, float spacing, rf_color tint); // Draw text using font and additional parameters +RF_API void rf_draw_text_wrap(rf_font font, const char* text, rf_vec2 position, float font_size, float spacing, rf_color tint, float wrap_width, rf_text_wrap_mode mode); // Draw text and wrap at a specific width +RF_API void rf_draw_text_rec(rf_font font, const char* text, rf_rec rec, float font_size, float spacing, rf_text_wrap_mode wrap, rf_color tint); // Draw text using font inside rectangle limits + +RF_API void rf_draw_line3d(rf_vec3 start_pos, rf_vec3 end_pos, rf_color color); // Draw a line in 3D world space +RF_API void rf_draw_circle3d(rf_vec3 center, float radius, rf_vec3 rotation_axis, float rotation_angle, rf_color color); // Draw a circle in 3D world space +RF_API void rf_draw_cube(rf_vec3 position, float width, float height, float length, rf_color color); // Draw cube +RF_API void rf_draw_cube_wires(rf_vec3 position, float width, float height, float length, rf_color color); // Draw cube wires +RF_API void rf_draw_cube_texture(rf_texture2d texture, rf_vec3 position, float width, float height, float length, rf_color color); // Draw cube textured +RF_API void rf_draw_sphere(rf_vec3 center_pos, float radius, rf_color color); // Draw sphere +RF_API void rf_draw_sphere_ex(rf_vec3 center_pos, float radius, int rings, int slices, rf_color color); // Draw sphere with extended parameters +RF_API void rf_draw_sphere_wires(rf_vec3 center_pos, float radius, int rings, int slices, rf_color color); // Draw sphere wires +RF_API void rf_draw_cylinder(rf_vec3 position, float radius_top, float radius_bottom, float height, int slices, rf_color color); // Draw a cylinder/cone +RF_API void rf_draw_cylinder_wires(rf_vec3 position, float radius_top, float radius_bottom, float height, int slices, rf_color color); // Draw a cylinder/cone wires +RF_API void rf_draw_plane(rf_vec3 center_pos, rf_vec2 size, rf_color color); // Draw a plane XZ +RF_API void rf_draw_ray(rf_ray ray, rf_color color); // Draw a ray line +RF_API void rf_draw_grid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) +RF_API void rf_draw_gizmo(rf_vec3 position); // Draw simple gizmo + +// rf_model drawing functions +RF_API void rf_draw_model(rf_model model, rf_vec3 position, float scale, rf_color tint); // Draw a model (with texture if set) +RF_API void rf_draw_model_ex(rf_model model, rf_vec3 position, rf_vec3 rotation_axis, float rotation_angle, rf_vec3 scale, rf_color tint); // Draw a model with extended parameters +RF_API void rf_draw_model_wires(rf_model model, rf_vec3 position, rf_vec3 rotation_axis, float rotation_angle, rf_vec3 scale, rf_color tint); // Draw a model wires (with texture if set) with extended parameters +RF_API void rf_draw_bounding_box(rf_bounding_box box, rf_color color); // Draw bounding box (wires) +RF_API void rf_draw_billboard(rf_camera3d camera, rf_texture2d texture, rf_vec3 center, float size, rf_color tint); // Draw a billboard texture +RF_API void rf_draw_billboard_rec(rf_camera3d camera, rf_texture2d texture, rf_rec source_rec, rf_vec3 center, float size, rf_color tint); // Draw a billboard texture defined by source_rec + +// rf_image draw + +#endif // RAYFORK_DRAWING_H +/*** End of inlined file: rayfork-drawing.h ***/ + + +/*** Start of inlined file: rayfork-3d.h ***/ +#ifndef RAYFORK_3D_H +#define RAYFORK_3D_H + +typedef enum rf_material_map_type +{ + // These 2 are the same + RF_MAP_ALBEDO = 0, + RF_MAP_DIFFUSE = 0, + + // These 2 are the same + RF_MAP_METALNESS = 1, + RF_MAP_SPECULAR = 1, + + RF_MAP_NORMAL = 2, + RF_MAP_ROUGHNESS = 3, + RF_MAP_OCCLUSION = 4, + RF_MAP_EMISSION = 5, + RF_MAP_HEIGHT = 6, + RF_MAP_CUBEMAP = 7, // NOTE: Uses GL_TEXTURE_CUBE_MAP + RF_MAP_IRRADIANCE = 8, // NOTE: Uses GL_TEXTURE_CUBE_MAP + RF_MAP_PREFILTER = 9, // NOTE: Uses GL_TEXTURE_CUBE_MAP + RF_MAP_BRDF = 10 +} rf_material_map_type; + +typedef struct rf_mesh +{ + int vertex_count; // Number of vertices stored in arrays + int triangle_count; // Number of triangles stored (indexed or not) + + // Default vertex data + float* vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float* texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float* texcoords2; // Vertex second texture coordinates (useful for lightmaps) (shader-location = 5) + float* normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float* tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char* colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short* indices; // Vertex indices (in case vertex data comes indexed) + + // Animation vertex data + float* anim_vertices; // Animated vertex positions (after bones transformations) + float* anim_normals; // Animated normals (after bones transformations) + int* bone_ids; // Vertex bone ids, up to 4 bones influence by vertex (skinning) + float* bone_weights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) + + // OpenGL identifiers + unsigned int vao_id; // OpenGL Vertex Array Object id + unsigned int* vbo_id; // OpenGL Vertex Buffer Objects id (default vertex data) +} rf_mesh; + +typedef struct rf_material_map +{ + rf_texture2d texture; // rf_material map texture + rf_color color; // rf_material map color + float value; // rf_material map value +} rf_material_map; + +typedef struct rf_material +{ + rf_shader shader; // rf_material shader + rf_material_map* maps; // rf_material maps array (RF_MAX_MATERIAL_MAPS) + float* params; // rf_material generic parameters (if required) +} rf_material; + +typedef struct rf_transform +{ + rf_vec3 translation; // Translation + rf_quaternion rotation; // Rotation + rf_vec3 scale; // Scale +} rf_transform; + +typedef struct rf_bone_info +{ + char name[32]; // Bone name + rf_int parent; // Bone parent +} rf_bone_info; + +typedef struct rf_model +{ + rf_mat transform; // Local transform matrix + rf_int mesh_count; // Number of meshes + rf_mesh* meshes; // Meshes array + + rf_int material_count; // Number of materials + rf_material* materials; // Materials array + int* mesh_material; // Mesh material number + + // Animation data + rf_int bone_count; // Number of bones + rf_bone_info* bones; // Bones information (skeleton) + rf_transform* bind_pose; // Bones base transformation (pose) +} rf_model; + +typedef struct rf_model_animation +{ + rf_int bone_count; // Number of bones + rf_bone_info* bones; // Bones information (skeleton) + rf_int frame_count; // Number of animation frames + rf_transform** frame_poses; // Poses array by frame +} rf_model_animation; + +typedef struct rf_model_animation_array +{ + rf_int size; + rf_model_animation* anims; +} rf_model_animation_array; + +typedef struct rf_materials_array +{ + rf_int size; + rf_material* materials; +} rf_materials_array; + +RF_API rf_bounding_box rf_mesh_bounding_box(rf_mesh mesh); // Compute mesh bounding box limits +RF_API void rf_mesh_compute_tangents(rf_mesh* mesh, rf_allocator allocator, rf_allocator temp_allocator); // Compute mesh tangents +RF_API void rf_mesh_compute_binormals(rf_mesh* mesh); // Compute mesh binormals +RF_API void rf_unload_mesh(rf_mesh mesh, rf_allocator allocator); // Unload mesh from memory (RAM and/or VRAM) + +RF_API rf_model rf_load_model(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +RF_API rf_model rf_load_model_from_obj(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); // Load model from files (meshes and materials) +RF_API rf_model rf_load_model_from_iqm(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); // Load model from files (meshes and materials) +RF_API rf_model rf_load_model_from_gltf(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); // Load model from files (meshes and materials) +RF_API rf_model rf_load_model_from_mesh(rf_mesh mesh, rf_allocator allocator); // Load model from generated mesh. Note: The function takes ownership of the mesh in model.meshes[0] +RF_API void rf_unload_model(rf_model model, rf_allocator allocator); // Unload model from memory (RAM and/or VRAM) + +RF_API rf_materials_array rf_load_materials_from_mtl(const char* filename, rf_allocator allocator, rf_io_callbacks io); // Load materials from model file +RF_API void rf_set_material_texture(rf_material* material, rf_material_map_type map_type, rf_texture2d texture); // Set texture for a material map type (rf_map_diffuse, rf_map_specular...) +RF_API void rf_set_model_mesh_material(rf_model* model, int mesh_id, int material_id); // Set material for a mesh +RF_API void rf_unload_material(rf_material material, rf_allocator allocator); // Unload material from GPU memory (VRAM) + +// Animations + +RF_API rf_model_animation_array rf_load_model_animations_from_iqm_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +RF_API rf_model_animation_array rf_load_model_animations_from_iqm(const unsigned char* data, int data_size, rf_allocator allocator, rf_allocator temp_allocator); // Load model animations from file +RF_API void rf_update_model_animation(rf_model model, rf_model_animation anim, int frame); // Update model animation pose +RF_API bool rf_is_model_animation_valid(rf_model model, rf_model_animation anim); // Check model animation skeleton match +RF_API void rf_unload_model_animation(rf_model_animation anim, rf_allocator allocator); // Unload animation data + +// mesh generation functions + +RF_API rf_mesh rf_gen_mesh_cube(float width, float height, float length, rf_allocator allocator, rf_allocator temp_allocator); // Generate cuboid mesh +RF_API rf_mesh rf_gen_mesh_poly(int sides, float radius, rf_allocator allocator, rf_allocator temp_allocator); // Generate polygonal mesh +RF_API rf_mesh rf_gen_mesh_plane(float width, float length, int res_x, int res_z, rf_allocator allocator, rf_allocator temp_allocator); // Generate plane mesh (with subdivisions) +RF_API rf_mesh rf_gen_mesh_sphere(float radius, int rings, int slices, rf_allocator allocator, rf_allocator temp_allocator); // Generate sphere mesh (standard sphere) +RF_API rf_mesh rf_gen_mesh_hemi_sphere(float radius, int rings, int slices, rf_allocator allocator, rf_allocator temp_allocator); // Generate half-sphere mesh (no bottom cap) +RF_API rf_mesh rf_gen_mesh_cylinder(float radius, float height, int slices, rf_allocator allocator, rf_allocator temp_allocator); // Generate cylinder mesh +RF_API rf_mesh rf_gen_mesh_torus(float radius, float size, int rad_seg, int sides, rf_allocator allocator, rf_allocator temp_allocator); // Generate torus mesh +RF_API rf_mesh rf_gen_mesh_knot(float radius, float size, int rad_seg, int sides, rf_allocator allocator, rf_allocator temp_allocator); // Generate trefoil knot mesh +RF_API rf_mesh rf_gen_mesh_heightmap(rf_image heightmap, rf_vec3 size, rf_allocator allocator, rf_allocator temp_allocator); // Generate heightmap mesh from image data +RF_API rf_mesh rf_gen_mesh_cubicmap(rf_image cubicmap, rf_vec3 cube_size, rf_allocator allocator, rf_allocator temp_allocator); // Generate cubes-based map mesh from image data + +#endif // RAYFORK_3D_H +/*** End of inlined file: rayfork-3d.h ***/ + + +/*** Start of inlined file: rayfork-context.h ***/ +#ifndef RAYFORK_CONTEXT_H +#define RAYFORK_CONTEXT_H + +typedef struct rf_default_font +{ + unsigned short pixels [128 * 128]; + rf_glyph_info chars [RF_BUILTIN_FONT_CHARS_COUNT]; + unsigned short chars_pixels [128 * 128]; +} rf_default_font; + +typedef struct rf_context +{ + struct /* Graphics stuff */ + { + int current_width; + int current_height; + + int render_width; + int render_height; + + rf_mat screen_scaling; + rf_render_batch* current_batch; + + rf_matrix_mode current_matrix_mode; + rf_mat* current_matrix; + rf_mat modelview; + rf_mat projection; + rf_mat transform; + bool transform_matrix_required; + rf_mat stack[RF_MAX_MATRIX_STACK_SIZE]; + int stack_counter; + + unsigned int default_texture_id; // Default texture (1px white) useful for plain color polys (required by shader) + unsigned int default_vertex_shader_id; // Default vertex shader id (used by default shader program) + unsigned int default_frag_shader_id; // Default fragment shader Id (used by default shader program) + + rf_shader default_shader; // Basic shader, support vertex color and diffuse texture + rf_shader current_shader; // Shader to be used on rendering (by default, default_shader) + + rf_blend_mode blend_mode; // Track current blending mode + + int framebuffer_width; // Default framebuffer width + int framebuffer_height; // Default framebuffer height + + rf_texture2d tex_shapes; + rf_rec rec_tex_shapes; + + rf_font default_font; + rf_default_font default_font_buffers; + + rf_gfx_context gfx_ctx; + }; + + rf_logger logger; + rf_log_type logger_filter; +} rf_context; + +RF_API void rf_init_context(rf_context* ctx); +RF_API void rf_init_gfx(int screen_width, int screen_height, rf_gfx_backend_data* gfx_data); +RF_API void rf_init_audio(rf_audio_backend_data* audio_data); + +RF_API rf_material rf_load_default_material(rf_allocator allocator); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RF_API rf_shader rf_load_default_shader(); + +RF_API rf_render_batch* rf_get_current_render_batch(); // Return a pointer to the current render batch +RF_API rf_font rf_get_default_font(); // Get the default font, useful to be used with extended parameters +RF_API rf_shader rf_get_default_shader(); // Get default shader +RF_API rf_texture2d rf_get_default_texture(); // Get default internal texture (white texture) +RF_API rf_context* rf_get_context(); // Get the context pointer +RF_API rf_image rf_get_screen_data(rf_color* dst, rf_int dst_size); // Get pixel data from GPU frontbuffer and return an rf_image (screenshot) + +RF_API void rf_set_global_context_pointer(rf_context* ctx); // Set the global context pointer +RF_API void rf_set_viewport(int width, int height); // Set viewport for a provided width and height +RF_API void rf_set_shapes_texture(rf_texture2d texture, rf_rec source); // Define default texture used to draw shapes + +#endif // RAYFORK_CONTEXT_H +/*** End of inlined file: rayfork-context.h ***/ + + +/*** Start of inlined file: rayfork-ez.h ***/ +#ifndef RAYFORK_EZ_H +#define RAYFORK_EZ_H + +#if !defined(RAYFORK_NO_EZ_API) + +RF_API rf_material rf_load_default_material_ez(); +RF_API rf_image rf_get_screen_data_ez(); +RF_API rf_base64_output rf_decode_base64_ez(const unsigned char* input); +RF_API rf_image rf_gfx_read_texture_pixels_ez(rf_texture2d texture); + +#pragma region image +#pragma region extract image data functions +RF_API rf_color* rf_image_pixels_to_rgba32_ez(rf_image image); +RF_API rf_vec4* rf_image_compute_pixels_to_normalized_ez(rf_image image); +RF_API rf_palette rf_image_extract_palette_ez(rf_image image, int palette_size); +#pragma endregion + +#pragma region loading & unloading functions +RF_API rf_image rf_load_image_from_file_data_ez(const void* src, int src_size); +RF_API rf_image rf_load_image_from_hdr_file_data_ez(const void* src, int src_size); +RF_API rf_image rf_load_image_from_file_ez(const char* filename); +RF_API void rf_unload_image_ez(rf_image image); +#pragma endregion + +#pragma region image manipulation +RF_API rf_image rf_image_copy_ez(rf_image image); + +RF_API rf_image rf_image_crop_ez(rf_image image, rf_rec crop); + +RF_API rf_image rf_image_resize_ez(rf_image image, int new_width, int new_height); +RF_API rf_image rf_image_resize_nn_ez(rf_image image, int new_width, int new_height); + +RF_API rf_image rf_image_format_ez(rf_image image, rf_uncompressed_pixel_format new_format); + +RF_API rf_image rf_image_alpha_clear_ez(rf_image image, rf_color color, float threshold); +RF_API rf_image rf_image_alpha_premultiply_ez(rf_image image); +RF_API rf_image rf_image_alpha_crop_ez(rf_image image, float threshold); +RF_API rf_image rf_image_dither_ez(rf_image image, int r_bpp, int g_bpp, int b_bpp, int a_bpp); + +RF_API rf_image rf_image_flip_vertical_ez(rf_image image); +RF_API rf_image rf_image_flip_horizontal_ez(rf_image image); + +RF_API rf_vec2 rf_get_seed_for_cellular_image_ez(int seeds_per_row, int tile_size, int i); + +RF_API rf_image rf_gen_image_color_ez(int width, int height, rf_color color); +RF_API rf_image rf_gen_image_gradient_v_ez(int width, int height, rf_color top, rf_color bottom); +RF_API rf_image rf_gen_image_gradient_h_ez(int width, int height, rf_color left, rf_color right); +RF_API rf_image rf_gen_image_gradient_radial_ez(int width, int height, float density, rf_color inner, rf_color outer); +RF_API rf_image rf_gen_image_checked_ez(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2); +RF_API rf_image rf_gen_image_white_noise_ez(int width, int height, float factor); +RF_API rf_image rf_gen_image_perlin_noise_ez(int width, int height, int offset_x, int offset_y, float scale); +RF_API rf_image rf_gen_image_cellular_ez(int width, int height, int tile_size); +#pragma endregion + +#pragma region mipmaps +RF_API rf_mipmaps_image rf_image_gen_mipmaps_ez(rf_image image, int gen_mipmaps_count); +RF_API void rf_unload_mipmaps_image_ez(rf_mipmaps_image image); +#pragma endregion + +#pragma region dds +RF_API rf_mipmaps_image rf_load_dds_image_ez(const void* src, int src_size); +RF_API rf_mipmaps_image rf_load_dds_image_from_file_ez(const char* file); +#pragma endregion + +#pragma region pkm +RF_API rf_image rf_load_pkm_image_ez(const void* src, int src_size); +RF_API rf_image rf_load_pkm_image_from_file_ez(const char* file); +#pragma endregion + +#pragma region ktx +RF_API rf_mipmaps_image rf_load_ktx_image_ez(const void* src, int src_size); +RF_API rf_mipmaps_image rf_load_ktx_image_from_file_ez(const char* file); +#pragma endregion +#pragma endregion + +#pragma region gif +RF_API rf_gif rf_load_animated_gif_ez(const void* data, int data_size); +RF_API rf_gif rf_load_animated_gif_file_ez(const char* filename); +RF_API void rf_unload_gif_ez(rf_gif gif); +#pragma endregion + +#pragma region texture +RF_API rf_texture2d rf_load_texture_from_file_ez(const char* filename); +RF_API rf_texture2d rf_load_texture_from_file_data_ez(const void* data, int dst_size); +RF_API rf_texture_cubemap rf_load_texture_cubemap_from_image_ez(rf_image image, rf_cubemap_layout_type layout_type); +#pragma endregion + +#pragma region font +RF_API rf_font rf_load_ttf_font_from_data_ez(const void* font_file_data, int font_size, rf_font_antialias antialias, const int* chars, int chars_count); +RF_API rf_font rf_load_ttf_font_from_file_ez(const char* filename, int font_size, rf_font_antialias antialias); + +RF_API rf_font rf_load_image_font_ez(rf_image image, rf_color key); +RF_API rf_font rf_load_image_font_from_file_ez(const char* path, rf_color key); + +RF_API void rf_unload_font_ez(rf_font font); +#pragma endregion + +#pragma region utf8 +RF_API rf_decoded_string rf_decode_utf8_ez(const char* text, int len); +#pragma endregion + +#pragma region drawing +RF_API void rf_image_draw_ez(rf_image* dst, rf_image src, rf_rec src_rec, rf_rec dst_rec, rf_color tint); +RF_API void rf_image_draw_rectangle_ez(rf_image* dst, rf_rec rec, rf_color color); +RF_API void rf_image_draw_rectangle_lines_ez(rf_image* dst, rf_rec rec, int thick, rf_color color); +#pragma endregion + +#pragma region model & materials & animations +RF_API void rf_mesh_compute_tangents_ez(rf_mesh* mesh); +RF_API void rf_unload_mesh_ez(rf_mesh mesh); + +RF_API rf_model rf_load_model_ez(const char* filename); +RF_API rf_model rf_load_model_from_obj_ez(const char* filename); +RF_API rf_model rf_load_model_from_iqm_ez(const char* filename); +RF_API rf_model rf_load_model_from_gltf_ez(const char* filename); +RF_API rf_model rf_load_model_from_mesh_ez(rf_mesh mesh); +RF_API void rf_unload_model_ez(rf_model model); + +RF_API rf_materials_array rf_load_materials_from_mtl_ez(const char* filename); +RF_API void rf_unload_material_ez(rf_material material); + +RF_API rf_model_animation_array rf_load_model_animations_from_iqm_file_ez(const char* filename); +RF_API rf_model_animation_array rf_load_model_animations_from_iqm_ez(const unsigned char* data, int data_size); +RF_API void rf_unload_model_animation_ez(rf_model_animation anim); + +RF_API rf_mesh rf_gen_mesh_cube_ez(float width, float height, float length); +RF_API rf_mesh rf_gen_mesh_poly_ez(int sides, float radius); +RF_API rf_mesh rf_gen_mesh_plane_ez(float width, float length, int res_x, int res_z); +RF_API rf_mesh rf_gen_mesh_sphere_ez(float radius, int rings, int slices); +RF_API rf_mesh rf_gen_mesh_hemi_sphere_ez(float radius, int rings, int slices); +RF_API rf_mesh rf_gen_mesh_cylinder_ez(float radius, float height, int slices); +RF_API rf_mesh rf_gen_mesh_torus_ez(float radius, float size, int rad_seg, int sides); +RF_API rf_mesh rf_gen_mesh_knot_ez(float radius, float size, int rad_seg, int sides); +RF_API rf_mesh rf_gen_mesh_heightmap_ez(rf_image heightmap, rf_vec3 size); +RF_API rf_mesh rf_gen_mesh_cubicmap_ez(rf_image cubicmap, rf_vec3 cube_size); +#pragma endregion + +#endif + +#endif // RAYFORK_EZ_H +/*** End of inlined file: rayfork-ez.h ***/ + +#endif // RAYFORK_H diff --git a/src/raylib.lua b/src/raylib.lua index 8549824..db39654 100644 --- a/src/raylib.lua +++ b/src/raylib.lua @@ -24,801 +24,990 @@ end local ffi = require "ffi" local C = ffi.C -local rl = {} -setmetatable(rl, rl) +local rf = { lua = raylua } +setmetatable(rf, rf) -- structs definition ffi.cdef [[ - typedef struct Vector2 { - float x; - float y; - } Vector2; +typedef ptrdiff_t rf_int; - typedef struct Vector3 { - float x; - float y; - float z; - } Vector3; +typedef struct rf_source_location +{ + const char* file_name; + const char* proc_name; + rf_int line_in_file; +} rf_source_location; - typedef struct Vector4 { - float x; - float y; - float z; - float w; - } Vector4; +typedef enum rf_allocator_mode +{ + RF_AM_UNKNOWN = 0, + RF_AM_ALLOC, + RF_AM_REALLOC, + RF_AM_FREE, +} rf_allocator_mode; - typedef Vector4 Quaternion; +typedef struct rf_allocator_args +{ + void* pointer_to_free_or_realloc; + rf_int size_to_allocate_or_reallocate; + int old_size; +} rf_allocator_args; - typedef struct Matrix { - float m0, m4, m8, m12; - float m1, m5, m9, m13; - float m2, m6, m10, m14; - float m3, m7, m11, m15; - } Matrix; +struct rf_allocator; - typedef struct Color { - unsigned char r; - unsigned char g; - unsigned char b; - unsigned char a; - } Color; +typedef void* (rf_allocator_proc)(struct rf_allocator* this_allocator, rf_source_location source_location, rf_allocator_mode mode, rf_allocator_args args); - typedef struct Rectangle { - float x; - float y; - float width; - float height; - } Rectangle; +typedef struct rf_allocator +{ + void* user_data; + rf_allocator_proc* allocator_proc; +} rf_allocator; - typedef struct Image { - void *data; - int width; - int height; - int mipmaps; - int format; - } Image; +typedef struct rf_io_callbacks +{ + void* user_data; + rf_int (*file_size_proc) (void* user_data, const char* filename); + bool (*read_file_proc) (void* user_data, const char* filename, void* dst, rf_int dst_size); +} rf_io_callbacks; - typedef struct Texture2D { - unsigned int id; - int width; - int height; - int mipmaps; - int format; - } Texture2D; - typedef Texture2D Texture; - typedef Texture2D TextureCubemap; +typedef enum rf_error_type +{ + RF_NO_ERROR, + RF_BAD_ARGUMENT, + RF_BAD_ALLOC, + RF_BAD_IO, + RF_BAD_BUFFER_SIZE, + RF_BAD_FORMAT, + RF_LIMIT_REACHED, + RF_STBI_FAILED, + RF_STBTT_FAILED, + RF_UNSUPPORTED, +} rf_error_type; - typedef struct RenderTexture2D { - unsigned int id; - Texture2D texture; - Texture2D depth; - bool depthTexture; - } RenderTexture2D; +typedef struct rf_recorded_error +{ + rf_source_location reported_source_location; + rf_error_type error_type; +} rf_recorded_error; - typedef RenderTexture2D RenderTexture; - typedef struct NPatchInfo { - Rectangle sourceRec; - int left; - int top; - int right; - int bottom; - int type; - } NPatchInfo; +typedef enum rf_log_type +{ + RF_LOG_TYPE_NONE = 0, + RF_LOG_TYPE_DEBUG = 0x1, + RF_LOG_TYPE_INFO = 0x2, + RF_LOG_TYPE_WARNING = 0x4, + RF_LOG_TYPE_ERROR = 0x8, + RF_LOG_TYPE_ALL = 0xF, +} rf_log_type; - typedef struct CharInfo { - int value; - int offsetX; - int offsetY; - int advanceX; - Image image; - } CharInfo; +struct rf_logger; - typedef struct Font { - int baseSize; - int charsCount; - Texture2D texture; - Rectangle *recs; - CharInfo *chars; - } Font; +typedef void (*rf_log_proc)(struct rf_logger* logger, rf_source_location source_location, rf_log_type log_type, const char* msg, rf_error_type error_type, va_list args); - typedef Font SpriteFont; - typedef struct Camera3D { - Vector3 position; - Vector3 target; - Vector3 up; - float fovy; - int type; - } Camera3D; +typedef struct rf_logger +{ + void* user_data; + rf_log_proc log_proc; +} rf_logger; - typedef Camera3D Camera; - typedef struct Camera2D { - Vector2 offset; - Vector2 target; - float rotation; - float zoom; - } Camera2D; +typedef rf_int (*rf_rand_proc)(rf_int min, rf_int max); - typedef struct Mesh { - int vertexCount; - int triangleCount; - float *vertices; - float *texcoords; - float *texcoords2; - float *normals; - float *tangents; - unsigned char *colors; - unsigned short *indices; - float *animVertices; - float *animNormals; - int *boneIds; - float *boneWeights; - unsigned int vaoId; - unsigned int *vboId; - } Mesh; +typedef uint32_t rf_rune; - typedef struct Shader { - unsigned int id; - int *locs; - } Shader; +typedef struct rf_decoded_utf8_stats +{ + rf_int bytes_processed; + rf_int invalid_bytes; + rf_int valid_rune_count; + rf_int total_rune_count; +} rf_decoded_utf8_stats; - typedef struct MaterialMap { - Texture2D texture; - Color color; - float value; - } MaterialMap; +typedef struct rf_decoded_rune +{ + rf_rune codepoint; + rf_int bytes_processed; + bool valid; +} rf_decoded_rune; - typedef struct Material { - Shader shader; - MaterialMap *maps; - float *params; - } Material; +typedef struct rf_decoded_string +{ + rf_rune* codepoints; + rf_int size; + rf_int invalid_bytes_count; + bool valid; +} rf_decoded_string; - typedef struct Transform { - Vector3 translation; - Quaternion rotation; - Vector3 scale; - } Transform; +typedef struct rf_sizei +{ + int width; + int height; +} rf_sizei; - typedef struct BoneInfo { - char name[32]; - int parent; - } BoneInfo; +typedef struct rf_sizef +{ + float width; + float height; +} rf_sizef; - typedef struct Model { - Matrix transform; +typedef struct rf_vec2 +{ + float x; + float y; +} rf_vec2; - int meshCount; - Mesh *meshes; +typedef struct rf_vec3 +{ + float x; + float y; + float z; +} rf_vec3; - int materialCount; - Material *materials; - int *meshMaterial; - int boneCount; - BoneInfo *bones; - Transform *bindPose; - } Model; +typedef struct rf_vec4 +{ + float x; + float y; + float z; + float w; +} rf_vec4, rf_quaternion; - typedef struct ModelAnimation { - int boneCount; - BoneInfo *bones; +typedef struct rf_mat +{ + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; +} rf_mat; - int frameCount; - Transform **framePoses; - } ModelAnimation; +typedef struct rf_float16 +{ + float v[16]; +} rf_float16; - typedef struct Ray { - Vector3 position; - Vector3 direction; - } Ray; +typedef struct rf_rec +{ + float x; + float y; + float width; + float height; +} rf_rec; - typedef struct RayHitInfo { - bool hit; - float distance; - Vector3 position; - Vector3 normal; - } RayHitInfo; +typedef struct rf_ray +{ + rf_vec3 position; + rf_vec3 direction; +} rf_ray; - typedef struct BoundingBox { - Vector3 min; - Vector3 max; - } BoundingBox; +typedef struct rf_ray_hit_info +{ + bool hit; + float distance; + rf_vec3 position; + rf_vec3 normal; +} rf_ray_hit_info; - typedef struct Wave { - unsigned int sampleCount; - unsigned int sampleRate; - unsigned int sampleSize; - unsigned int channels; - void *data; - } Wave; +typedef struct rf_bounding_box +{ + rf_vec3 min; + rf_vec3 max; +} rf_bounding_box; - typedef struct rAudioBuffer rAudioBuffer; - typedef struct AudioStream { - unsigned int sampleRate; - unsigned int sampleSize; - unsigned int channels; +struct rf_model; - rAudioBuffer *buffer; - } AudioStream; +typedef struct rf_base64_output +{ + int size; + unsigned char* buffer; +} rf_base64_output; - typedef struct Sound { - unsigned int sampleCount; - AudioStream stream; - } Sound; +typedef enum rf_pixel_format +{ + RF_UNCOMPRESSED_GRAYSCALE = 1, + RF_UNCOMPRESSED_GRAY_ALPHA, + RF_UNCOMPRESSED_R5G6B5, + RF_UNCOMPRESSED_R8G8B8, + RF_UNCOMPRESSED_R5G5B5A1, + RF_UNCOMPRESSED_R4G4B4A4, + RF_UNCOMPRESSED_R8G8B8A8, + RF_UNCOMPRESSED_RGBA32 = 8, + RF_UNCOMPRESSED_R32, + RF_UNCOMPRESSED_R32G32B32, + RF_UNCOMPRESSED_R32G32B32A32, + RF_UNCOMPRESSED_NORMALIZED = 11, + RF_COMPRESSED_DXT1_RGB, + RF_COMPRESSED_DXT1_RGBA, + RF_COMPRESSED_DXT3_RGBA, + RF_COMPRESSED_DXT5_RGBA, + RF_COMPRESSED_ETC1_RGB, + RF_COMPRESSED_ETC2_RGB, + RF_COMPRESSED_ETC2_EAC_RGBA, + RF_COMPRESSED_PVRT_RGB, + RF_COMPRESSED_PVRT_RGBA, + RF_COMPRESSED_ASTC_4x4_RGBA, + RF_COMPRESSED_ASTC_8x8_RGBA +} rf_pixel_format; - typedef struct Music { - int ctxType; - void *ctxData; +typedef enum rf_pixel_format rf_compressed_pixel_format; +typedef enum rf_pixel_format rf_uncompressed_pixel_format; - bool looping; - unsigned int sampleCount; +typedef struct rf_color +{ + unsigned char r, g, b, a; +} rf_color; - AudioStream stream; - } Music; +typedef struct rf_palette +{ + rf_color* colors; + int count; +} rf_palette; - typedef struct VrDeviceInfo { - int hResolution; - int vResolution; - float hScreenSize; - float vScreenSize; - float vScreenCenter; - float eyeToScreenDistance; - float lensSeparationDistance; - float interpupillaryDistance; - float lensDistortionValues[4]; - float chromaAbCorrection[4]; - } VrDeviceInfo; +typedef enum rf_camera_type +{ + RF_CAMERA_PERSPECTIVE = 0, + RF_CAMERA_ORTHOGRAPHIC +} rf_camera_type; - typedef enum { - FLAG_RESERVED = 1, - FLAG_FULLSCREEN_MODE = 2, - FLAG_WINDOW_RESIZABLE = 4, - FLAG_WINDOW_UNDECORATED = 8, - FLAG_WINDOW_TRANSPARENT = 16, - FLAG_WINDOW_HIDDEN = 128, - FLAG_WINDOW_ALWAYS_RUN = 256, - FLAG_MSAA_4X_HINT = 32, - FLAG_VSYNC_HINT = 64 - } ConfigFlag; +typedef struct rf_camera2d +{ + rf_vec2 offset; + rf_vec2 target; + float rotation; + float zoom; +} rf_camera2d; - typedef enum { - LOG_ALL = 0, - LOG_TRACE, - LOG_DEBUG, - LOG_INFO, - LOG_WARNING, - LOG_ERROR, - LOG_FATAL, - LOG_NONE - } TraceLogType; +typedef struct rf_camera3d +{ + rf_camera_type type; + rf_vec3 position; + rf_vec3 target; + rf_vec3 up; + float fovy; +} rf_camera3d; - typedef enum { - KEY_APOSTROPHE = 39, - KEY_COMMA = 44, - KEY_MINUS = 45, - KEY_PERIOD = 46, - KEY_SLASH = 47, - KEY_ZERO = 48, - KEY_ONE = 49, - KEY_TWO = 50, - KEY_THREE = 51, - KEY_FOUR = 52, - KEY_FIVE = 53, - KEY_SIX = 54, - KEY_SEVEN = 55, - KEY_EIGHT = 56, - KEY_NINE = 57, - KEY_SEMICOLON = 59, - KEY_EQUAL = 61, - KEY_A = 65, - KEY_B = 66, - KEY_C = 67, - KEY_D = 68, - KEY_E = 69, - KEY_F = 70, - KEY_G = 71, - KEY_H = 72, - KEY_I = 73, - KEY_J = 74, - KEY_K = 75, - KEY_L = 76, - KEY_M = 77, - KEY_N = 78, - KEY_O = 79, - KEY_P = 80, - KEY_Q = 81, - KEY_R = 82, - KEY_S = 83, - KEY_T = 84, - KEY_U = 85, - KEY_V = 86, - KEY_W = 87, - KEY_X = 88, - KEY_Y = 89, - KEY_Z = 90, - KEY_SPACE = 32, - KEY_ESCAPE = 256, - KEY_ENTER = 257, - KEY_TAB = 258, - KEY_BACKSPACE = 259, - KEY_INSERT= 260, - KEY_DELETE= 261, - KEY_RIGHT = 262, - KEY_LEFT = 263, - KEY_DOWN = 264, - KEY_UP = 265, - KEY_PAGE_UP = 266, - KEY_PAGE_DOWN = 267, - KEY_HOME = 268, - KEY_END = 269, - KEY_CAPS_LOCK = 280, - KEY_SCROLL_LOCK = 281, - KEY_NUM_LOCK = 282, - KEY_PRINT_SCREEN = 283, - KEY_PAUSE = 284, - KEY_F1 = 290, - KEY_F2 = 291, - KEY_F3 = 292, - KEY_F4 = 293, - KEY_F5 = 294, - KEY_F6 = 295, - KEY_F7 = 296, - KEY_F8 = 297, - KEY_F9 = 298, - KEY_F10 = 299, - KEY_F11 = 300, - KEY_F12 = 301, - KEY_LEFT_SHIFT = 340, - KEY_LEFT_CONTROL = 341, - KEY_LEFT_ALT = 342, - KEY_LEFT_SUPER = 343, - KEY_RIGHT_SHIFT = 344, - KEY_RIGHT_CONTROL = 345, - KEY_RIGHT_ALT = 346, - KEY_RIGHT_SUPER = 347, - KEY_KB_MENU = 348, - KEY_LEFT_BRACKET= 91, - KEY_BACKSLASH = 92, - KEY_RIGHT_BRACKET = 93, - KEY_GRAVE = 96, - KEY_KP_0 = 320, - KEY_KP_1 = 321, - KEY_KP_2 = 322, - KEY_KP_3 = 323, - KEY_KP_4 = 324, - KEY_KP_5 = 325, - KEY_KP_6 = 326, - KEY_KP_7 = 327, - KEY_KP_8 = 328, - KEY_KP_9 = 329, - KEY_KP_DECIMAL = 330, - KEY_KP_DIVIDE = 331, - KEY_KP_MULTIPLY = 332, - KEY_KP_SUBTRACT = 333, - KEY_KP_ADD = 334, - KEY_KP_ENTER = 335, - KEY_KP_EQUAL = 336 - } KeyboardKey; +typedef enum rf_builtin_camera3d_mode +{ + RF_CAMERA_CUSTOM = 0, + RF_CAMERA_FREE, + RF_CAMERA_ORBITAL, + RF_CAMERA_FIRST_PERSON, + RF_CAMERA_THIRD_PERSON +} rf_builtin_camera3d_mode; - typedef enum { - KEY_BACK = 4, - KEY_MENU = 82, - KEY_VOLUME_UP = 24, - KEY_VOLUME_DOWN = 25 - } AndroidButton; +typedef struct rf_camera3d_state +{ + rf_vec2 camera_angle; + float camera_target_distance; + float player_eyes_position; + rf_builtin_camera3d_mode camera_mode; + int swing_counter; + rf_vec2 previous_mouse_position; +} rf_camera3d_state; - typedef enum { - MOUSE_LEFT_BUTTON = 0, - MOUSE_RIGHT_BUTTON = 1, - MOUSE_MIDDLE_BUTTON = 2 - } MouseButton; +typedef struct rf_input_state_for_update_camera +{ + rf_vec2 mouse_position; + int mouse_wheel_move; + bool is_camera_pan_control_key_down; + bool is_camera_alt_control_key_down; + bool is_camera_smooth_zoom_control_key; + bool direction_keys[6]; +} rf_input_state_for_update_camera; - typedef enum { - GAMEPAD_PLAYER1 = 0, - GAMEPAD_PLAYER2 = 1, - GAMEPAD_PLAYER3 = 2, - GAMEPAD_PLAYER4 = 3 - } GamepadNumber; +typedef enum rf_desired_channels +{ + RF_ANY_CHANNELS = 0, + RF_1BYTE_GRAYSCALE = 1, + RF_2BYTE_GRAY_ALPHA = 2, + RF_3BYTE_R8G8B8 = 3, + RF_4BYTE_R8G8B8A8 = 4, +} rf_desired_channels; - typedef enum { - GAMEPAD_BUTTON_UNKNOWN = 0, - GAMEPAD_BUTTON_LEFT_FACE_UP, - GAMEPAD_BUTTON_LEFT_FACE_RIGHT, - GAMEPAD_BUTTON_LEFT_FACE_DOWN, - GAMEPAD_BUTTON_LEFT_FACE_LEFT, - GAMEPAD_BUTTON_RIGHT_FACE_UP, - GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, - GAMEPAD_BUTTON_RIGHT_FACE_DOWN, - GAMEPAD_BUTTON_RIGHT_FACE_LEFT, - GAMEPAD_BUTTON_LEFT_TRIGGER_1, - GAMEPAD_BUTTON_LEFT_TRIGGER_2, - GAMEPAD_BUTTON_RIGHT_TRIGGER_1, - GAMEPAD_BUTTON_RIGHT_TRIGGER_2, - GAMEPAD_BUTTON_MIDDLE_LEFT, - GAMEPAD_BUTTON_MIDDLE, - GAMEPAD_BUTTON_MIDDLE_RIGHT, - GAMEPAD_BUTTON_LEFT_THUMB, - GAMEPAD_BUTTON_RIGHT_THUMB - } GamepadButton; +typedef struct rf_image +{ + void* data; + int width; + int height; + rf_pixel_format format; + bool valid; +} rf_image; - typedef enum { - GAMEPAD_AXIS_UNKNOWN = 0, - GAMEPAD_AXIS_LEFT_X, - GAMEPAD_AXIS_LEFT_Y, - GAMEPAD_AXIS_RIGHT_X, - GAMEPAD_AXIS_RIGHT_Y, - GAMEPAD_AXIS_LEFT_TRIGGER, - GAMEPAD_AXIS_RIGHT_TRIGGER - } GamepadAxis; +typedef struct rf_mipmaps_stats +{ + int possible_mip_counts; + int mipmaps_buffer_size; +} rf_mipmaps_stats; - typedef enum { - LOC_VERTEX_POSITION = 0, - LOC_VERTEX_TEXCOORD01, - LOC_VERTEX_TEXCOORD02, - LOC_VERTEX_NORMAL, - LOC_VERTEX_TANGENT, - LOC_VERTEX_COLOR, - LOC_MATRIX_MVP, - LOC_MATRIX_MODEL, - LOC_MATRIX_VIEW, - LOC_MATRIX_PROJECTION, - LOC_VECTOR_VIEW, - LOC_COLOR_DIFFUSE, - LOC_COLOR_SPECULAR, - LOC_COLOR_AMBIENT, - LOC_MAP_ALBEDO, - LOC_MAP_METALNESS, - LOC_MAP_NORMAL, - LOC_MAP_ROUGHNESS, - LOC_MAP_OCCLUSION, - LOC_MAP_EMISSION, - LOC_MAP_HEIGHT, - LOC_MAP_CUBEMAP, - LOC_MAP_IRRADIANCE, - LOC_MAP_PREFILTER, - LOC_MAP_BRDF - } ShaderLocationIndex; +typedef struct rf_mipmaps_image +{ + union + { + rf_image image; + struct + { + void* data; + int width; + int height; + rf_pixel_format format; + bool valid; + }; + }; - typedef enum { - UNIFORM_FLOAT = 0, - UNIFORM_VEC2, - UNIFORM_VEC3, - UNIFORM_VEC4, - UNIFORM_INT, - UNIFORM_IVEC2, - UNIFORM_IVEC3, - UNIFORM_IVEC4, - UNIFORM_SAMPLER2D - } ShaderUniformDataType; + int mipmaps; +} rf_mipmaps_image; - typedef enum { - MAP_ALBEDO = 0, - MAP_METALNESS = 1, - MAP_NORMAL = 2, - MAP_ROUGHNESS = 3, - MAP_OCCLUSION, - MAP_EMISSION, - MAP_HEIGHT, - MAP_CUBEMAP, - MAP_IRRADIANCE, - MAP_PREFILTER, - MAP_BRDF - } MaterialMapType; +typedef struct rf_gif +{ + int frames_count; + int* frame_delays; - typedef enum { - UNCOMPRESSED_GRAYSCALE = 1, - UNCOMPRESSED_GRAY_ALPHA, - UNCOMPRESSED_R5G6B5, - UNCOMPRESSED_R8G8B8, - UNCOMPRESSED_R5G5B5A1, - UNCOMPRESSED_R4G4B4A4, - UNCOMPRESSED_R8G8B8A8, - UNCOMPRESSED_R32, - UNCOMPRESSED_R32G32B32, - UNCOMPRESSED_R32G32B32A32, - COMPRESSED_DXT1_RGB, - COMPRESSED_DXT1_RGBA, - COMPRESSED_DXT3_RGBA, - COMPRESSED_DXT5_RGBA, - COMPRESSED_ETC1_RGB, - COMPRESSED_ETC2_RGB, - COMPRESSED_ETC2_EAC_RGBA, - COMPRESSED_PVRT_RGB, - COMPRESSED_PVRT_RGBA, - COMPRESSED_ASTC_4x4_RGBA, - COMPRESSED_ASTC_8x8_RGBA - } PixelFormat; + union + { + rf_image image; - typedef enum { - FILTER_POINT = 0, - FILTER_BILINEAR, - FILTER_TRILINEAR, - FILTER_ANISOTROPIC_4X, - FILTER_ANISOTROPIC_8X, - FILTER_ANISOTROPIC_16X, - } TextureFilterMode; + struct + { + void* data; + int width; + int height; + rf_pixel_format format; + bool valid; + }; + }; +} rf_gif; - typedef enum { - CUBEMAP_AUTO_DETECT = 0, - CUBEMAP_LINE_VERTICAL, - CUBEMAP_LINE_HORIZONTAL, - CUBEMAP_CROSS_THREE_BY_FOUR, - CUBEMAP_CROSS_FOUR_BY_THREE, - CUBEMAP_PANORAMA - } CubemapLayoutType; +typedef enum rf_audio_format +{ + RF_AUDIO_FORMAT_UNKNOWN = 0, + RF_AUDIO_FORMAT_WAV, + RF_AUDIO_FORMAT_OGG, + RF_AUDIO_FORMAT_FLAC, + RF_AUDIO_FORMAT_MP3, + RF_AUDIO_FORMAT_XM, + RF_AUDIO_FORMAT_MOD, +} rf_audio_format; - typedef enum { - WRAP_REPEAT = 0, - WRAP_CLAMP, - WRAP_MIRROR_REPEAT, - WRAP_MIRROR_CLAMP - } TextureWrapMode; +typedef enum rf_audio_data_type +{ + RF_DECODED_AUDIO_DATA, + RF_ENCODED_AUDIO_DATA +} rf_audio_data_type; - typedef enum { - FONT_DEFAULT = 0, - FONT_BITMAP, - FONT_SDF - } FontType; +typedef enum rf_audio_player_type +{ + RF_STREAMING_AUDIO_PLAYER, + RF_STATIC_AUDIO_PLAYER, +} rf_audio_player_type; - typedef enum { - BLEND_ALPHA = 0, - BLEND_ADDITIVE, - BLEND_MULTIPLIED, - BLEND_ADD_COLORS, - BLEND_SUBTRACT_COLORS, - BLEND_CUSTOM - } BlendMode; +typedef enum rf_audio_channels +{ + RF_MONO = 1, + RF_STEREO = 2, +} rf_audio_channels; - typedef enum { - GESTURE_NONE = 0, - GESTURE_TAP = 1, - GESTURE_DOUBLETAP = 2, - GESTURE_HOLD = 4, - GESTURE_DRAG = 8, - GESTURE_SWIPE_RIGHT = 16, - GESTURE_SWIPE_LEFT = 32, - GESTURE_SWIPE_UP = 64, - GESTURE_SWIPE_DOWN = 128, - GESTURE_PINCH_IN = 256, - GESTURE_PINCH_OUT = 512 - } GestureType; +typedef struct rf_audio_buffer rf_audio_buffer; - typedef enum { - CAMERA_CUSTOM = 0, - CAMERA_FREE, - CAMERA_ORBITAL, - CAMERA_FIRST_PERSON, - CAMERA_THIRD_PERSON - } CameraMode; +typedef struct rf_audio_data +{ + rf_audio_data_type type; - typedef enum { - CAMERA_PERSPECTIVE = 0, - CAMERA_ORTHOGRAPHIC - } CameraType; + void* data; + int data_size; - typedef enum { - NPT_9PATCH = 0, - NPT_3PATCH_VERTICAL, - NPT_3PATCH_HORIZONTAL - } NPatchType; + int size_in_frames; + int sample_count; + int sample_rate; + int sample_size; + rf_audio_channels channels; + bool valid; +} rf_audio_data; + +typedef struct rf_audio_player +{ + rf_audio_player_type type; + rf_audio_data source; + float volume; + bool looping; + bool valid; +} rf_audio_player; + +typedef struct rf_audio_device +{ + int id; +} rf_audio_device; + +typedef enum rf_matrix_mode +{ + RF_MODELVIEW = 0x1700, + RF_PROJECTION = 0x1701, + RF_TEXTURE = 0x1702, +} rf_matrix_mode; + +typedef enum rf_drawing_mode +{ + RF_LINES = 0x0001, + RF_TRIANGLES = 0x0004, + RF_QUADS = 0x0007, +} rf_drawing_mode; + +typedef enum rf_shader_location_index +{ + RF_LOC_VERTEX_POSITION = 0, + RF_LOC_VERTEX_TEXCOORD01 = 1, + RF_LOC_VERTEX_TEXCOORD02 = 2, + RF_LOC_VERTEX_NORMAL = 3, + RF_LOC_VERTEX_TANGENT = 4, + RF_LOC_VERTEX_COLOR = 5, + RF_LOC_MATRIX_MVP = 6, + RF_LOC_MATRIX_MODEL = 7, + RF_LOC_MATRIX_VIEW = 8, + RF_LOC_MATRIX_PROJECTION = 9, + RF_LOC_VECTOR_VIEW = 10, + RF_LOC_COLOR_DIFFUSE = 11, + RF_LOC_COLOR_SPECULAR = 12, + RF_LOC_COLOR_AMBIENT = 13, + + RF_LOC_MAP_ALBEDO = 14, + RF_LOC_MAP_DIFFUSE = 14, + + RF_LOC_MAP_METALNESS = 15, + RF_LOC_MAP_SPECULAR = 15, + + RF_LOC_MAP_NORMAL = 16, + RF_LOC_MAP_ROUGHNESS = 17, + RF_LOC_MAP_OCCLUSION = 18, + RF_LOC_MAP_EMISSION = 19, + RF_LOC_MAP_HEIGHT = 20, + RF_LOC_MAP_CUBEMAP = 21, + RF_LOC_MAP_IRRADIANCE = 22, + RF_LOC_MAP_PREFILTER = 23, + RF_LOC_MAP_BRDF = 24, +} rf_shader_location_index; + +typedef enum rf_shader_uniform_data_type +{ + RF_UNIFORM_FLOAT = 0, + RF_UNIFORM_VEC2, + RF_UNIFORM_VEC3, + RF_UNIFORM_VEC4, + RF_UNIFORM_INT, + RF_UNIFORM_IVEC2, + RF_UNIFORM_IVEC3, + RF_UNIFORM_IVEC4, + RF_UNIFORM_SAMPLER2D +} rf_shader_uniform_data_type; + +typedef enum rf_texture_filter_mode +{ + RF_FILTER_POINT = 0, + RF_FILTER_BILINEAR, + RF_FILTER_TRILINEAR, + RF_FILTER_ANISOTROPIC_4x, + RF_FILTER_ANISOTROPIC_8x, + RF_FILTER_ANISOTROPIC_16x, +} rf_texture_filter_mode; + +typedef enum rf_cubemap_layout_type +{ + RF_CUBEMAP_AUTO_DETECT = 0, + RF_CUBEMAP_LINE_VERTICAL, + RF_CUBEMAP_LINE_HORIZONTAL, + RF_CUBEMAP_CROSS_THREE_BY_FOUR, + RF_CUBEMAP_CROSS_FOUR_BY_TREE, + RF_CUBEMAP_PANORAMA +} rf_cubemap_layout_type; + +typedef enum rf_texture_wrap_mode +{ + RF_WRAP_REPEAT = 0, + RF_WRAP_CLAMP, + RF_WRAP_MIRROR_REPEAT, + RF_WRAP_MIRROR_CLAMP +} rf_texture_wrap_mode; + +typedef enum rf_blend_mode +{ + RF_BLEND_ALPHA = 0, + RF_BLEND_ADDITIVE, + RF_BLEND_MULTIPLIED +} rf_blend_mode; + +typedef struct rf_shader +{ + unsigned int id; + int locs[32]; +} rf_shader; + +typedef struct rf_gfx_pixel_format +{ + unsigned int internal_format; + unsigned int format; + unsigned int type; + bool valid; +} rf_gfx_pixel_format; + +typedef struct rf_texture2d +{ + unsigned int id; + int width; + int height; + int mipmaps; + rf_pixel_format format; + bool valid; +} rf_texture2d, rf_texture_cubemap; + +typedef struct rf_render_texture2d +{ + unsigned int id; + rf_texture2d texture; + rf_texture2d depth; + int depth_texture; +} rf_render_texture2d; + +struct rf_vertex_buffer; +struct rf_material; +typedef void rf_gfx_backend_data; +typedef void rf_audio_backend_data; + +typedef float rf_gfx_vertex_data_type; +typedef float rf_gfx_texcoord_data_type; +typedef unsigned char rf_gfx_color_data_type; + +// NOTE: this is unsigned short with GL ~= 3.2 +typedef unsigned int rf_gfx_vertex_index_data_type; + +typedef struct rf_vertex_buffer +{ + int elements_count; + int v_counter; + int tc_counter; + int c_counter; + + unsigned int vao_id; + unsigned int vbo_id[4]; + + rf_gfx_vertex_data_type* vertices; + rf_gfx_texcoord_data_type* texcoords; + rf_gfx_color_data_type* colors; + rf_gfx_vertex_index_data_type* indices; +} rf_vertex_buffer; + +typedef struct rf_draw_call +{ + rf_drawing_mode mode; + int vertex_count; + int vertex_alignment; + + unsigned int texture_id; +} rf_draw_call; + +typedef struct rf_opengl_procs +{ + void (*Viewport) (int x, int y, int width, int height); + void (*BindTexture) (unsigned int target, unsigned int texture); + void (*TexParameteri) (unsigned int target, unsigned int pname, int param); + void (*TexParameterf) (unsigned int target, unsigned int pname, float param); + void (*TexParameteriv) (unsigned int target, unsigned int pname, const int* params); + void (*BindFramebuffer) (unsigned int target, unsigned int framebuffer); + void (*Enable) (unsigned int cap); + void (*Disable) (unsigned int cap); + void (*Scissor) (int x, int y, int width, int height); + void (*DeleteTextures) (int n, const unsigned int* textures); + void (*DeleteRenderbuffers) (int n, const unsigned int* renderbuffers); + void (*DeleteFramebuffers) (int n, const unsigned int* framebuffers); + void (*DeleteVertexArrays) (int n, const unsigned int* arrays); + void (*DeleteBuffers) (int n, const unsigned int* buffers); + void (*ClearColor) (float red, float green, float blue, float alpha); + void (*Clear) (unsigned int mask); + void (*BindBuffer) (unsigned int target, unsigned int buffer); + void (*BufferSubData) (unsigned int target, ptrdiff_t offset, ptrdiff_t size, const void* data); + void (*BindVertexArray) (unsigned int array); + void (*GenBuffers) (int n, unsigned int* buffers); + void (*BufferData) (unsigned int target, ptrdiff_t size, const void* data, unsigned int usage); + void (*VertexAttribPointer) (unsigned int index, int size, unsigned int type, unsigned char normalized, int stride, const void* pointer); + void (*EnableVertexAttribArray) (unsigned int index); + void (*GenVertexArrays) (int n, unsigned int* arrays); + void (*VertexAttrib3f) (unsigned int index, float x, float y, float z); + void (*DisableVertexAttribArray) (unsigned int index); + void (*VertexAttrib4f) (unsigned int index, float x, float y, float z, float w); + void (*VertexAttrib2f) (unsigned int index, float x, float y); + void (*UseProgram) (unsigned int program); + void (*Uniform4f) (int location, float v0, float v1, float v2, float v3); + void (*ActiveTexture) (unsigned int texture); + void (*Uniform1i) (int location, int v0); + void (*UniformMatrix4fv) (int location, int count, unsigned char transpose, const float* value); + void (*DrawElements) (unsigned int mode, int count, unsigned int type, const void* indices); + void (*DrawArrays) (unsigned int mode, int first, int count); + void (*PixelStorei) (unsigned int pname, int param); + void (*GenTextures) (int n, unsigned int* textures); + void (*TexImage2D) (unsigned int target, int level, int internalformat, int width, int height, int border, unsigned int format, unsigned int type, const void* pixels); + void (*GenRenderbuffers) (int n, unsigned int* renderbuffers); + void (*BindRenderbuffer) (unsigned int target, unsigned int renderbuffer); + void (*RenderbufferStorage) (unsigned int target, unsigned int internalformat, int width, int height); + void (*CompressedTexImage2D) (unsigned int target, int level, unsigned int internalformat, int width, int height, int border, int imageSize, const void* data); + void (*TexSubImage2D) (unsigned int target, int level, int txoffset, int yoffset, int width, int height, unsigned int format, unsigned int type, const void* pixels); + void (*GenerateMipmap) (unsigned int target); + void (*ReadPixels) (int x, int y, int width, int height, unsigned int format, unsigned int type, void* pixels); + void (*GenFramebuffers) (int n, unsigned int* framebuffers); + void (*FramebufferTexture2D) (unsigned int target, unsigned int attachment, unsigned int textarget, unsigned int texture, int level); + void (*FramebufferRenderbuffer) (unsigned int target, unsigned int attachment, unsigned int renderbuffertarget, unsigned int renderbuffer); + unsigned int (*CheckFramebufferStatus) (unsigned int target); + unsigned int (*CreateShader) (unsigned int type); + void (*ShaderSource) (unsigned int shader, int count, const char** string, const int* length); + void (*CompileShader) (unsigned int shader); + void (*GetShaderiv) (unsigned int shader, unsigned int pname, int* params); + void (*GetShaderInfoLog) (unsigned int shader, int bufSize, int* length, char* infoLog); + unsigned int (*CreateProgram) (); + void (*AttachShader) (unsigned int program, unsigned int shader); + void (*BindAttribLocation) (unsigned int program, unsigned int index, const char* name); + void (*LinkProgram) (unsigned int program); + void (*GetProgramiv) (unsigned int program, unsigned int pname, int* params); + void (*GetProgramInfoLog) (unsigned int program, int bufSize, int* length, char* infoLog); + void (*DeleteProgram) (unsigned int program); + int (*GetAttribLocation) (unsigned int program, const char* name); + int (*GetUniformLocation) (unsigned int program, const char* name); + void (*DetachShader) (unsigned int program, unsigned int shader); + void (*DeleteShader) (unsigned int shader); + void (*GetActiveUniform) (unsigned int program, unsigned int index, int bufSize, int* length, int* size, unsigned int* type, char* name); + void (*Uniform1f) (int location, float v0); + void (*Uniform1fv) (int location, int count, const float* value); + void (*Uniform2fv) (int location, int count, const float* value); + void (*Uniform3fv) (int location, int count, const float* value); + void (*Uniform4fv) (int location, int count, const float* value); + void (*Uniform1iv) (int location, int count, const int* value); + void (*Uniform2iv) (int location, int count, const int* value); + void (*Uniform3iv) (int location, int count, const int* value); + void (*Uniform4iv) (int location, int count, const int* value); + const unsigned char* (*GetString) (unsigned int name); + void (*GetFloatv) (unsigned int pname, float* data); + void (*DepthFunc) (unsigned int func); + void (*BlendFunc) (unsigned int sfactor, unsigned int dfactor); + void (*CullFace) (unsigned int mode); + void (*FrontFace) (unsigned int mode); + const unsigned char* (*GetStringi) (unsigned int name, unsigned int index); + void (*GetTexImage) (unsigned int target, int level, unsigned int format, unsigned int type, void* pixels); + void (*ClearDepth) (double depth); + void (*ClearDepthf) (float depth); + void (*GetIntegerv) (unsigned int pname, int* data); + void (*PolygonMode) (unsigned int face, unsigned int mode); +} rf_opengl_procs; + + +typedef struct rf_gfx_context +{ + rf_opengl_procs gl; + + struct { + bool tex_comp_dxt_supported; + bool tex_comp_etc1_supported; + bool tex_comp_etc2_supported; + bool tex_comp_pvrt_supported; + bool tex_comp_astc_supported; + bool tex_npot_supported; + bool tex_float_supported; + bool tex_depth_supported; + int max_depth_bits; + bool tex_mirror_clamp_supported; + bool tex_anisotropic_filter_supported; + float max_anisotropic_level; + bool debug_marker_supported; + } extensions; +} rf_gfx_context; + +typedef struct rf_render_batch +{ + rf_int vertex_buffers_count; + rf_int current_buffer; + rf_vertex_buffer* vertex_buffers; + + rf_int draw_calls_size; + rf_int draw_calls_counter; + rf_draw_call* draw_calls; + float current_depth; + + bool valid; +} rf_render_batch; + +typedef struct rf_one_element_vertex_buffer +{ + rf_gfx_vertex_data_type vertices [12]; + rf_gfx_texcoord_data_type texcoords [8]; + rf_gfx_color_data_type colors [16]; + rf_gfx_vertex_index_data_type indices[6]; +} rf_one_element_vertex_buffer; + +typedef struct rf_default_vertex_buffer +{ + rf_gfx_vertex_data_type vertices [24576]; + rf_gfx_texcoord_data_type texcoords [16384]; + rf_gfx_color_data_type colors [32768]; + rf_gfx_vertex_index_data_type indices [12288]; +} rf_default_vertex_buffer; + +typedef struct rf_default_render_batch +{ + rf_vertex_buffer vertex_buffers [1]; + rf_draw_call draw_calls [256]; + rf_default_vertex_buffer vertex_buffers_memory [1]; +} rf_default_render_batch; + +typedef enum rf_font_antialias +{ + RF_FONT_ANTIALIAS = 0, + RF_FONT_NO_ANTIALIAS, +} rf_font_antialias; + +typedef struct rf_glyph_info +{ + union + { + rf_rec rec; + struct { float x, y, width, height; }; + }; + + int codepoint; + int offset_x; + int offset_y; + int advance_x; +} rf_glyph_info; + +typedef struct rf_ttf_font_info +{ + + const void* ttf_data; + int font_size; + int largest_glyph_size; + + + float scale_factor; + int ascent; + int descent; + int line_gap; + + + struct + { + void* userdata; + unsigned char* data; + int fontstart; + int numGlyphs; + int loca, head, glyf, hhea, hmtx, kern, gpos, svg; + int index_map; + int indexToLocFormat; + + struct + { + unsigned char* data; + int cursor; + int size; + } cff, charstrings, gsubrs, subrs, fontdicts, fdselect; + } internal_stb_font_info; + + bool valid; +} rf_ttf_font_info; + +typedef struct rf_font +{ + int base_size; + rf_texture2d texture; + rf_glyph_info* glyphs; + rf_int glyphs_count; + bool valid; +} rf_font; + +typedef int rf_glyph_index; + +typedef enum rf_text_wrap_mode +{ + RF_CHAR_WRAP, + RF_WORD_WRAP, +} rf_text_wrap_mode; + +typedef enum rf_ninepatch_type +{ + RF_NPT_9PATCH = 0, + RF_NPT_3PATCH_VERTICAL, + RF_NPT_3PATCH_HORIZONTAL +} rf_ninepatch_type; + +typedef struct rf_npatch_info +{ + rf_rec source_rec; + int left; + int top; + int right; + int bottom; + int type; +} rf_npatch_info; + +typedef struct rf_material_map +{ + rf_texture2d texture; + rf_color color; + float value; +} rf_material_map; + +typedef struct rf_material +{ + rf_shader shader; + rf_material_map* maps; + float* params; +} rf_material; + +typedef enum rf_material_map_type +{ + RF_MAP_ALBEDO = 0, + RF_MAP_DIFFUSE = 0, + + RF_MAP_METALNESS = 1, + RF_MAP_SPECULAR = 1, + + RF_MAP_NORMAL = 2, + RF_MAP_ROUGHNESS = 3, + RF_MAP_OCCLUSION = 4, + RF_MAP_EMISSION = 5, + RF_MAP_HEIGHT = 6, + RF_MAP_CUBEMAP = 7, + RF_MAP_IRRADIANCE = 8, + RF_MAP_PREFILTER = 9, + RF_MAP_BRDF = 10 +} rf_material_map_type; + +typedef struct rf_mesh +{ + int vertex_count; + int triangle_count; + + float* vertices; + float* texcoords; + float* texcoords2; + float* normals; + float* tangents; + unsigned char* colors; + unsigned short* indices; + + float* anim_vertices; + float* anim_normals; + int* bone_ids; + float* bone_weights; + + unsigned int vao_id; + unsigned int* vbo_id; +} rf_mesh; + +typedef struct rf_transform +{ + rf_vec3 translation; + rf_quaternion rotation; + rf_vec3 scale; +} rf_transform; + +typedef struct rf_bone_info +{ + char name[32]; + rf_int parent; +} rf_bone_info; + +typedef struct rf_model +{ + rf_mat transform; + rf_int mesh_count; + rf_mesh* meshes; + + rf_int material_count; + rf_material* materials; + int* mesh_material; + + rf_int bone_count; + rf_bone_info* bones; + rf_transform* bind_pose; +} rf_model; + +typedef struct rf_model_animation +{ + rf_int bone_count; + rf_bone_info* bones; + rf_int frame_count; + rf_transform** frame_poses; +} rf_model_animation; + +typedef struct rf_model_animation_array +{ + rf_int size; + rf_model_animation* anims; +} rf_model_animation_array; + +typedef struct rf_materials_array +{ + rf_int size; + rf_material* materials; +} rf_materials_array; + +typedef struct rf_default_font +{ + unsigned short pixels [16384]; + rf_glyph_info chars [224]; + unsigned short chars_pixels [16384]; +} rf_default_font; + +typedef struct rf_context +{ + struct + { + int current_width; + int current_height; + + int render_width; + int render_height; + + rf_mat screen_scaling; + rf_render_batch* current_batch; + + rf_matrix_mode current_matrix_mode; + rf_mat* current_matrix; + rf_mat modelview; + rf_mat projection; + rf_mat transform; + bool transform_matrix_required; + rf_mat stack[32]; + int stack_counter; + + unsigned int default_texture_id; + unsigned int default_vertex_shader_id; + unsigned int default_frag_shader_id; + + rf_shader default_shader; + rf_shader current_shader; + + rf_blend_mode blend_mode; + + int framebuffer_width; + int framebuffer_height; + + rf_texture2d tex_shapes; + rf_rec rec_tex_shapes; + + rf_font default_font; + rf_default_font default_font_buffers; + + rf_gfx_context gfx_ctx; + }; + + rf_logger logger; + rf_log_type logger_filter; +} rf_context; - typedef void (*TraceLogCallback)(int logType, const char *text, va_list args); ]] --- raymath cdef -ffi.cdef [[ - typedef struct float3 { float v[3]; } float3; - typedef struct float16 { float v[16]; } float16; -]] +rf.RF_DEFAULT_ATTRIB_POSITION_NAME = "vertex_position" +rf.RF_DEFAULT_ATTRIB_TEXCOORD_NAME = "vertex_tex_coord" +rf.RF_DEFAULT_ATTRIB_NORMAL_NAME = "vertex_normal" +rf.RF_DEFAULT_ATTRIB_COLOR_NAME = "vertex_color" +rf.RF_DEFAULT_ATTRIB_TANGENT_NAME = "vertex_tangent" +rf.RF_DEFAULT_ATTRIB_TEXCOORD2_NAME = "vertex_tex_coord2" --- Physac cdef -ffi.cdef [[ - typedef struct PhysicsBodyData *PhysicsBody; - typedef enum PhysicsShapeType { - PHYSICS_CIRCLE, - PHYSICS_POLYGON - } PhysicsShapeType; - - typedef struct Matrix2x2 { - float m00; - float m01; - float m10; - float m11; - } Matrix2x2; - - typedef struct PolygonData { - unsigned int vertexCount; - Vector2 positions[24]; - Vector2 normals[24]; - } PolygonData; - - typedef struct PhysicsShape { - PhysicsShapeType type; - PhysicsBody body; - float radius; - Matrix2x2 transform; - PolygonData vertexData; - } PhysicsShape; - - typedef struct PhysicsBodyData { - unsigned int id; - bool enabled; - Vector2 position; - Vector2 velocity; - Vector2 force; - float angularVelocity; - float torque; - float orient; - float inertia; - float inverseInertia; - float mass; - float inverseMass; - float staticFriction; - float dynamicFriction; - float restitution; - bool useGravity; - bool isGrounded; - bool freezeOrient; - PhysicsShape shape; - } PhysicsBodyData; -]] - --- gestures cdef -ffi.cdef [[ - typedef enum { TOUCH_UP, TOUCH_DOWN, TOUCH_MOVE } TouchAction; - - typedef struct { - int touchAction; - int pointCount; - int pointerId[4]; - Vector2 position[4]; - } GestureEvent; -]] - --- raygui cdef -ffi.cdef [[ - typedef struct GuiStyleProp { - unsigned short controlId; - unsigned short propertyId; - int propertyValue; - } GuiStyleProp; - - typedef enum { - GUI_STATE_NORMAL = 0, - GUI_STATE_FOCUSED, - GUI_STATE_PRESSED, - GUI_STATE_DISABLED, - } GuiControlState; - - typedef enum { - GUI_TEXT_ALIGN_LEFT = 0, - GUI_TEXT_ALIGN_CENTER, - GUI_TEXT_ALIGN_RIGHT, - } GuiTextAlignment; - - typedef enum { - DEFAULT = 0, - LABEL, - BUTTON, - TOGGLE, - SLIDER, - PROGRESSBAR, - CHECKBOX, - COMBOBOX, - DROPDOWNBOX, - TEXTBOX, - VALUEBOX, - SPINNER, - LISTVIEW, - COLORPICKER, - SCROLLBAR, - STATUSBAR - } GuiControl; - - typedef enum { - BORDER_COLOR_NORMAL = 0, - BASE_COLOR_NORMAL, - TEXT_COLOR_NORMAL, - BORDER_COLOR_FOCUSED, - BASE_COLOR_FOCUSED, - TEXT_COLOR_FOCUSED, - BORDER_COLOR_PRESSED, - BASE_COLOR_PRESSED, - TEXT_COLOR_PRESSED, - BORDER_COLOR_DISABLED, - BASE_COLOR_DISABLED, - TEXT_COLOR_DISABLED, - BORDER_WIDTH, - TEXT_PADDING, - TEXT_ALIGNMENT, - RESERVED - } GuiControlProperty; - - typedef enum { - TEXT_SIZE = 16, - TEXT_SPACING, - LINE_COLOR, - BACKGROUND_COLOR, - } GuiDefaultProperty; - - typedef enum { - GROUP_PADDING = 16, - } GuiToggleProperty; - - typedef enum { - SLIDER_WIDTH = 16, - SLIDER_PADDING - } GuiSliderProperty; - - typedef enum { - PROGRESS_PADDING = 16, - } GuiProgressBarProperty; - - typedef enum { - CHECK_PADDING = 16 - } GuiCheckBoxProperty; - - typedef enum { - COMBO_BUTTON_WIDTH = 16, - COMBO_BUTTON_PADDING - } GuiComboBoxProperty; - - typedef enum { - ARROW_PADDING = 16, - DROPDOWN_ITEMS_PADDING - } GuiDropdownBoxProperty; - - typedef enum { - TEXT_INNER_PADDING = 16, - TEXT_LINES_PADDING, - COLOR_SELECTED_FG, - COLOR_SELECTED_BG - } GuiTextBoxProperty; - - typedef enum { - SPIN_BUTTON_WIDTH = 16, - SPIN_BUTTON_PADDING, - } GuiSpinnerProperty; - - typedef enum { - ARROWS_SIZE = 16, - ARROWS_VISIBLE, - SCROLL_SLIDER_PADDING, - SCROLL_SLIDER_SIZE, - SCROLL_PADDING, - SCROLL_SPEED, - } GuiScrollBarProperty; - - typedef enum { - SCROLLBAR_LEFT_SIDE = 0, - SCROLLBAR_RIGHT_SIDE - } GuiScrollBarSide; - - typedef enum { - LIST_ITEMS_HEIGHT = 16, - LIST_ITEMS_PADDING, - SCROLLBAR_WIDTH, - SCROLLBAR_SIDE, - } GuiListViewProperty; - - typedef enum { - COLOR_SELECTOR_SIZE = 16, - HUEBAR_WIDTH, - HUEBAR_PADDING, - HUEBAR_SELECTOR_HEIGHT, - HUEBAR_SELECTOR_OVERFLOW - } GuiColorPickerProperty; - - typedef struct GuiTextBoxState { - int cursor; - int start; - int index; - int select; - } GuiTextBoxState; -]] -- Load bind entry ffi.cdef [[ - struct raylua_bind_entry { + struct rf_lua_bind_entry { const char *name; const char *proto; void *ptr; @@ -826,7 +1015,7 @@ ffi.cdef [[ ]] do - local entries = ffi.cast("struct raylua_bind_entry *", raylua.bind_entries) + local entries = ffi.cast("struct rf_lua_bind_entry *", raylua.bind_entries) local i = ffi.new("size_t", 0) local NULL = ffi.new("void *", nil) @@ -835,11 +1024,11 @@ do while entries[i].name ~= NULL do local name, proto = ffi.string(entries[i].name), ffi.string(entries[i].proto) - if rl[name] then + if rf[name] then print("RAYLUA: Warn: Duplicated FFI entry : " .. name) end - rl[name] = ffi.cast(proto, entries[i].ptr) + rf[name] = ffi.cast(proto, entries[i].ptr) i = i + 1 end @@ -848,53 +1037,48 @@ end -- colors local function new_color(r, g, b, a) - return ffi.new("Color", r, g, b, a) + return ffi.new("rf_color", r, g, b, a) end -rl.LIGHTGRAY = new_color(200, 200, 200, 255) -rl.GRAY = new_color(130, 130, 130, 255) -rl.DARKGRAY = new_color(80, 80, 80, 255) -rl.YELLOW = new_color(253, 249, 0, 255) -rl.GOLD = new_color(255, 203, 0, 255) -rl.ORANGE = new_color(255, 161, 0, 255) -rl.PINK = new_color(255, 109, 194, 255) -rl.RED = new_color(230, 41, 55, 255) -rl.MAROON = new_color(190, 33, 55, 255) -rl.GREEN = new_color(0, 228, 48, 255) -rl.LIME = new_color(0, 158, 47, 255) -rl.DARKGREEN = new_color(0, 117, 44, 255) -rl.SKYBLUE = new_color(102, 191, 255, 255) -rl.BLUE = new_color(0, 121, 241, 255) -rl.DARKBLUE = new_color(0, 82, 172, 255) -rl.PURPLE = new_color(200, 122, 255, 255) -rl.VIOLET = new_color(135, 60, 190, 255) -rl.DARKPURPLE = new_color(112, 31, 126, 255) -rl.BEIGE = new_color(211, 176, 131, 255) -rl.BROWN = new_color(127, 106, 79, 255) -rl.DARKBROWN = new_color(76, 63, 47, 255) -rl.WHITE = new_color(255, 255, 255, 255) -rl.BLACK = new_color(0, 0, 0, 255) -rl.BLANK = new_color(0, 0, 0, 0) -rl.MAGENTA = new_color(255, 0, 255, 255) -rl.RAYWHITE = new_color(245, 245, 245, 255) +rf.LIGHTGRAY = new_color(200, 200, 200, 255) +rf.GRAY = new_color(130, 130, 130, 255) +rf.DARKGRAY = new_color(80, 80, 80, 255) +rf.YELLOW = new_color(253, 249, 0, 255) +rf.GOLD = new_color(255, 203, 0, 255) +rf.ORANGE = new_color(255, 161, 0, 255) +rf.PINK = new_color(255, 109, 194, 255) +rf.RED = new_color(230, 41, 55, 255) +rf.MAROON = new_color(190, 33, 55, 255) +rf.GREEN = new_color(0, 228, 48, 255) +rf.LIME = new_color(0, 158, 47, 255) +rf.DARKGREEN = new_color(0, 117, 44, 255) +rf.SKYBLUE = new_color(102, 191, 255, 255) +rf.BLUE = new_color(0, 121, 241, 255) +rf.DARKBLUE = new_color(0, 82, 172, 255) +rf.PURPLE = new_color(200, 122, 255, 255) +rf.VIOLET = new_color(135, 60, 190, 255) +rf.DARKPURPLE = new_color(112, 31, 126, 255) +rf.BEIGE = new_color(211, 176, 131, 255) +rf.BROWN = new_color(127, 106, 79, 255) +rf.DARKBROWN = new_color(76, 63, 47, 255) +rf.WHITE = new_color(255, 255, 255, 255) +rf.BLACK = new_color(0, 0, 0, 255) +rf.BLANK = new_color(0, 0, 0, 0) +rf.MAGENTA = new_color(255, 0, 255, 255) +rf.RAYWHITE = new_color(245, 245, 245, 255) -rl.LOC_MAP_DIFFUSE = C.LOC_MAP_ALBEDO -rl.LOC_MAP_SPECULAR = C.LOC_MAP_METALNESS -rl.MAP_DIFFUSE = C.MAP_ALBEDO -rl.MAP_SPECULAR = C.MAP_METALNESS - -function rl.ref(obj) +function rf.ref(obj) return ffi.cast(ffi.typeof("$ *", obj), obj) end -rl.new = ffi.new +rf.new = ffi.new -rl.__index = function (self, key) +rf.__index = function (self, key) return C[key] end -rl.__newindex = function () - error "rl table is readonly" +rf.__newindex = function () + error "rf table is readonly" end -_G.rl = rl +_G.rf = rf diff --git a/src/raylua.c b/src/raylua.c index 72404aa..20041bd 100644 --- a/src/raylua.c +++ b/src/raylua.c @@ -20,21 +20,7 @@ #include #include -#include -#include - -#include -#include -#include - -#define RAYGUI_SUPPORT_ICONS -#define RAYGUI_IMPLEMENTATION -#define RAYGUI_STATIC -#include - -#define PHYSAC_IMPLEMENTATION -#define PHYSAC_NO_THREADS -#include +#include "lib/rayfork.h" #include "autogen/bind.c" #include "autogen/boot.c" @@ -59,7 +45,7 @@ void raylua_boot(lua_State *L, lua_CFunction loadfile, lua_CFunction listfiles, } lua_pushstring(L, "bind_entries"); - lua_pushlightuserdata(L, raylua_entries); + lua_pushlightuserdata(L, rayfork_entries); lua_settable(L, -3); lua_pushstring(L, "isrepl"); diff --git a/src/raylua.lua b/src/raylua.lua index 3af0a53..89fc06e 100644 --- a/src/raylua.lua +++ b/src/raylua.lua @@ -16,7 +16,7 @@ local load = loadstring -raylua.version = "v3.1-dev1" +raylua.version = "v0.9 rayfork" function raylua.repl() print("> raylua " .. raylua.version .. " <") diff --git a/src/raylua_e.c b/src/raylua_e.c index 376b543..40f8a61 100644 --- a/src/raylua_e.c +++ b/src/raylua_e.c @@ -23,7 +23,7 @@ #include #include -#include +#include "lib/rayfork.h" #include "raylua.h" #include "lib/miniz.h" @@ -103,7 +103,7 @@ unsigned char *raylua_loadFileData(const char *path, unsigned int *out_size) } size_t size = stat.m_uncomp_size; - unsigned char *buffer = RL_MALLOC(size); + unsigned char *buffer = malloc(size); if (buffer == NULL) { printf("RAYLUA: WARN: Can't allocate file buffer for '%s'.", path); return NULL; @@ -130,7 +130,7 @@ char *raylua_loadFileText(const char *path) } size_t size = stat.m_uncomp_size; - char *buffer = RL_MALLOC(size + 1); + char *buffer = malloc(size + 1); if (buffer == NULL) { printf("RAYLUA: WARN: Can't allocate file buffer for '%s'.", path); return NULL; @@ -174,11 +174,6 @@ int main(int argc, const char **argv) lua_setglobal(L, "arg"); - SetFilesystemOverride((FilesystemOverride){ - .loadFileData = &raylua_loadFileData, - .loadFileText = &raylua_loadFileText, - }); - FILE *self = raylua_open_self(argv[0]); if (self == NULL) { diff --git a/tools/api.h b/tools/api.h deleted file mode 100644 index cd36fb6..0000000 --- a/tools/api.h +++ /dev/null @@ -1,449 +0,0 @@ -void InitWindow(int width, int height, const char *title) -bool WindowShouldClose(void) -void CloseWindow(void) -bool IsWindowReady(void) -bool IsWindowMinimized(void) -bool IsWindowMaximized(void) -bool IsWindowFocused(void) -bool IsWindowResized(void) -bool IsWindowHidden(void) -bool IsWindowFullscreen(void) -void ToggleFullscreen(void) -void UnhideWindow(void) -void HideWindow(void) -void DecorateWindow(void) -void UndecorateWindow(void) -void MaximizeWindow(void) -void RestoreWindow(void) -void SetWindowIcon(Image image) -void SetWindowTitle(const char *title) -void SetWindowPosition(int x, int y) -void SetWindowMonitor(int monitor) -void SetWindowMinSize(int width, int height) -void SetWindowSize(int width, int height) -void *GetWindowHandle(void) -int GetScreenWidth(void) -int GetScreenHeight(void) -int GetMonitorCount(void) -int GetMonitorWidth(int monitor) -int GetMonitorHeight(int monitor) -int GetMonitorPhysicalWidth(int monitor) -int GetMonitorPhysicalHeight(int monitor) -int GetMonitorRefreshRate(int monitor) -Vector2 GetWindowPosition(void) -Vector2 GetWindowScaleDPI(void) -const char *GetMonitorName(int monitor) -const char *GetClipboardText(void) -void SetClipboardText(const char *text) -void ShowCursor(void) -void HideCursor(void) -bool IsCursorHidden(void) -void EnableCursor(void) -void DisableCursor(void) -bool IsCursorOnScreen(void) -void ClearBackground(Color color) -void BeginDrawing(void) -void EndDrawing(void) -void BeginMode2D(Camera2D camera) -void EndMode2D(void) -void BeginMode3D(Camera3D camera) -void EndMode3D(void) -void BeginTextureMode(RenderTexture2D target) -void EndTextureMode(void) -void BeginScissorMode(int x, int y, int width, int height) -void EndScissorMode(void) -Ray GetMouseRay(Vector2 mousePosition, Camera camera) -Matrix GetCameraMatrix(Camera camera) -Matrix GetCameraMatrix2D(Camera2D camera) -Vector2 GetWorldToScreen(Vector3 position, Camera camera) -Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height) -Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) -Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) -void SetTargetFPS(int fps) -int GetFPS(void) -float GetFrameTime(void) -double GetTime(void) -int ColorToInt(Color color) -Vector4 ColorNormalize(Color color) -Color ColorFromNormalized(Vector4 normalized) -Vector3 ColorToHSV(Color color) -Color ColorFromHSV(Vector3 hsv) -Color GetColor(int hexValue) -Color Fade(Color color, float alpha) -void SetConfigFlags(unsigned int flags) -void SetTraceLogLevel(int logType) -void SetTraceLogExit(int logType) -void SetTraceLogCallback(TraceLogCallback callback) -void TraceLog(int logType, const char *text, ...) -void TakeScreenshot(const char *fileName) -int GetRandomValue(int min, int max) -unsigned char *LoadFileData(const char *fileName, int *bytesRead) -void SaveFileData(const char *fileName, void *data, int bytesToWrite) -char *LoadFileText(const char *fileName) -void SaveFileText(const char *fileName, char *text) -bool FileExists(const char *fileName) -bool IsFileExtension(const char *fileName, const char *ext) -bool DirectoryExists(const char *dirPath) -const char *GetExtension(const char *fileName) -const char *GetFileName(const char *filePath) -const char *GetFileNameWithoutExt(const char *filePath) -const char *GetDirectoryPath(const char *filePath) -const char *GetPrevDirectoryPath(const char *dirPath) -const char *GetWorkingDirectory(void) -char **GetDirectoryFiles(const char *dirPath, int *count) -void ClearDirectoryFiles(void) -bool ChangeDirectory(const char *dir) -bool IsFileDropped(void) -char **GetDroppedFiles(int *count) -void ClearDroppedFiles(void) -long GetFileModTime(const char *fileName) -unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength) -unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength) -void SaveStorageValue(int position, int value) -int LoadStorageValue(int position) -void OpenURL(const char *url) -bool IsKeyPressed(int key) -bool IsKeyDown(int key) -bool IsKeyReleased(int key) -bool IsKeyUp(int key) -void SetExitKey(int key) -int GetKeyPressed(void) -bool IsGamepadAvailable(int gamepad) -bool IsGamepadName(int gamepad, const char *name) -const char *GetGamepadName(int gamepad) -bool IsGamepadButtonPressed(int gamepad, int button) -bool IsGamepadButtonDown(int gamepad, int button) -bool IsGamepadButtonReleased(int gamepad, int button) -bool IsGamepadButtonUp(int gamepad, int button) -int GetGamepadButtonPressed(void) -int GetGamepadAxisCount(int gamepad) -float GetGamepadAxisMovement(int gamepad, int axis) -bool IsMouseButtonPressed(int button) -bool IsMouseButtonDown(int button) -bool IsMouseButtonReleased(int button) -bool IsMouseButtonUp(int button) -int GetMouseX(void) -int GetMouseY(void) -Vector2 GetMousePosition(void) -void SetMousePosition(int x, int y) -void SetMouseOffset(int offsetX, int offsetY) -void SetMouseScale(float scaleX, float scaleY) -int GetMouseWheelMove(void) -int GetTouchX(void) -int GetTouchY(void) -Vector2 GetTouchPosition(int index) -void SetGesturesEnabled(unsigned int gestureFlags) -bool IsGestureDetected(int gesture) -int GetGestureDetected(void) -int GetTouchPointsCount(void) -float GetGestureHoldDuration(void) -Vector2 GetGestureDragVector(void) -float GetGestureDragAngle(void) -Vector2 GetGesturePinchVector(void) -float GetGesturePinchAngle(void) -void SetCameraMode(Camera camera, int mode) -void UpdateCamera(Camera *camera) -void SetCameraPanControl(int panKey) -void SetCameraAltControl(int altKey) -void SetCameraSmoothZoomControl(int szKey) -void SetCameraMoveControls(int frontKey, int backKey, int rightKey, int leftKey, int upKey, int downKey) -void DrawPixel(int posX, int posY, Color color) -void DrawPixelV(Vector2 position, Color color) -void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color) -void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) -void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) -void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) -void DrawLineStrip(Vector2 *points, int numPoints, Color color) -void DrawCircle(int centerX, int centerY, float radius, Color color) -void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle, int segments, Color color) -void DrawCircleSectorLines(Vector2 center, float radius, int startAngle, int endAngle, int segments, Color color) -void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) -void DrawCircleV(Vector2 center, float radius, Color color) -void DrawCircleLines(int centerX, int centerY, float radius, Color color) -void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color) -void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color) -void DrawRing(Vector2 center, float innerRadius, float outerRadius, int startAngle, int endAngle, int segments, Color color) -void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, int startAngle, int endAngle, int segments, Color color) -void DrawRectangle(int posX, int posY, int width, int height, Color color) -void DrawRectangleV(Vector2 position, Vector2 size, Color color) -void DrawRectangleRec(Rectangle rec, Color color) -void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color) -void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) -void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2) -void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) -void DrawRectangleLines(int posX, int posY, int width, int height, Color color) -void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color) -void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color) -void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int lineThick, Color color) -void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) -void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) -void DrawTriangleFan(Vector2 *points, int numPoints, Color color) -void DrawTriangleStrip(Vector2 *points, int pointsCount, Color color) -void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color) -void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color) -bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) -bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2) -bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) -Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) -bool CheckCollisionPointRec(Vector2 point, Rectangle rec) -bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius) -bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3) -Image LoadImage(const char *fileName) -Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize) -Image LoadImageAnim(const char *fileName, int *frames) -void ExportImage(Image image, const char *fileName) -void ExportImageAsCode(Image image, const char *fileName) -Texture2D LoadTexture(const char *fileName) -Texture2D LoadTextureFromImage(Image image) -TextureCubemap LoadTextureCubemap(Image image, int layoutType) -RenderTexture2D LoadRenderTexture(int width, int height) -void UnloadImage(Image image) -void UnloadTexture(Texture2D texture) -void UnloadRenderTexture(RenderTexture2D target) -Color *GetImageData(Image image) -Vector4 *GetImageDataNormalized(Image image) -Rectangle GetImageAlphaBorder(Image image, float threshold) -int GetPixelDataSize(int width, int height, int format) -Image GetTextureData(Texture2D texture) -Image GetScreenData(void) -void UpdateTexture(Texture2D texture, const void *pixels) -void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels) -Image ImageCopy(Image image) -Image ImageFromImage(Image image, Rectangle rec) -void ImageToPOT(Image *image, Color fillColor) -void ImageFormat(Image *image, int newFormat) -void ImageAlphaMask(Image *image, Image alphaMask) -void ImageAlphaClear(Image *image, Color color, float threshold) -void ImageAlphaCrop(Image *image, float threshold) -void ImageAlphaPremultiply(Image *image) -void ImageCrop(Image *image, Rectangle crop) -void ImageResize(Image *image, int newWidth, int newHeight) -void ImageResizeNN(Image *image, int newWidth,int newHeight) -void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color color) -void ImageMipmaps(Image *image) -void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) -Color *GetImagePalette(Image image, int maxPaletteSize, int *extractCount) -Image ImageText(const char *text, int fontSize, Color color) -Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) -void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) -void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color) -void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color) -void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) -void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color) -void ImageClearBackground(Image *dst, Color color) -void ImageDrawPixel(Image *dst, int posX, int posY, Color color) -void ImageDrawPixelV(Image *dst, Vector2 position, Color color) -void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) -void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) -void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color) -void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color) -void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color) -void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) -void ImageFlipVertical(Image *image) -void ImageFlipHorizontal(Image *image) -void ImageRotateCW(Image *image) -void ImageRotateCCW(Image *image) -void ImageColorTint(Image *image, Color color) -void ImageColorInvert(Image *image) -void ImageColorGrayscale(Image *image) -void ImageColorContrast(Image *image, float contrast) -void ImageColorBrightness(Image *image, int brightness) -void ImageColorReplace(Image *image, Color color, Color replace) -Image GenImageColor(int width, int height, Color color) -Image GenImageGradientV(int width, int height, Color top, Color bottom) -Image GenImageGradientH(int width, int height, Color left, Color right) -Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer) -Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2) -Image GenImageWhiteNoise(int width, int height, float factor) -Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) -Image GenImageCellular(int width, int height, int tileSize) -void GenTextureMipmaps(Texture2D *texture) -void SetTextureFilter(Texture2D texture, int filterMode) -void SetTextureWrap(Texture2D texture, int wrapMode) -void DrawTexture(Texture2D texture, int posX, int posY, Color tint) -void DrawTextureV(Texture2D texture, Vector2 position, Color tint) -void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint) -void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint) -void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint) -void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, Color tint) -void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destRec, Vector2 origin, float rotation, Color tint) -Font GetFontDefault(void) -Font LoadFont(const char *fileName) -Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount) -Font LoadFontFromImage(Image image, Color key, int firstChar) -CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, int type) -Image GenImageFontAtlas(const CharInfo *chars, Rectangle **recs, int charsCount, int fontSize, int padding, int packMethod) -void UnloadFont(Font font) -void DrawFPS(int posX, int posY) -void DrawText(const char *text, int posX, int posY, int fontSize, Color color) -void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) -void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint) -void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint) -void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float scale, Color tint) -int MeasureText(const char *text, int fontSize) -Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing) -int GetGlyphIndex(Font font, int codepoint) -int TextCopy(char *dst, const char *src) -bool TextIsEqual(const char *text1, const char *text2) -unsigned int TextLength(const char *text) -const char *TextFormat(const char *text, ...) -const char *TextSubtext(const char *text, int position, int length) -char *TextReplace(char *text, const char *replace, const char *by) -char *TextInsert(const char *text, const char *insert, int position) -const char *TextJoin(const char **textList, int count, const char *delimiter) -const char **TextSplit(const char *text, char delimiter, int *count) -void TextAppend(char *text, const char *append, int *position) -int TextFindIndex(const char *text, const char *find) -const char *TextToUpper(const char *text) -const char *TextToLower(const char *text) -const char *TextToPascal(const char *text) -int TextToInteger(const char *text) -char *TextToUtf8(int *codepoints, int length) -int *GetCodepoints(const char *text, int *count) -int GetCodepointsCount(const char *text) -int GetNextCodepoint(const char *text, int *bytesProcessed) -const char *CodepointToUtf8(int codepoint, int *byteLength) -void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color) -void DrawPoint3D(Vector3 position, Color color) -void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color) -void DrawCube(Vector3 position, float width, float height, float length, Color color) -void DrawCubeV(Vector3 position, Vector3 size, Color color) -void DrawCubeWires(Vector3 position, float width, float height, float length, Color color) -void DrawCubeWiresV(Vector3 position, Vector3 size, Color color) -void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color) -void DrawSphere(Vector3 centerPos, float radius, Color color) -void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color) -void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color) -void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color) -void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color) -void DrawPlane(Vector3 centerPos, Vector2 size, Color color) -void DrawRay(Ray ray, Color color) -void DrawGrid(int slices, float spacing) -void DrawGizmo(Vector3 position) -Model LoadModel(const char *fileName) -Model LoadModelFromMesh(Mesh mesh) -void UnloadModel(Model model) -Mesh *LoadMeshes(const char *fileName, int *meshCount) -void ExportMesh(Mesh mesh, const char *fileName) -void UnloadMesh(Mesh mesh) -Material *LoadMaterials(const char *fileName, int *materialCount) -Material LoadMaterialDefault(void) -void UnloadMaterial(Material material) -void SetMaterialTexture(Material *material, int mapType, Texture2D texture) -void SetModelMeshMaterial(Model *model, int meshId, int materialId) -ModelAnimation *LoadModelAnimations(const char *fileName, int *animsCount) -void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) -void UnloadModelAnimation(ModelAnimation anim) -bool IsModelAnimationValid(Model model, ModelAnimation anim) -Mesh GenMeshPoly(int sides, float radius) -Mesh GenMeshPlane(float width, float length, int resX, int resZ) -Mesh GenMeshCube(float width, float height, float length) -Mesh GenMeshSphere(float radius, int rings, int slices) -Mesh GenMeshHemiSphere(float radius, int rings, int slices) -Mesh GenMeshCylinder(float radius, float height, int slices) -Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) -Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) -Mesh GenMeshHeightmap(Image heightmap, Vector3 size) -Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) -BoundingBox MeshBoundingBox(Mesh mesh) -void MeshTangents(Mesh *mesh) -void MeshBinormals(Mesh *mesh) -void MeshNormalsSmooth(Mesh *mesh) -void DrawModel(Model model, Vector3 position, float scale, Color tint) -void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) -void DrawModelWires(Model model, Vector3 position, float scale, Color tint) -void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) -void DrawBoundingBox(BoundingBox box, Color color) -void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint) -void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vector3 center, float size, Color tint) -bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB) -bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2) -bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius) -bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint) -bool CheckCollisionRayBox(Ray ray, BoundingBox box) -RayHitInfo GetCollisionRayModel(Ray ray, Model model) -RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3) -RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight) -Shader LoadShader(const char *vsFileName, const char *fsFileName) -Shader LoadShaderCode(const char *vsCode, const char *fsCode) -void UnloadShader(Shader shader) -Shader GetShaderDefault(void) -Texture2D GetTextureDefault(void) -Texture2D GetShapesTexture(void) -Rectangle GetShapesTextureRec(void) -void SetShapesTexture(Texture2D texture, Rectangle source) -int GetShaderLocation(Shader shader, const char *uniformName) -void SetShaderValue(Shader shader, int uniformLoc, const void *value, int uniformType) -void SetShaderValueV(Shader shader, int uniformLoc, const void *value, int uniformType, int count) -void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat) -void SetShaderValueTexture(Shader shader, int uniformLoc, Texture2D texture) -void SetMatrixProjection(Matrix proj) -void SetMatrixModelview(Matrix view) -Matrix GetMatrixModelview(void) -Matrix GetMatrixProjection(void) -Texture2D GenTextureCubemap(Shader shader, Texture2D map, int size) -Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size) -Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) -Texture2D GenTextureBRDF(Shader shader, int size) -void BeginShaderMode(Shader shader) -void EndShaderMode(void) -void BeginBlendMode(int mode) -void EndBlendMode(void) -void InitVrSimulator(void) -void CloseVrSimulator(void) -void UpdateVrTracking(Camera *camera) -void SetVrConfiguration(VrDeviceInfo info, Shader distortion) -bool IsVrSimulatorReady(void) -void ToggleVrMode(void) -void BeginVrDrawing(void) -void EndVrDrawing(void) -void InitAudioDevice(void) -void CloseAudioDevice(void) -bool IsAudioDeviceReady(void) -void SetMasterVolume(float volume) -Wave LoadWave(const char *fileName) -Sound LoadSound(const char *fileName) -Sound LoadSoundFromWave(Wave wave) -void UpdateSound(Sound sound, const void *data, int samplesCount) -void UnloadWave(Wave wave) -void UnloadSound(Sound sound) -void ExportWave(Wave wave, const char *fileName) -void ExportWaveAsCode(Wave wave, const char *fileName) -void PlaySound(Sound sound) -void StopSound(Sound sound) -void PauseSound(Sound sound) -void ResumeSound(Sound sound) -void PlaySoundMulti(Sound sound) -void StopSoundMulti(void) -int GetSoundsPlaying(void) -bool IsSoundPlaying(Sound sound) -void SetSoundVolume(Sound sound, float volume) -void SetSoundPitch(Sound sound, float pitch) -void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) -Wave WaveCopy(Wave wave) -void WaveCrop(Wave *wave, int initSample, int finalSample) -float *GetWaveData(Wave wave) -Music LoadMusicStream(const char *fileName) -void UnloadMusicStream(Music music) -void PlayMusicStream(Music music) -void UpdateMusicStream(Music music) -void StopMusicStream(Music music) -void PauseMusicStream(Music music) -void ResumeMusicStream(Music music) -bool IsMusicPlaying(Music music) -void SetMusicVolume(Music music, float volume) -void SetMusicPitch(Music music, float pitch) -float GetMusicTimeLength(Music music) -float GetMusicTimePlayed(Music music) -AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) -void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) -void CloseAudioStream(AudioStream stream) -bool IsAudioStreamProcessed(AudioStream stream) -void PlayAudioStream(AudioStream stream) -void PauseAudioStream(AudioStream stream) -void ResumeAudioStream(AudioStream stream) -bool IsAudioStreamPlaying(AudioStream stream) -void StopAudioStream(AudioStream stream) -void SetAudioStreamVolume(AudioStream stream, float volume) -void SetAudioStreamPitch(AudioStream stream, float pitch) -void SetAudioStreamBufferSizeDefault(int size) diff --git a/tools/easings.h b/tools/easings.h deleted file mode 100644 index 0e9dc28..0000000 --- a/tools/easings.h +++ /dev/null @@ -1,28 +0,0 @@ -float EaseLinearNone(float t, float b, float c, float d) -float EaseLinearIn(float t, float b, float c, float d) -float EaseLinearOut(float t, float b, float c, float d) -float EaseLinearInOut(float t,float b, float c, float d) -float EaseSineIn(float t, float b, float c, float d) -float EaseSineOut(float t, float b, float c, float d) -float EaseSineInOut(float t, float b, float c, float d) -float EaseCircIn(float t, float b, float c, float d) -float EaseCircOut(float t, float b, float c, float d) -float EaseCircInOut(float t, float b, float c, float d) -float EaseCubicIn(float t, float b, float c, float d) -float EaseCubicOut(float t, float b, float c, float d) -float EaseCubicInOut(float t, float b, float c, float d) -float EaseQuadIn(float t, float b, float c, float d) -float EaseQuadOut(float t, float b, float c, float d) -float EaseQuadInOut(float t, float b, float c, float d) -float EaseExpoIn(float t, float b, float c, float d) -float EaseExpoOut(float t, float b, float c, float d) -float EaseExpoInOut(float t, float b, float c, float d) -float EaseBackIn(float t, float b, float c, float d) -float EaseBackOut(float t, float b, float c, float d) -float EaseBackInOut(float t, float b, float c, float d) -float EaseBounceOut(float t, float b, float c, float d) -float EaseBounceIn(float t, float b, float c, float d) -float EaseBounceInOut(float t, float b, float c, float d) -float EaseElasticIn(float t, float b, float c, float d) -float EaseElasticOut(float t, float b, float c, float d) -float EaseElasticInOut(float t, float b, float c, float d) diff --git a/tools/genbind.lua b/tools/genbind.lua index 6dc3176..1c9ab02 100644 --- a/tools/genbind.lua +++ b/tools/genbind.lua @@ -1,30 +1,18 @@ local keywords = { "int", "long", "short", "char", "float", "double", "uint8_t", "uint16_t", "uint32_t", "uint64_t", - "const", "unsigned", "register", + "const", "unsigned", "register", "va_list"; "void", "intptr_t", "bool" } -local structs = { - "Vector2", "Vector3", "Vector4", "Quaternion", - "Matrix", "Color", "Rectangle", "Image", "Texture", "Texture2D", - "RenderTexture", "NPatchInfo", "CharInfo", "Font", - "Camera", "Camera2D", "Mesh", "Shader", "MaterialMap", - "Material", "Model", "Transform", "BoneInfo", "ModelAnimation", - "Ray", "RayHitInfo", "BoundingBox", "Wave", "Sound", "Music", - "AudioStream", "VrDeviceInfo", "Camera3D", "RenderTexture2D", - "TextureCubemap", "TraceLogCallback", "PhysicsBody", - "GestureEvent", "GuiStyle", "GuiTextBoxState" -} - local functions = {} local proto = {} local counter = 0 local custom_support = { - ["rlgl"] = function (line) - return line:gsub("[%s*]+(rl%w+)", function (part) + ["rayfork"] = function (line) + return line:gsub("(rf_[^( ]+)(%()", function (part) functions[#functions + 1] = part counter = counter + 1 @@ -32,16 +20,16 @@ local custom_support = { print("WARN: Multiple matches for: " .. line) end - return "(*)" + return "(*)(" end) end } local file = io.open(arg[1], "wb") -local modules = { "api" } +local modules = { } -for i=2,#arg do - modules[i] = arg[i] +for i=1,#arg do + modules[i] = arg[i + 1] end for _,modname in ipairs(modules) do @@ -50,31 +38,16 @@ for _,modname in ipairs(modules) do line = custom_support[modname](line) end - line = line:gsub("(%W)([%l%d][%w_]*)", function (before, part) - for i,keyword in ipairs(keywords) do + counter = 0 + + line = line:gsub("[^ *(),]+", function (part) + for _,keyword in pairs(keywords) do if part == keyword then - return before .. part - end - end - - return before - end) - - line = line:gsub("%u%w+", function (part) - for i,struct in ipairs(structs) do - if part == struct then return part end end - functions[#functions + 1] = part - counter = counter + 1 - - if count == 2 then - print("WARN: Multiple matches for: " .. line) - end - - return "(*)" + return part:sub(0, 3) == "rf_" and part or "" end) -- Strip spaces @@ -89,13 +62,13 @@ assert(#proto == #functions, "Mismatching proto and function count : " .. #proto .. " ~= " .. #functions) file:write [[ -struct raylua_bind_entry { +struct rf_lua_bind_entry { const char *name; const char *proto; void *ptr; }; -struct raylua_bind_entry raylua_entries[] = { +struct rf_lua_bind_entry rayfork_entries[] = { ]] for i=1,#proto do diff --git a/tools/gestures.h b/tools/gestures.h deleted file mode 100644 index 0407ec1..0000000 --- a/tools/gestures.h +++ /dev/null @@ -1,2 +0,0 @@ -void ProcessGestureEvent(GestureEvent event) -void UpdateGestures(void) diff --git a/tools/rayfork.h b/tools/rayfork.h new file mode 100644 index 0000000..540fd11 --- /dev/null +++ b/tools/rayfork.h @@ -0,0 +1,546 @@ +void* rf_calloc_wrapper(rf_allocator allocator, rf_int amount, rf_int size); +void* rf_libc_allocator_wrapper(struct rf_allocator* this_allocator, rf_source_location source_location, rf_allocator_mode mode, rf_allocator_args args); +rf_int rf_libc_get_file_size(void* user_data, const char* filename); +bool rf_libc_load_file_into_buffer(void* user_data, const char* filename, void* dst, rf_int dst_size); +rf_recorded_error rf_get_last_recorded_error(); +const char* rf_log_type_string(rf_log_type); +void rf_set_logger(rf_logger logger); +void rf_set_logger_filter(rf_log_type); +void rf_libc_printf_logger(struct rf_logger* logger, rf_source_location source_location, rf_log_type log_type, const char* msg, rf_error_type error_type, va_list args); +rf_int rf_libc_rand_wrapper(rf_int min, rf_int max); +rf_decoded_rune rf_decode_utf8_char(const char* text, rf_int len); +rf_decoded_utf8_stats rf_count_utf8_chars(const char* text, rf_int len); +rf_decoded_string rf_decode_utf8_to_buffer(const char* text, rf_int len, rf_rune* dst, rf_int dst_size); +rf_decoded_string rf_decode_utf8(const char* text, rf_int len, rf_allocator allocator); +float rf_next_pot(float it); +rf_vec2 rf_center_to_screen(float w, float h); +rf_vec2 rf_center_to_object(rf_sizef center_this, rf_rec to_this); +float rf_clamp(float value, float min, float max); +float rf_lerp(float start, float end, float amount); +rf_vec2 rf_vec2_add(rf_vec2 v1, rf_vec2 v2); +rf_vec2 rf_vec2_sub(rf_vec2 v1, rf_vec2 v2); +float rf_vec2_len(rf_vec2 v); +float rf_vec2_dot_product(rf_vec2 v1, rf_vec2 v2); +float rf_vec2_distance(rf_vec2 v1, rf_vec2 v2); +float rf_vec2_angle(rf_vec2 v1, rf_vec2 v2); +rf_vec2 rf_vec2_scale(rf_vec2 v, float scale); +rf_vec2 rf_vec2_mul_v(rf_vec2 v1, rf_vec2 v2); +rf_vec2 rf_vec2_negate(rf_vec2 v); +rf_vec2 rf_vec2_div(rf_vec2 v, float div); +rf_vec2 rf_vec2_div_v(rf_vec2 v1, rf_vec2 v2); +rf_vec2 rf_vec2_normalize(rf_vec2 v); +rf_vec2 rf_vec2_lerp(rf_vec2 v1, rf_vec2 v2, float amount); +rf_vec3 rf_vec3_add(rf_vec3 v1, rf_vec3 v2); +rf_vec3 rf_vec3_sub(rf_vec3 v1, rf_vec3 v2); +rf_vec3 rf_vec3_mul(rf_vec3 v, float scalar); +rf_vec3 rf_vec3_mul_v(rf_vec3 v1, rf_vec3 v2); +rf_vec3 rf_vec3_cross_product(rf_vec3 v1, rf_vec3 v2); +rf_vec3 rf_vec3_perpendicular(rf_vec3 v); +float rf_vec3_len(rf_vec3 v); +float rf_vec3_dot_product(rf_vec3 v1, rf_vec3 v2); +float rf_vec3_distance(rf_vec3 v1, rf_vec3 v2); +rf_vec3 rf_vec3_scale(rf_vec3 v, float scale); +rf_vec3 rf_vec3_negate(rf_vec3 v); +rf_vec3 rf_vec3_div(rf_vec3 v, float div); +rf_vec3 rf_vec3_div_v(rf_vec3 v1, rf_vec3 v2); +rf_vec3 rf_vec3_normalize(rf_vec3 v); +void rf_vec3_ortho_normalize(rf_vec3* v1, rf_vec3* v2); +rf_vec3 rf_vec3_transform(rf_vec3 v, rf_mat mat); +rf_vec3 rf_vec3_rotate_by_quaternion(rf_vec3 v, rf_quaternion q); +rf_vec3 rf_vec3_lerp(rf_vec3 v1, rf_vec3 v2, float amount); +rf_vec3 rf_vec3_reflect(rf_vec3 v, rf_vec3 normal); +rf_vec3 rf_vec3_min(rf_vec3 v1, rf_vec3 v2); +rf_vec3 rf_vec3_max(rf_vec3 v1, rf_vec3 v2); +rf_vec3 rf_vec3_barycenter(rf_vec3 p, rf_vec3 a, rf_vec3 b, rf_vec3 c); +float rf_mat_determinant(rf_mat mat); +float rf_mat_trace(rf_mat mat); +rf_mat rf_mat_transpose(rf_mat mat); +rf_mat rf_mat_invert(rf_mat mat); +rf_mat rf_mat_normalize(rf_mat mat); +rf_mat rf_mat_identity(void); +rf_mat rf_mat_add(rf_mat left, rf_mat right); +rf_mat rf_mat_sub(rf_mat left, rf_mat right); +rf_mat rf_mat_translate(float x, float y, float z); +rf_mat rf_mat_rotate(rf_vec3 axis, float angle); +rf_mat rf_mat_rotate_xyz(rf_vec3 ang); +rf_mat rf_mat_rotate_x(float angle); +rf_mat rf_mat_rotate_y(float angle); +rf_mat rf_mat_rotate_z(float angle); +rf_mat rf_mat_scale(float x, float y, float z); +rf_mat rf_mat_mul(rf_mat left, rf_mat right); +rf_mat rf_mat_frustum(double left, double right, double bottom, double top, double near_val, double far_val); +rf_mat rf_mat_perspective(double fovy, double aspect, double near_val, double far_val); +rf_mat rf_mat_ortho(double left, double right, double bottom, double top, double near_val, double far_val); +rf_mat rf_mat_look_at(rf_vec3 eye, rf_vec3 target, rf_vec3 up); +rf_float16 rf_mat_to_float16(rf_mat mat); +rf_quaternion rf_quaternion_identity(void); +float rf_quaternion_len(rf_quaternion q); +rf_quaternion rf_quaternion_normalize(rf_quaternion q); +rf_quaternion rf_quaternion_invert(rf_quaternion q); +rf_quaternion rf_quaternion_mul(rf_quaternion q1, rf_quaternion q2); +rf_quaternion rf_quaternion_lerp(rf_quaternion q1, rf_quaternion q2, float amount); +rf_quaternion rf_quaternion_nlerp(rf_quaternion q1, rf_quaternion q2, float amount); +rf_quaternion rf_quaternion_slerp(rf_quaternion q1, rf_quaternion q2, float amount); +rf_quaternion rf_quaternion_from_vector3_to_vector3(rf_vec3 from, rf_vec3 to); +rf_quaternion rf_quaternion_from_matrix(rf_mat mat); +rf_mat rf_quaternion_to_matrix(rf_quaternion q); +rf_quaternion rf_quaternion_from_axis_angle(rf_vec3 axis, float angle); +void rf_quaternion_to_axis_angle(rf_quaternion q, rf_vec3* outAxis, float* outAngle); +rf_quaternion rf_quaternion_from_euler(float roll, float pitch, float yaw); +rf_vec3 rf_quaternion_to_euler(rf_quaternion q); +rf_quaternion rf_quaternion_transform(rf_quaternion q, rf_mat mat); +bool rf_check_collision_recs(rf_rec rec1, rf_rec rec2); +bool rf_check_collision_circles(rf_vec2 center1, float radius1, rf_vec2 center2, float radius2); +bool rf_check_collision_circle_rec(rf_vec2 center, float radius, rf_rec rec); +bool rf_check_collision_point_rec(rf_vec2 point, rf_rec rec); +bool rf_check_collision_point_circle(rf_vec2 point, rf_vec2 center, float radius); +bool rf_check_collision_point_triangle(rf_vec2 point, rf_vec2 p1, rf_vec2 p2, rf_vec2 p3); +rf_rec rf_get_collision_rec(rf_rec rec1, rf_rec rec2); +bool rf_check_collision_spheres(rf_vec3 center_a, float radius_a, rf_vec3 center_b, float radius_b); +bool rf_check_collision_boxes(rf_bounding_box box1, rf_bounding_box box2); +bool rf_check_collision_box_sphere(rf_bounding_box box, rf_vec3 center, float radius); +bool rf_check_collision_ray_sphere(rf_ray ray, rf_vec3 center, float radius); +bool rf_check_collision_ray_sphere_ex(rf_ray ray, rf_vec3 center, float radius, rf_vec3* collision_point); +bool rf_check_collision_ray_box(rf_ray ray, rf_bounding_box box); +rf_ray_hit_info rf_collision_ray_model(rf_ray ray, struct rf_model model); +rf_ray_hit_info rf_collision_ray_triangle(rf_ray ray, rf_vec3 p1, rf_vec3 p2, rf_vec3 p3); +rf_ray_hit_info rf_collision_ray_ground(rf_ray ray, float ground_height); +int rf_get_size_base64(const unsigned char* input); +rf_base64_output rf_decode_base64(const unsigned char* input, rf_allocator allocator); +const char* rf_pixel_format_string(rf_pixel_format format); +bool rf_is_uncompressed_format(rf_pixel_format format); +bool rf_is_compressed_format(rf_pixel_format format); +int rf_bits_per_pixel(rf_pixel_format format); +int rf_bytes_per_pixel(rf_uncompressed_pixel_format format); +int rf_pixel_buffer_size(int width, int height, rf_pixel_format format); +bool rf_format_pixels_to_normalized(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, rf_vec4* dst, rf_int dst_size); +bool rf_format_pixels_to_rgba32(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, rf_color* dst, rf_int dst_size); +bool rf_format_pixels(const void* src, rf_int src_size, rf_uncompressed_pixel_format src_format, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format); +rf_vec4 rf_format_one_pixel_to_normalized(const void* src, rf_uncompressed_pixel_format src_format); +rf_color rf_format_one_pixel_to_rgba32(const void* src, rf_uncompressed_pixel_format src_format); +void rf_format_one_pixel(const void* src, rf_uncompressed_pixel_format src_format, void* dst, rf_uncompressed_pixel_format dst_format); +bool rf_color_match_rgb(rf_color a, rf_color b); +bool rf_color_match(rf_color a, rf_color b); +int rf_color_to_int(rf_color color); +rf_vec4 rf_color_normalize(rf_color color); +rf_color rf_color_from_normalized(rf_vec4 normalized); +rf_vec3 rf_color_to_hsv(rf_color color); +rf_color rf_color_from_hsv(rf_vec3 hsv); +rf_color rf_color_from_int(int hex_value); +rf_color rf_fade(rf_color color, float alpha); +rf_vec3 rf_unproject(rf_vec3 source, rf_mat proj, rf_mat view); +rf_ray rf_get_mouse_ray(rf_sizei screen_size, rf_vec2 mouse_position, rf_camera3d camera); +rf_mat rf_get_camera_matrix(rf_camera3d camera); +rf_mat rf_get_camera_matrix2d(rf_camera2d camera); +rf_vec2 rf_get_world_to_screen(rf_sizei screen_size, rf_vec3 position, rf_camera3d camera); +rf_vec2 rf_get_world_to_screen2d(rf_vec2 position, rf_camera2d camera); +rf_vec2 rf_get_screen_to_world2d(rf_vec2 position, rf_camera2d camera); +void rf_set_camera3d_mode(rf_camera3d_state* state, rf_camera3d camera, rf_builtin_camera3d_mode mode); +void rf_update_camera3d(rf_camera3d* camera, rf_camera3d_state* state, rf_input_state_for_update_camera input_state); +int rf_image_size(rf_image image); +int rf_image_size_in_format(rf_image image, rf_pixel_format format); +bool rf_image_get_pixels_as_rgba32_to_buffer(rf_image image, rf_color* dst, rf_int dst_size); +bool rf_image_get_pixels_as_normalized_to_buffer(rf_image image, rf_vec4* dst, rf_int dst_size); +rf_color* rf_image_pixels_to_rgba32(rf_image image, rf_allocator allocator); +rf_vec4* rf_image_compute_pixels_to_normalized(rf_image image, rf_allocator allocator); +void rf_image_extract_palette_to_buffer(rf_image image, rf_color* palette_dst, rf_int palette_size); +rf_palette rf_image_extract_palette(rf_image image, rf_int palette_size, rf_allocator allocator); +rf_rec rf_image_alpha_border(rf_image image, float threshold); +bool rf_supports_image_file_type(const char* filename); +rf_image rf_load_image_from_file_data_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size, rf_desired_channels channels, rf_allocator temp_allocator); +rf_image rf_load_image_from_file_data(const void* src, rf_int src_size, rf_allocator allocator, rf_allocator temp_allocator); +rf_image rf_load_image_from_hdr_file_data_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size, rf_desired_channels channels, rf_allocator temp_allocator); +rf_image rf_load_image_from_hdr_file_data(const void* src, rf_int src_size, rf_allocator allocator, rf_allocator temp_allocator); +rf_image rf_load_image_from_format_to_buffer(const void* src, rf_int src_size, int src_width, int src_height, rf_uncompressed_pixel_format src_format, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format); +rf_image rf_load_image_from_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +void rf_unload_image(rf_image image, rf_allocator allocator); +int rf_mipmaps_image_size(rf_mipmaps_image image); +rf_mipmaps_stats rf_compute_mipmaps_stats(rf_image image, int desired_mipmaps_count); +rf_mipmaps_image rf_image_gen_mipmaps_to_buffer(rf_image image, int gen_mipmaps_count, void* dst, rf_int dst_size, rf_allocator temp_allocator); +rf_mipmaps_image rf_image_gen_mipmaps(rf_image image, int desired_mipmaps_count, rf_allocator allocator, rf_allocator temp_allocator); +void rf_unload_mipmaps_image(rf_mipmaps_image image, rf_allocator allocator); +rf_int rf_get_dds_image_size(const void* src, rf_int src_size); +rf_mipmaps_image rf_load_dds_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size); +rf_mipmaps_image rf_load_dds_image(const void* src, rf_int src_size, rf_allocator allocator); +rf_mipmaps_image rf_load_dds_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_int rf_get_pkm_image_size(const void* src, rf_int src_size); +rf_image rf_load_pkm_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size); +rf_image rf_load_pkm_image(const void* src, rf_int src_size, rf_allocator allocator); +rf_image rf_load_pkm_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_int rf_get_ktx_image_size(const void* src, rf_int src_size); +rf_mipmaps_image rf_load_ktx_image_to_buffer(const void* src, rf_int src_size, void* dst, rf_int dst_size); +rf_mipmaps_image rf_load_ktx_image(const void* src, rf_int src_size, rf_allocator allocator); +rf_mipmaps_image rf_load_ktx_image_from_file(const char* file, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_gif rf_load_animated_gif(const void* data, rf_int data_size, rf_allocator allocator, rf_allocator temp_allocator); +rf_gif rf_load_animated_gif_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_sizei rf_gif_frame_size(rf_gif gif); +rf_image rf_get_frame_from_gif(rf_gif gif, int frame); +void rf_unload_gif(rf_gif gif, rf_allocator allocator); +rf_vec2 rf_get_seed_for_cellular_image(int seeds_per_row, int tile_size, int i, rf_rand_proc rand); +rf_image rf_gen_image_color_to_buffer(int width, int height, rf_color color, rf_color* dst, rf_int dst_size); +rf_image rf_gen_image_color(int width, int height, rf_color color, rf_allocator allocator); +rf_image rf_gen_image_gradient_v_to_buffer(int width, int height, rf_color top, rf_color bottom, rf_color* dst, rf_int dst_size); +rf_image rf_gen_image_gradient_v(int width, int height, rf_color top, rf_color bottom, rf_allocator allocator); +rf_image rf_gen_image_gradient_h_to_buffer(int width, int height, rf_color left, rf_color right, rf_color* dst, rf_int dst_size); +rf_image rf_gen_image_gradient_h(int width, int height, rf_color left, rf_color right, rf_allocator allocator); +rf_image rf_gen_image_gradient_radial_to_buffer(int width, int height, float density, rf_color inner, rf_color outer, rf_color* dst, rf_int dst_size); +rf_image rf_gen_image_gradient_radial(int width, int height, float density, rf_color inner, rf_color outer, rf_allocator allocator); +rf_image rf_gen_image_checked_to_buffer(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2, rf_color* dst, rf_int dst_size); +rf_image rf_gen_image_checked(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2, rf_allocator allocator); +rf_image rf_gen_image_white_noise_to_buffer(int width, int height, float factor, rf_rand_proc rand, rf_color* dst, rf_int dst_size); +rf_image rf_gen_image_white_noise(int width, int height, float factor, rf_rand_proc rand, rf_allocator allocator); +rf_image rf_gen_image_perlin_noise_to_buffer(int width, int height, int offset_x, int offset_y, float scale, rf_color* dst, rf_int dst_size); +rf_image rf_gen_image_perlin_noise(int width, int height, int offset_x, int offset_y, float scale, rf_allocator allocator); +rf_image rf_gen_image_cellular_to_buffer(int width, int height, int tile_size, rf_rand_proc rand, rf_color* dst, rf_int dst_size); +rf_image rf_gen_image_cellular(int width, int height, int tile_size, rf_rand_proc rand, rf_allocator allocator); +rf_image rf_image_copy_to_buffer(rf_image image, void* dst, rf_int dst_size); +rf_image rf_image_copy(rf_image image, rf_allocator allocator); +rf_image rf_image_crop_to_buffer(rf_image image, rf_rec crop, void* dst, rf_int dst_size, rf_uncompressed_pixel_format dst_format); +rf_image rf_image_crop(rf_image image, rf_rec crop, rf_allocator allocator); +rf_image rf_image_resize_to_buffer(rf_image image, int new_width, int new_height, void* dst, rf_int dst_size, rf_allocator temp_allocator); +rf_image rf_image_resize(rf_image image, int new_width, int new_height, rf_allocator allocator, rf_allocator temp_allocator); +rf_image rf_image_resize_nn_to_buffer(rf_image image, int new_width, int new_height, void* dst, rf_int dst_size); +rf_image rf_image_resize_nn(rf_image image, int new_width, int new_height, rf_allocator allocator); +rf_image rf_image_format_to_buffer(rf_image image, rf_uncompressed_pixel_format dst_format, void* dst, rf_int dst_size); +rf_image rf_image_format(rf_image image, rf_uncompressed_pixel_format new_format, rf_allocator allocator); +rf_image rf_image_alpha_mask_to_buffer(rf_image image, rf_image alpha_mask, void* dst, rf_int dst_size); +rf_image rf_image_alpha_clear(rf_image image, rf_color color, float threshold, rf_allocator allocator, rf_allocator temp_allocator); +rf_image rf_image_alpha_premultiply(rf_image image, rf_allocator allocator, rf_allocator temp_allocator); +rf_rec rf_image_alpha_crop_rec(rf_image image, float threshold); +rf_image rf_image_alpha_crop(rf_image image, float threshold, rf_allocator allocator); +rf_image rf_image_dither(rf_image image, int r_bpp, int g_bpp, int b_bpp, int a_bpp, rf_allocator allocator, rf_allocator temp_allocator); +rf_image rf_image_flip_vertical_to_buffer(rf_image image, void* dst, rf_int dst_size); +rf_image rf_image_flip_vertical(rf_image image, rf_allocator allocator); +rf_image rf_image_flip_horizontal_to_buffer(rf_image image, void* dst, rf_int dst_size); +rf_image rf_image_flip_horizontal(rf_image image, rf_allocator allocator); +rf_image rf_image_rotate_cw_to_buffer(rf_image image, void* dst, rf_int dst_size); +rf_image rf_image_rotate_cw(rf_image image); +rf_image rf_image_rotate_ccw_to_buffer(rf_image image, void* dst, rf_int dst_size); +rf_image rf_image_rotate_ccw(rf_image image); +rf_image rf_image_color_tint_to_buffer(rf_image image, rf_color color, void* dst, rf_int dst_size); +rf_image rf_image_color_tint(rf_image image, rf_color color); +rf_image rf_image_color_invert_to_buffer(rf_image image, void* dst, rf_int dst_size); +rf_image rf_image_color_invert(rf_image image); +rf_image rf_image_color_grayscale_to_buffer(rf_image image, void* dst, rf_int dst_size); +rf_image rf_image_color_grayscale(rf_image image); +rf_image rf_image_color_contrast_to_buffer(rf_image image, float contrast, void* dst, rf_int dst_size); +rf_image rf_image_color_contrast(rf_image image, int contrast); +rf_image rf_image_color_brightness_to_buffer(rf_image image, int brightness, void* dst, rf_int dst_size); +rf_image rf_image_color_brightness(rf_image image, int brightness); +rf_image rf_image_color_replace_to_buffer(rf_image image, rf_color color, rf_color replace, void* dst, rf_int dst_size); +rf_image rf_image_color_replace(rf_image image, rf_color color, rf_color replace); +void rf_image_draw(rf_image* dst, rf_image src, rf_rec src_rec, rf_rec dst_rec, rf_color tint, rf_allocator temp_allocator); +void rf_image_draw_rectangle(rf_image* dst, rf_rec rec, rf_color color, rf_allocator temp_allocator); +void rf_image_draw_rectangle_lines(rf_image* dst, rf_rec rec, int thick, rf_color color, rf_allocator temp_allocator); +rf_shader rf_gfx_load_shader(const char* vs_code, const char* fs_code); +void rf_gfx_unload_shader(rf_shader shader); +int rf_gfx_get_shader_location(rf_shader shader, const char* uniform_name); +void rf_gfx_set_shader_value(rf_shader shader, int uniform_loc, const void* value, int uniform_name); +void rf_gfx_set_shader_value_v(rf_shader shader, int uniform_loc, const void* value, int uniform_name, int count); +void rf_gfx_set_shader_value_matrix(rf_shader shader, int uniform_loc, rf_mat mat); +void rf_gfx_set_shader_value_texture(rf_shader shader, int uniform_loc, rf_texture2d texture); +rf_mat rf_gfx_get_matrix_projection(); +rf_mat rf_gfx_get_matrix_modelview(); +void rf_gfx_set_matrix_projection(rf_mat proj); +void rf_gfx_set_matrix_modelview(rf_mat view); +void rf_gfx_blend_mode(rf_blend_mode mode); +void rf_gfx_matrix_mode(rf_matrix_mode mode); +void rf_gfx_push_matrix(); +void rf_gfx_pop_matrix(); +void rf_gfx_load_identity(); +void rf_gfx_translatef(float x, float y, float z); +void rf_gfx_rotatef(float angleDeg, float x, float y, float z); +void rf_gfx_scalef(float x, float y, float z); +void rf_gfx_mult_matrixf(float* matf); +void rf_gfx_frustum(double left, double right, double bottom, double top, double znear, double zfar); +void rf_gfx_ortho(double left, double right, double bottom, double top, double znear, double zfar); +void rf_gfx_viewport(int x, int y, int width, int height); +void rf_gfx_begin(rf_drawing_mode mode); +void rf_gfx_end(); +void rf_gfx_vertex2i(int x, int y); +void rf_gfx_vertex2f(float x, float y); +void rf_gfx_vertex3f(float x, float y, float z); +void rf_gfx_tex_coord2f(float x, float y); +void rf_gfx_normal3f(float x, float y, float z); +void rf_gfx_color4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); +void rf_gfx_color3f(float x, float y, float z); +void rf_gfx_color4f(float x, float y, float z, float w); +void rf_gfx_enable_texture(unsigned int id); +void rf_gfx_disable_texture(); +void rf_gfx_set_texture_wrap(rf_texture2d texture, rf_texture_wrap_mode wrap_mode); +void rf_gfx_set_texture_filter(rf_texture2d texture, rf_texture_filter_mode filter_mode); +void rf_gfx_enable_render_texture(unsigned int id); +void rf_gfx_disable_render_texture(void); +void rf_gfx_enable_depth_test(void); +void rf_gfx_disable_depth_test(void); +void rf_gfx_enable_backface_culling(void); +void rf_gfx_disable_backface_culling(void); +void rf_gfx_enable_scissor_test(void); +void rf_gfx_disable_scissor_test(void); +void rf_gfx_scissor(int x, int y, int width, int height); +void rf_gfx_enable_wire_mode(void); +void rf_gfx_disable_wire_mode(void); +void rf_gfx_delete_textures(unsigned int id); +void rf_gfx_delete_render_textures(rf_render_texture2d target); +void rf_gfx_delete_shader(unsigned int id); +void rf_gfx_delete_vertex_arrays(unsigned int id); +void rf_gfx_delete_buffers(unsigned int id); +void rf_gfx_clear_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); +void rf_gfx_clear_screen_buffers(void); +void rf_gfx_update_buffer(int buffer_id, void* data, int data_size); +unsigned int rf_gfx_load_attrib_buffer(unsigned int vao_id, int shader_loc, void* buffer, int size, bool dynamic); +void rf_gfx_init_vertex_buffer(struct rf_vertex_buffer* vertex_buffer); +void rf_gfx_close(); +void rf_gfx_draw(); +bool rf_gfx_check_buffer_limit(int v_count); +void rf_gfx_set_debug_marker(const char* text); +unsigned int rf_gfx_load_texture(void* data, int width, int height, rf_pixel_format format, int mipmap_count); +unsigned int rf_gfx_load_texture_depth(int width, int height, int bits, bool use_render_buffer); +unsigned int rf_gfx_load_texture_cubemap(void* data, int size, rf_pixel_format format); +void rf_gfx_update_texture(unsigned int id, int width, int height, rf_pixel_format format, const void* pixels, int pixels_size); +rf_gfx_pixel_format rf_gfx_get_internal_texture_formats(rf_pixel_format format); +void rf_gfx_unload_texture(unsigned int id); +void rf_gfx_generate_mipmaps(rf_texture2d* texture); +rf_image rf_gfx_read_texture_pixels_to_buffer(rf_texture2d texture, void* dst, int dst_size); +rf_image rf_gfx_read_texture_pixels(rf_texture2d texture, rf_allocator allocator); +void rf_gfx_read_screen_pixels(rf_color* dst, int width, int height); +rf_render_texture2d rf_gfx_load_render_texture(int width, int height, rf_pixel_format format, int depth_bits, bool use_depth_texture); +void rf_gfx_render_texture_attach(rf_render_texture2d target, unsigned int id, int attach_type); +bool rf_gfx_render_texture_complete(rf_render_texture2d target); +void rf_gfx_load_mesh(struct rf_mesh* mesh, bool dynamic); +void rf_gfx_update_mesh(struct rf_mesh mesh, int buffer, int num); +void rf_gfx_update_mesh_at(struct rf_mesh mesh, int buffer, int num, int index); +void rf_gfx_draw_mesh(struct rf_mesh mesh, struct rf_material material, rf_mat transform); +void rf_gfx_unload_mesh(struct rf_mesh mesh); +rf_render_batch rf_create_custom_render_batch_from_buffers(rf_vertex_buffer* vertex_buffers, rf_int vertex_buffers_count, rf_draw_call* draw_calls, rf_int draw_calls_count); +rf_render_batch rf_create_custom_render_batch(rf_int vertex_buffers_count, rf_int draw_calls_count, rf_int vertex_buffer_elements_count, rf_allocator allocator); +rf_render_batch rf_create_default_render_batch(rf_allocator allocator); +void rf_set_active_render_batch(rf_render_batch* batch); +void rf_unload_render_batch(rf_render_batch batch, rf_allocator allocator); +rf_texture2d rf_load_texture_from_file(const char* filename, rf_allocator temp_allocator, rf_io_callbacks io); +rf_texture2d rf_load_texture_from_file_data(const void* data, rf_int dst_size, rf_allocator temp_allocator); +rf_texture2d rf_load_texture_from_image(rf_image image); +rf_texture2d rf_load_texture_from_image_with_mipmaps(rf_mipmaps_image image); +rf_texture_cubemap rf_load_texture_cubemap_from_image(rf_image image, rf_cubemap_layout_type layout_type, rf_allocator temp_allocator); +rf_render_texture2d rf_load_render_texture(int width, int height); +void rf_update_texture(rf_texture2d texture, const void* pixels, rf_int pixels_size); +void rf_gen_texture_mipmaps(rf_texture2d* texture); +void rf_set_texture_filter(rf_texture2d texture, rf_texture_filter_mode filter_mode); +void rf_set_texture_wrap(rf_texture2d texture, rf_texture_wrap_mode wrap_mode); +void rf_unload_texture(rf_texture2d texture); +void rf_unload_render_texture(rf_render_texture2d target); +rf_texture2d rf_gen_texture_cubemap(rf_shader shader, rf_texture2d sky_hdr, rf_int size); +rf_texture2d rf_gen_texture_irradiance(rf_shader shader, rf_texture2d cubemap, rf_int size); +rf_texture2d rf_gen_texture_prefilter(rf_shader shader, rf_texture2d cubemap, rf_int size); +rf_texture2d rf_gen_texture_brdf(rf_shader shader, rf_int size); +rf_ttf_font_info rf_parse_ttf_font(const void* ttf_data, rf_int font_size); +void rf_compute_ttf_font_glyph_metrics(rf_ttf_font_info* font_info, const int* codepoints, rf_int codepoints_count, rf_glyph_info* dst, rf_int dst_count); +int rf_compute_ttf_font_atlas_width(int padding, rf_glyph_info* glyph_metrics, rf_int glyphs_count); +rf_image rf_generate_ttf_font_atlas(rf_ttf_font_info* font_info, int atlas_width, int padding, rf_glyph_info* glyphs, rf_int glyphs_count, rf_font_antialias antialias, unsigned short* dst, rf_int dst_count, rf_allocator temp_allocator); +rf_font rf_ttf_font_from_atlas(int font_size, rf_image atlas, rf_glyph_info* glyph_metrics, rf_int glyphs_count); +rf_font rf_load_ttf_font_from_data(const void* font_file_data, int font_size, rf_font_antialias antialias, const int* chars, rf_int char_count, rf_allocator allocator, rf_allocator temp_allocator); +rf_font rf_load_ttf_font_from_file(const char* filename, int font_size, rf_font_antialias antialias, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +bool rf_compute_glyph_metrics_from_image(rf_image image, rf_color key, const int* codepoints, rf_glyph_info* dst, rf_int codepoints_and_dst_count); +rf_font rf_load_image_font_from_data(rf_image image, rf_glyph_info* glyphs, rf_int glyphs_count); +rf_font rf_load_image_font(rf_image image, rf_color key, rf_allocator allocator); +rf_font rf_load_image_font_from_file(const char* path, rf_color key, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +void rf_unload_font(rf_font font, rf_allocator allocator); +rf_glyph_index rf_get_glyph_index(rf_font font, int character); +int rf_font_height(rf_font font, float font_size); +rf_sizef rf_measure_text(rf_font font, const char* text, float font_size, float extra_spacing); +rf_sizef rf_measure_text_rec(rf_font font, const char* text, rf_rec rec, float font_size, float extra_spacing, bool wrap); +rf_sizef rf_measure_string(rf_font font, const char* text, int len, float font_size, float extra_spacing); +rf_sizef rf_measure_string_rec(rf_font font, const char* text, int text_len, rf_rec rec, float font_size, float extra_spacing, bool wrap); +void rf_clear(rf_color color); +void rf_begin(); +void rf_end(); +void rf_begin_2d(rf_camera2d camera); +void rf_end_2d(); +void rf_begin_3d(rf_camera3d camera); +void rf_end_3d(); +void rf_begin_render_to_texture(rf_render_texture2d target); +void rf_end_render_to_texture(); +void rf_begin_scissor_mode(int x, int y, int width, int height); +void rf_end_scissor_mode(); +void rf_begin_shader(rf_shader shader); +void rf_end_shader(); +void rf_begin_blend_mode(rf_blend_mode mode); +void rf_end_blend_mode(); +void rf_draw_pixel(int pos_x, int pos_y, rf_color color); +void rf_draw_pixel_v(rf_vec2 position, rf_color color); +void rf_draw_line(int startPosX, int startPosY, int endPosX, int endPosY, rf_color color); +void rf_draw_line_v(rf_vec2 startPos, rf_vec2 endPos, rf_color color); +void rf_draw_line_ex(rf_vec2 startPos, rf_vec2 endPos, float thick, rf_color color); +void rf_draw_line_bezier(rf_vec2 start_pos, rf_vec2 end_pos, float thick, rf_color color); +void rf_draw_line_strip(rf_vec2* points, int num_points, rf_color color); +void rf_draw_circle(int center_x, int center_y, float radius, rf_color color); +void rf_draw_circle_v(rf_vec2 center, float radius, rf_color color); +void rf_draw_circle_sector(rf_vec2 center, float radius, int start_angle, int end_angle, int segments, rf_color color); +void rf_draw_circle_sector_lines(rf_vec2 center, float radius, int start_angle, int end_angle, int segments, rf_color color); +void rf_draw_circle_gradient(int center_x, int center_y, float radius, rf_color color1, rf_color color2); +void rf_draw_circle_lines(int center_x, int center_y, float radius, rf_color color); +void rf_draw_ring(rf_vec2 center, float inner_radius, float outer_radius, int start_angle, int end_angle, int segments, rf_color color); +void rf_draw_ring_lines(rf_vec2 center, float inner_radius, float outer_radius, int start_angle, int end_angle, int segments, rf_color color); +void rf_draw_rectangle(int posX, int posY, int width, int height, rf_color color); +void rf_draw_rectangle_v(rf_vec2 position, rf_vec2 size, rf_color color); +void rf_draw_rectangle_rec(rf_rec rec, rf_color color); +void rf_draw_rectangle_pro(rf_rec rec, rf_vec2 origin, float rotation, rf_color color); +void rf_draw_rectangle_gradient_v(int pos_x, int pos_y, int width, int height, rf_color color1, rf_color color2);// Draw a vertical-gradient-filled rectangle +void rf_draw_rectangle_gradient_h(int pos_x, int pos_y, int width, int height, rf_color color1, rf_color color2);// Draw a horizontal-gradient-filled rectangle +void rf_draw_rectangle_gradient(rf_rec rec, rf_color col1, rf_color col2, rf_color col3, rf_color col4); +void rf_draw_rectangle_outline(rf_rec rec, int line_thick, rf_color color); +void rf_draw_rectangle_rounded(rf_rec rec, float roundness, int segments, rf_color color); +void rf_draw_rectangle_rounded_lines(rf_rec rec, float roundness, int segments, int line_thick, rf_color color); +void rf_draw_triangle(rf_vec2 v1, rf_vec2 v2, rf_vec2 v3, rf_color color); +void rf_draw_triangle_lines(rf_vec2 v1, rf_vec2 v2, rf_vec2 v3, rf_color color); +void rf_draw_triangle_fan(rf_vec2* points, int num_points, rf_color color); +void rf_draw_triangle_strip(rf_vec2* points, int points_count, rf_color color); +void rf_draw_poly(rf_vec2 center, int sides, float radius, float rotation, rf_color color); +void rf_draw_texture(rf_texture2d texture, int x, int y, rf_color tint); +void rf_draw_texture_ex(rf_texture2d texture, int x, int y, int w, int h, float rotation, rf_color tint); +void rf_draw_texture_region(rf_texture2d texture, rf_rec source_rec, rf_rec dest_rec, rf_vec2 origin, float rotation, rf_color tint); +void rf_draw_texture_npatch(rf_texture2d texture, rf_npatch_info n_patch_info, rf_rec dest_rec, rf_vec2 origin, float rotation, rf_color tint); +void rf_draw_string(const char* string, int string_len, int posX, int posY, int font_size, rf_color color); +void rf_draw_string_ex(rf_font font, const char* string, int string_len, rf_vec2 position, float fontSize, float spacing, rf_color tint); +void rf_draw_string_wrap(rf_font font, const char* string, int string_len, rf_vec2 position, float font_size, float spacing, rf_color tint, float wrap_width, rf_text_wrap_mode mode); +void rf_draw_string_rec(rf_font font, const char* string, int string_len, rf_rec rec, float font_size, float spacing, rf_text_wrap_mode wrap, rf_color tint); +void rf_draw_text(const char* text, int posX, int posY, int font_size, rf_color color); +void rf_draw_text_ex(rf_font font, const char* text, rf_vec2 position, float fontSize, float spacing, rf_color tint); +void rf_draw_text_wrap(rf_font font, const char* text, rf_vec2 position, float font_size, float spacing, rf_color tint, float wrap_width, rf_text_wrap_mode mode); +void rf_draw_text_rec(rf_font font, const char* text, rf_rec rec, float font_size, float spacing, rf_text_wrap_mode wrap, rf_color tint); +void rf_draw_line3d(rf_vec3 start_pos, rf_vec3 end_pos, rf_color color); +void rf_draw_circle3d(rf_vec3 center, float radius, rf_vec3 rotation_axis, float rotation_angle, rf_color color); +void rf_draw_cube(rf_vec3 position, float width, float height, float length, rf_color color); +void rf_draw_cube_wires(rf_vec3 position, float width, float height, float length, rf_color color); +void rf_draw_cube_texture(rf_texture2d texture, rf_vec3 position, float width, float height, float length, rf_color color); +void rf_draw_sphere(rf_vec3 center_pos, float radius, rf_color color); +void rf_draw_sphere_ex(rf_vec3 center_pos, float radius, int rings, int slices, rf_color color); +void rf_draw_sphere_wires(rf_vec3 center_pos, float radius, int rings, int slices, rf_color color); +void rf_draw_cylinder(rf_vec3 position, float radius_top, float radius_bottom, float height, int slices, rf_color color); +void rf_draw_cylinder_wires(rf_vec3 position, float radius_top, float radius_bottom, float height, int slices, rf_color color); +void rf_draw_plane(rf_vec3 center_pos, rf_vec2 size, rf_color color); +void rf_draw_ray(rf_ray ray, rf_color color); +void rf_draw_grid(int slices, float spacing); +void rf_draw_gizmo(rf_vec3 position); +void rf_draw_model(rf_model model, rf_vec3 position, float scale, rf_color tint); +void rf_draw_model_ex(rf_model model, rf_vec3 position, rf_vec3 rotation_axis, float rotation_angle, rf_vec3 scale, rf_color tint); +void rf_draw_model_wires(rf_model model, rf_vec3 position, rf_vec3 rotation_axis, float rotation_angle, rf_vec3 scale, rf_color tint); +void rf_draw_bounding_box(rf_bounding_box box, rf_color color); +void rf_draw_billboard(rf_camera3d camera, rf_texture2d texture, rf_vec3 center, float size, rf_color tint); +void rf_draw_billboard_rec(rf_camera3d camera, rf_texture2d texture, rf_rec source_rec, rf_vec3 center, float size, rf_color tint); +rf_bounding_box rf_mesh_bounding_box(rf_mesh mesh); +void rf_mesh_compute_tangents(rf_mesh* mesh, rf_allocator allocator, rf_allocator temp_allocator); +void rf_mesh_compute_binormals(rf_mesh* mesh); +void rf_unload_mesh(rf_mesh mesh, rf_allocator allocator); +rf_model rf_load_model(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_model rf_load_model_from_obj(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_model rf_load_model_from_iqm(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_model rf_load_model_from_gltf(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_model rf_load_model_from_mesh(rf_mesh mesh, rf_allocator allocator); +void rf_unload_model(rf_model model, rf_allocator allocator); +rf_materials_array rf_load_materials_from_mtl(const char* filename, rf_allocator allocator, rf_io_callbacks io); +void rf_unload_material(rf_material material, rf_allocator allocator); +rf_model_animation_array rf_load_model_animations_from_iqm_file(const char* filename, rf_allocator allocator, rf_allocator temp_allocator, rf_io_callbacks io); +rf_model_animation_array rf_load_model_animations_from_iqm(const unsigned char* data, int data_size, rf_allocator allocator, rf_allocator temp_allocator); +void rf_update_model_animation(rf_model model, rf_model_animation anim, int frame); +bool rf_is_model_animation_valid(rf_model model, rf_model_animation anim); +void rf_unload_model_animation(rf_model_animation anim, rf_allocator allocator); +rf_mesh rf_gen_mesh_cube(float width, float height, float length, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_poly(int sides, float radius, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_plane(float width, float length, int res_x, int res_z, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_sphere(float radius, int rings, int slices, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_hemi_sphere(float radius, int rings, int slices, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_cylinder(float radius, float height, int slices, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_torus(float radius, float size, int rad_seg, int sides, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_knot(float radius, float size, int rad_seg, int sides, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_heightmap(rf_image heightmap, rf_vec3 size, rf_allocator allocator, rf_allocator temp_allocator); +rf_mesh rf_gen_mesh_cubicmap(rf_image cubicmap, rf_vec3 cube_size, rf_allocator allocator, rf_allocator temp_allocator); +void rf_init_context(rf_context* ctx); +void rf_init_gfx(int screen_width, int screen_height, rf_gfx_backend_data* gfx_data); +rf_material rf_load_default_material(rf_allocator allocator); +rf_shader rf_load_default_shader(); +rf_font rf_get_default_font(); +rf_shader rf_get_default_shader(); +rf_texture2d rf_get_default_texture(); +rf_context* rf_get_context(); +rf_image rf_get_screen_data(rf_color* dst, rf_int dst_size); +void rf_set_global_context_pointer(rf_context* ctx); +void rf_set_viewport(int width, int height); +void rf_set_shapes_texture(rf_texture2d texture, rf_rec source); +rf_material rf_load_default_material_ez(); +rf_image rf_get_screen_data_ez(); +rf_base64_output rf_decode_base64_ez(const unsigned char* input); +rf_image rf_gfx_read_texture_pixels_ez(rf_texture2d texture); +rf_color* rf_image_pixels_to_rgba32_ez(rf_image image); +rf_vec4* rf_image_compute_pixels_to_normalized_ez(rf_image image); +rf_palette rf_image_extract_palette_ez(rf_image image, int palette_size); +rf_image rf_load_image_from_file_data_ez(const void* src, int src_size); +rf_image rf_load_image_from_hdr_file_data_ez(const void* src, int src_size); +rf_image rf_load_image_from_file_ez(const char* filename); +void rf_unload_image_ez(rf_image image); +rf_image rf_image_copy_ez(rf_image image); +rf_image rf_image_crop_ez(rf_image image, rf_rec crop); +rf_image rf_image_resize_ez(rf_image image, int new_width, int new_height); +rf_image rf_image_resize_nn_ez(rf_image image, int new_width, int new_height); +rf_image rf_image_format_ez(rf_image image, rf_uncompressed_pixel_format new_format); +rf_image rf_image_alpha_clear_ez(rf_image image, rf_color color, float threshold); +rf_image rf_image_alpha_premultiply_ez(rf_image image); +rf_image rf_image_alpha_crop_ez(rf_image image, float threshold); +rf_image rf_image_dither_ez(rf_image image, int r_bpp, int g_bpp, int b_bpp, int a_bpp); +rf_image rf_image_flip_vertical_ez(rf_image image); +rf_image rf_image_flip_horizontal_ez(rf_image image); +rf_vec2 rf_get_seed_for_cellular_image_ez(int seeds_per_row, int tile_size, int i); +rf_image rf_gen_image_color_ez(int width, int height, rf_color color); +rf_image rf_gen_image_gradient_v_ez(int width, int height, rf_color top, rf_color bottom); +rf_image rf_gen_image_gradient_h_ez(int width, int height, rf_color left, rf_color right); +rf_image rf_gen_image_gradient_radial_ez(int width, int height, float density, rf_color inner, rf_color outer); +rf_image rf_gen_image_checked_ez(int width, int height, int checks_x, int checks_y, rf_color col1, rf_color col2); +rf_image rf_gen_image_white_noise_ez(int width, int height, float factor); +rf_image rf_gen_image_perlin_noise_ez(int width, int height, int offset_x, int offset_y, float scale); +rf_image rf_gen_image_cellular_ez(int width, int height, int tile_size); +rf_mipmaps_image rf_image_gen_mipmaps_ez(rf_image image, int gen_mipmaps_count); +void rf_unload_mipmaps_image_ez(rf_mipmaps_image image); +rf_mipmaps_image rf_load_dds_image_ez(const void* src, int src_size); +rf_mipmaps_image rf_load_dds_image_from_file_ez(const char* file); +rf_image rf_load_pkm_image_ez(const void* src, int src_size); +rf_image rf_load_pkm_image_from_file_ez(const char* file); +rf_mipmaps_image rf_load_ktx_image_ez(const void* src, int src_size); +rf_mipmaps_image rf_load_ktx_image_from_file_ez(const char* file); +rf_gif rf_load_animated_gif_ez(const void* data, int data_size); +rf_gif rf_load_animated_gif_file_ez(const char* filename); +void rf_unload_gif_ez(rf_gif gif); +rf_texture2d rf_load_texture_from_file_ez(const char* filename); +rf_texture2d rf_load_texture_from_file_data_ez(const void* data, int dst_size); +rf_texture_cubemap rf_load_texture_cubemap_from_image_ez(rf_image image, rf_cubemap_layout_type layout_type); +rf_font rf_load_ttf_font_from_data_ez(const void* font_file_data, int font_size, rf_font_antialias antialias, const int* chars, int chars_count); +rf_font rf_load_ttf_font_from_file_ez(const char* filename, int font_size, rf_font_antialias antialias); +rf_font rf_load_image_font_ez(rf_image image, rf_color key); +rf_font rf_load_image_font_from_file_ez(const char* path, rf_color key); +void rf_unload_font_ez(rf_font font); +rf_decoded_string rf_decode_utf8_ez(const char* text, int len); +void rf_image_draw_ez(rf_image* dst, rf_image src, rf_rec src_rec, rf_rec dst_rec, rf_color tint); +void rf_image_draw_rectangle_ez(rf_image* dst, rf_rec rec, rf_color color); +void rf_image_draw_rectangle_lines_ez(rf_image* dst, rf_rec rec, int thick, rf_color color); +void rf_mesh_compute_tangents_ez(rf_mesh* mesh); +void rf_unload_mesh_ez(rf_mesh mesh); +rf_model rf_load_model_ez(const char* filename); +rf_model rf_load_model_from_obj_ez(const char* filename); +rf_model rf_load_model_from_iqm_ez(const char* filename); +rf_model rf_load_model_from_gltf_ez(const char* filename); +rf_model rf_load_model_from_mesh_ez(rf_mesh mesh); +void rf_unload_model_ez(rf_model model); +rf_materials_array rf_load_materials_from_mtl_ez(const char* filename); +void rf_unload_material_ez(rf_material material); +rf_model_animation_array rf_load_model_animations_from_iqm_file_ez(const char* filename); +rf_model_animation_array rf_load_model_animations_from_iqm_ez(const unsigned char* data, int data_size); +void rf_unload_model_animation_ez(rf_model_animation anim); +rf_mesh rf_gen_mesh_cube_ez(float width, float height, float length); +rf_mesh rf_gen_mesh_poly_ez(int sides, float radius); +rf_mesh rf_gen_mesh_plane_ez(float width, float length, int res_x, int res_z); +rf_mesh rf_gen_mesh_sphere_ez(float radius, int rings, int slices); +rf_mesh rf_gen_mesh_hemi_sphere_ez(float radius, int rings, int slices); +rf_mesh rf_gen_mesh_cylinder_ez(float radius, float height, int slices); +rf_mesh rf_gen_mesh_torus_ez(float radius, float size, int rad_seg, int sides); +rf_mesh rf_gen_mesh_knot_ez(float radius, float size, int rad_seg, int sides); +rf_mesh rf_gen_mesh_heightmap_ez(rf_image heightmap, rf_vec3 size); +rf_mesh rf_gen_mesh_cubicmap_ez(rf_image cubicmap, rf_vec3 cube_size); diff --git a/tools/raymath.h b/tools/raymath.h deleted file mode 100644 index d12db3a..0000000 --- a/tools/raymath.h +++ /dev/null @@ -1,91 +0,0 @@ -float Clamp(float value, float min, float max) -float Lerp(float start, float end, float amount) -float Normalize(float value, float start, float end) -float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) -Vector2 Vector2Zero(void) -Vector2 Vector2One(void) -Vector2 Vector2Add(Vector2 v1, Vector2 v2) -Vector2 Vector2AddValue(Vector2 v, float add) -Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) -Vector2 Vector2SubtractValue(Vector2 v, float sub) -float Vector2Length(Vector2 v) -float Vector2DotProduct(Vector2 v1, Vector2 v2) -float Vector2Distance(Vector2 v1, Vector2 v2) -float Vector2Angle(Vector2 v1, Vector2 v2) -Vector2 Vector2Scale(Vector2 v, float scale) -Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) -Vector2 Vector2Negate(Vector2 v) -Vector2 Vector2Divide(Vector2 v1, Vector2 v2) -Vector2 Vector2Normalize(Vector2 v) -Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) -Vector2 Vector2Rotate(Vector2 v, float degs) -Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) -Vector3 Vector3Zero(void) -Vector3 Vector3One(void) -Vector3 Vector3Add(Vector3 v1, Vector3 v2) -Vector3 Vector3AddValue(Vector3 v, float add) -Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) -Vector3 Vector3SubtractValue(Vector3 v, float sub) -Vector3 Vector3Scale(Vector3 v, float scalar) -Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) -Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) -Vector3 Vector3Perpendicular(Vector3 v) -float Vector3Length(const Vector3 v) -float Vector3DotProduct(Vector3 v1, Vector3 v2) -float Vector3Distance(Vector3 v1, Vector3 v2) -Vector3 Vector3Negate(Vector3 v) -Vector3 Vector3Divide(Vector3 v1, Vector3 v2) -Vector3 Vector3Normalize(Vector3 v) -void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) -Vector3 Vector3Transform(Vector3 v, Matrix mat) -Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) -Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) -Vector3 Vector3Reflect(Vector3 v, Vector3 normal) -Vector3 Vector3Min(Vector3 v1, Vector3 v2) -Vector3 Vector3Max(Vector3 v1, Vector3 v2) -Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) -float3 Vector3ToFloatV(Vector3 v) -float MatrixDeterminant(Matrix mat) -float MatrixTrace(Matrix mat) -Matrix MatrixTranspose(Matrix mat) -Matrix MatrixInvert(Matrix mat) -Matrix MatrixNormalize(Matrix mat) -Matrix MatrixIdentity(void) -Matrix MatrixAdd(Matrix left, Matrix right) -Matrix MatrixSubtract(Matrix left, Matrix right) -Matrix MatrixTranslate(float x, float y, float z) -Matrix MatrixRotate(Vector3 axis, float angle) -Matrix MatrixRotateXYZ(Vector3 ang) -Matrix MatrixRotateX(float angle) -Matrix MatrixRotateY(float angle) -Matrix MatrixRotateZ(float angle) -Matrix MatrixScale(float x, float y, float z) -Matrix MatrixMultiply(Matrix left, Matrix right) -Matrix MatrixFrustum(double left, double right, double bottom, double top, double near, double far) -Matrix MatrixPerspective(double fovy, double aspect, double near, double far) -Matrix MatrixOrtho(double left, double right, double bottom, double top, double near, double far) -Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) -float16 MatrixToFloatV(Matrix mat) -Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) -Quaternion QuaternionAddValue(Quaternion q, float add) -Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) -Quaternion QuaternionSubtractValue(Quaternion q, float sub) -Quaternion QuaternionIdentity(void) -float QuaternionLength(Quaternion q) -Quaternion QuaternionNormalize(Quaternion q) -Quaternion QuaternionInvert(Quaternion q) -Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) -Quaternion QuaternionScale(Quaternion q, float mul) -Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) -Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) -Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) -Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) -Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) -Quaternion QuaternionFromMatrix(Matrix mat) -Matrix QuaternionToMatrix(Quaternion q) -Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) -void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) -Quaternion QuaternionFromEuler(float roll, float pitch, float yaw) -Vector3 QuaternionToEuler(Quaternion q) -Quaternion QuaternionTransform(Quaternion q, Matrix mat) -Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) diff --git a/tools/rlgl.h b/tools/rlgl.h deleted file mode 100644 index 21fb70f..0000000 --- a/tools/rlgl.h +++ /dev/null @@ -1,70 +0,0 @@ -void rlMatrixMode(int mode) -void rlPushMatrix(void) -void rlPopMatrix(void) -void rlLoadIdentity(void) -void rlTranslatef(float x, float y, float z) -void rlRotatef(float angleDeg, float x, float y, float z) -void rlScalef(float x, float y, float z) -void rlMultMatrixf(float *matf) -void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) -void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) -void rlViewport(int x, int y, int width, int height) -void rlBegin(int mode) -void rlEnd(void) -void rlVertex2i(int x, int y) -void rlVertex2f(float x, float y) -void rlVertex3f(float x, float y, float z) -void rlTexCoord2f(float x, float y) -void rlNormal3f(float x, float y, float z) -void rlColor4ub(uint8_t r, uint8_t g, uint8_t b, uint8_t a) -void rlColor3f(float x, float y, float z) -void rlColor4f(float x, float y, float z, float w) -void rlEnableTexture(unsigned int id) -void rlDisableTexture(void) -void rlTextureParameters(unsigned int id, int param, int value) -void rlEnableRenderTexture(unsigned int id) -void rlDisableRenderTexture(void) -void rlEnableDepthTest(void) -void rlDisableDepthTest(void) -void rlEnableBackfaceCulling(void) -void rlDisableBackfaceCulling(void) -void rlEnableScissorTest(void) -void rlDisableScissorTest(void) -void rlScissor(int x, int y, int width, int height) -void rlEnableWireMode(void) -void rlDisableWireMode(void) -void rlDeleteTextures(unsigned int id) -void rlDeleteRenderTextures(RenderTexture2D target) -void rlDeleteShader(unsigned int id) -void rlDeleteVertexArrays(unsigned int id) -void rlDeleteBuffers(unsigned int id) -void rlClearColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) -void rlClearScreenBuffers(void) -void rlUpdateBuffer(int bufferId, void *data, int dataSize) -unsigned int rlLoadAttribBuffer(unsigned int vaoId, int shaderLoc, void *buffer, int size, bool dynamic) -void rlglInit(int width, int height) -void rlglClose(void) -void rlglDraw(void) -void rlCheckErrors(void) -int rlGetVersion(void) -bool rlCheckBufferLimit(int vCount) -void rlSetDebugMarker(const char *text) -void rlSetBlendMode(int glSrcFactor, int glDstFactor, int glEquation) -void rlLoadExtensions(void *loader) -unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount) -unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderBuffer) -unsigned int rlLoadTextureCubemap(void *data, int size, int format) -void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) -void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) -void rlUnloadTexture(unsigned int id) -void rlGenerateMipmaps(Texture2D *texture) -void *rlReadTexturePixels(Texture2D texture) -unsigned char *rlReadScreenPixels(int width, int height) -RenderTexture2D rlLoadRenderTexture(int width, int height, int format, int depthBits, bool useDepthTexture) -void rlRenderTextureAttach(RenderTexture target, unsigned int id, int attachType) -bool rlRenderTextureComplete(RenderTexture target) -void rlLoadMesh(Mesh *mesh, bool dynamic) -void rlUpdateMesh(Mesh mesh, int buffer, int num) -void rlUpdateMeshAt(Mesh mesh, int buffer, int num, int index) -void rlDrawMesh(Mesh mesh, Material material, Matrix transform) -void rlUnloadMesh(Mesh mesh)