From 9ef3448193c4d823975816472e4e9f198cea21c9 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 18 Oct 2025 19:47:05 +0200 Subject: [PATCH 01/25] Reviewed UUIDs --- .../examples/core_text_file_loading.vcxproj | 2 +- projects/VS2022/raylib.sln | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/projects/VS2022/examples/core_text_file_loading.vcxproj b/projects/VS2022/examples/core_text_file_loading.vcxproj index c2e25e042..15324701d 100644 --- a/projects/VS2022/examples/core_text_file_loading.vcxproj +++ b/projects/VS2022/examples/core_text_file_loading.vcxproj @@ -51,7 +51,7 @@ - {6B1A933E-71B8-4C1F-9E79-02D98830E671} + {2F578155-D51F-4C03-AB7F-5C5122CA46CC} Win32Proj core_text_file_loading 10.0 diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 7a7eca297..d0a886a4f 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -391,7 +391,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_highdpi_testbed", "exa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_screen_recording", "examples\core_screen_recording.vcxproj", "{9DE2FC01-A839-4F89-8319-9071D4C54821}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_text_file_loading", "examples\core_text_file_loading.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_text_file_loading", "examples\core_text_file_loading.vcxproj", "{2F578155-D51F-4C03-AB7F-5C5122CA46CC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -4847,6 +4847,30 @@ Global {9DE2FC01-A839-4F89-8319-9071D4C54821}.Release|x64.Build.0 = Release|x64 {9DE2FC01-A839-4F89-8319-9071D4C54821}.Release|x86.ActiveCfg = Release|Win32 {9DE2FC01-A839-4F89-8319-9071D4C54821}.Release|x86.Build.0 = Release|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|ARM64.Build.0 = Debug|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|x64.ActiveCfg = Debug|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|x64.Build.0 = Debug|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|x86.ActiveCfg = Debug|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Debug|x86.Build.0 = Debug|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|ARM64.ActiveCfg = Release|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|ARM64.Build.0 = Release|ARM64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x64.ActiveCfg = Release|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x64.Build.0 = Release|x64 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x86.ActiveCfg = Release|Win32 + {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From aeafce5db4a8b52371882f33dcd5f4ffacbaecac Mon Sep 17 00:00:00 2001 From: JordSant <77529699+JordSant@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:50:52 +0200 Subject: [PATCH 02/25] [examples] Added: `shaders_mandelbrot_set` (#5282) * [examples] Added: `shaders_mandelbrot_set` * Simplified shader code and added comments * Comments starting with a capital letter, and some minor fixes to adhere to the convention --- .../shaders/glsl100/mandelbrot_set.fs | 61 ++ .../shaders/glsl120/mandelbrot_set.fs | 59 ++ .../shaders/glsl330/mandelbrot_set.fs | 58 ++ examples/shaders/shaders_mandelbrot_set.c | 224 +++++++ examples/shaders/shaders_mandelbrot_set.png | Bin 0 -> 71902 bytes .../examples/shaders_mandelbrot_set.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 2 + 7 files changed, 973 insertions(+) create mode 100644 examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs create mode 100644 examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs create mode 100644 examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs create mode 100644 examples/shaders/shaders_mandelbrot_set.c create mode 100644 examples/shaders/shaders_mandelbrot_set.png create mode 100644 projects/VS2022/examples/shaders_mandelbrot_set.vcxproj diff --git a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs new file mode 100644 index 000000000..aab8514e8 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs @@ -0,0 +1,61 @@ +#version 100 + +#define PI 3.1415926535897932384626433832795 + +precision highp float; + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +uniform vec2 offset; // Offset of the scale +uniform float zoom; // Zoom of the scale +// NOTE: Maximum number of shader for-loop iterations depend on GPU, +// For example, on RasperryPi for this examply only supports up to 60 +uniform int maxIterations; // Max iterations per pixel + +const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity +const float max2 = max*max; // Square of max to avoid computing square root + +void main() +{ + // The pixel coordinates are scaled so they are on the mandelbrot scale + // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; + + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i + + int iter = 0; + while (iter < maxIterations) + { + float aa = a*a; + float bb = b*b; + if (aa + bb > max2) + break; + + float twoab = 2.0*a*b; + a = aa - bb + c.x; + b = twoab + c.y; + + ++iter; + } + + if (iter >= maxIterations) + { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } + else + { + float normR = float(iter - (iter/55)*55)/55.0; + float normG = float(iter - (iter/69)*69)/69.0; + float normB = float(iter - (iter/40)*40)/40.0; + + gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); + } +} diff --git a/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs new file mode 100644 index 000000000..5da3ef437 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs @@ -0,0 +1,59 @@ +#version 120 + +#define PI 3.1415926535897932384626433832795 + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +uniform vec2 offset; // Offset of the scale +uniform float zoom; // Zoom of the scale +// NOTE: Maximum number of shader for-loop iterations depend on GPU, +// For example, on RasperryPi for this examply only supports up to 60 +uniform int maxIterations; // Max iterations per pixel + +const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity +const float max2 = max*max; // Square of max to avoid computing square root + +void main() +{ + // The pixel coordinates are scaled so they are on the mandelbrot scale + // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; + + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i + + int iter = 0; + while (iter < maxIterations) + { + float aa = a*a; + float bb = b*b; + if (aa + bb > max2) + break; + + float twoab = 2.0*a*b; + a = aa - bb + c.x; + b = twoab + c.y; + + ++iter; + } + + if (iter >= maxIterations) + { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } + else + { + float normR = float(iter - (iter/55)*55)/55.0; + float normG = float(iter - (iter/69)*69)/69.0; + float normB = float(iter - (iter/40)*40)/40.0; + + gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); + } +} diff --git a/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs new file mode 100644 index 000000000..bde74565f --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs @@ -0,0 +1,58 @@ +#version 330 + +#define PI 3.1415926535897932384626433832795 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Output fragment color +out vec4 finalColor; + +uniform vec2 offset; // Offset of the scale +uniform float zoom; // Zoom of the scale +uniform int maxIterations; // Max iterations per pixel + +const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity +const float max2 = max*max; // Square of max to avoid computing square root + +void main() +{ + // The pixel coordinates are scaled so they are on the mandelbrot scale + // NOTE: fragTexCoord already comes as normalized screen coordinates but offset must be normalized before scaling and zoom + vec2 c = vec2((fragTexCoord.x - 0.5)*2.5, (fragTexCoord.y - 0.5)*1.5)/zoom; + c.x += offset.x; + c.y += offset.y; + float a = 0.0; + float b = 0.0; + + // The Mandelbrot set is a two-dimensional set defined in the complex plane on which the iteration of the function + // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 + // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i + + int iter = 0; + for (iter = 0; iter < maxIterations; ++iter) + { + float aa = a*a; + float bb = b*b; + if (aa + bb > max2) + break; + + float twoab = 2.0*a*b; + a = aa - bb + c.x; + b = twoab + c.y; + } + + if (iter >= maxIterations) + { + finalColor = vec4(0.0, 0.0, 0.0, 1.0); + } + else + { + float normR = float(iter%55)/55.0; + float normG = float(iter%69)/69.0; + float normB = float(iter%40)/40.0; + + finalColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); + } +} diff --git a/examples/shaders/shaders_mandelbrot_set.c b/examples/shaders/shaders_mandelbrot_set.c new file mode 100644 index 000000000..c8d0ce8d3 --- /dev/null +++ b/examples/shaders/shaders_mandelbrot_set.c @@ -0,0 +1,224 @@ +/******************************************************************************************* +* +* raylib [shaders] example - mandelbrot set +* +* Example complexity rating: [★★★☆] 3/4 +* +* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support, +* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version +* +* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3) +* +* Example originally created with raylib 5.6, last time updated with raylib 5.6 +* +* Example contributed by Jordi Santonja (@JordSant) +* Based on previous work by Josh Colclough (@joshcol9232) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2025 Jordi Santonja (@JordSant) +* +********************************************************************************************/ + +#include "raylib.h" +#include + +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + +// A few good interesting places +const float pointsOfInterest[6][3] = +{ + { -1.76826775f, -0.00422996283f, 28435.9238f }, + { 0.322004497f, -0.0357099883f, 56499.7266f }, + { -0.748880744f, -0.0562955774f, 9237.59082f }, + { -1.78385007f, -0.0156200649f, 14599.5283f }, + { -0.0985441282f, -0.924688697f, 26259.8535f }, + { 0.317785531f, -0.0322612226f, 29297.9258f }, +}; + +const int screenWidth = 800; +const int screenHeight = 450; +const float zoomSpeed = 1.01f; +const float offsetSpeedMul = 2.0f; + +const float startingZoom = 0.6f; +const float startingOffset[2] = { -0.5f, 0.0f }; + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - mandelbrot set"); + + // Load mandelbrot set shader + // NOTE: Defining 0 (NULL) for vertex shader forces usage of internal default vertex shader + Shader shader = LoadShader(0, TextFormat("resources/shaders/glsl%i/mandelbrot_set.fs", GLSL_VERSION)); + + // Create a RenderTexture2D to be used for render to texture + RenderTexture2D target = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); + + // Offset and zoom to draw the mandelbrot set at. (centered on screen and default size) + float offset[2] = { startingOffset[0], startingOffset[1] }; + float zoom = startingZoom; + // Depending on the zoom the mximum number of iterations must be adapted to get more detail as we zzoom in + // The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys + int maxIterations = 333; + float maxIterationsMultiplier = 166.5f; + + // Get variable (uniform) locations on the shader to connect with the program + // NOTE: If uniform variable could not be found in the shader, function returns -1 + int zoomLoc = GetShaderLocation(shader, "zoom"); + int offsetLoc = GetShaderLocation(shader, "offset"); + int maxIterationsLoc = GetShaderLocation(shader, "maxIterations"); + + // Upload the shader uniform values! + SetShaderValue(shader, zoomLoc, &zoom, SHADER_UNIFORM_FLOAT); + SetShaderValue(shader, offsetLoc, offset, SHADER_UNIFORM_VEC2); + SetShaderValue(shader, maxIterationsLoc, &maxIterations, SHADER_UNIFORM_INT); + + bool showControls = true; // Show controls + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + bool updateShader = false; + + // Press [1 - 6] to reset c to a point of interest + if (IsKeyPressed(KEY_ONE) || + IsKeyPressed(KEY_TWO) || + IsKeyPressed(KEY_THREE) || + IsKeyPressed(KEY_FOUR) || + IsKeyPressed(KEY_FIVE) || + IsKeyPressed(KEY_SIX)) + { + int interestIndex = 0; + if (IsKeyPressed(KEY_ONE)) interestIndex = 0; + else if (IsKeyPressed(KEY_TWO)) interestIndex = 1; + else if (IsKeyPressed(KEY_THREE)) interestIndex = 2; + else if (IsKeyPressed(KEY_FOUR)) interestIndex = 3; + else if (IsKeyPressed(KEY_FIVE)) interestIndex = 4; + else if (IsKeyPressed(KEY_SIX)) interestIndex = 5; + + offset[0] = pointsOfInterest[interestIndex][0]; + offset[1] = pointsOfInterest[interestIndex][1]; + zoom = pointsOfInterest[interestIndex][2]; + updateShader = true; + } + + // If "R" is pressed, reset zoom and offset + if (IsKeyPressed(KEY_R)) + { + offset[0] = startingOffset[0]; + offset[1] = startingOffset[1]; + zoom = startingZoom; + updateShader = true; + } + + if (IsKeyPressed(KEY_F1)) showControls = !showControls; // Toggle whether or not to show controls + + // Change number of max iterations with UP and DOWN keys + // WARNING: Increasing the number of max iterations greatly impacts performance + if (IsKeyPressed(KEY_UP)) + { + maxIterationsMultiplier *= 1.4f; + updateShader = true; + } + else if (IsKeyPressed(KEY_DOWN)) + { + maxIterationsMultiplier /= 1.4f; + updateShader = true; + } + + // If either left or right button is pressed, zoom in/out + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) || IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) + { + // Change zoom. If Mouse left -> zoom in. Mouse right -> zoom out + zoom *= IsMouseButtonDown(MOUSE_BUTTON_LEFT)? zoomSpeed : (1.0f/zoomSpeed); + + const Vector2 mousePos = GetMousePosition(); + Vector2 offsetVelocity; + // Find the velocity at which to change the camera. Take the distance of the mouse + // From the center of the screen as the direction, and adjust magnitude based on the current zoom + offsetVelocity.x = (mousePos.x/(float)screenWidth - 0.5f)*offsetSpeedMul/zoom; + offsetVelocity.y = (mousePos.y/(float)screenHeight - 0.5f)*offsetSpeedMul/zoom; + + // Apply move velocity to camera + offset[0] += GetFrameTime()*offsetVelocity.x; + offset[1] += GetFrameTime()*offsetVelocity.y; + + updateShader = true; + } + + // In case a parameter has been changed, update the shader values + if (updateShader) + { + // As we zoom in, increase the number of max iterations to get more detail + // Aproximate formula, but it works-ish + maxIterations = (int)(sqrtf(2.0f*sqrtf(fabsf(1.0f - sqrtf(37.5f*zoom))))*maxIterationsMultiplier); + + // Update the shader uniform values! + SetShaderValue(shader, zoomLoc, &zoom, SHADER_UNIFORM_FLOAT); + SetShaderValue(shader, offsetLoc, offset, SHADER_UNIFORM_VEC2); + SetShaderValue(shader, maxIterationsLoc, &maxIterations, SHADER_UNIFORM_INT); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + // Using a render texture to draw Mandelbrot set + BeginTextureMode(target); // Enable drawing to texture + ClearBackground(BLACK); // Clear the render texture + + // Draw a rectangle in shader mode to be used as shader canvas + // NOTE: Rectangle uses font white character texture coordinates, + // So shader can not be applied here directly because input vertexTexCoord + // Do not represent full screen coordinates (space where want to apply shader) + DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), BLACK); + EndTextureMode(); + + BeginDrawing(); + ClearBackground(BLACK); // Clear screen background + + // Draw the saved texture and rendered mandelbrot set with shader + // NOTE: We do not invert texture on Y, already considered inside shader + BeginShaderMode(shader); + // WARNING: If FLAG_WINDOW_HIGHDPI is enabled, HighDPI monitor scaling should be considered + // When rendering the RenderTexture2D to fit in the HighDPI scaled Window + DrawTextureEx(target.texture, (Vector2){ 0.0f, 0.0f }, 0.0f, 1.0f, WHITE); + EndShaderMode(); + + if (showControls) + { + DrawText("Press Mouse buttons right/left to zoom in/out and move", 10, 15, 10, RAYWHITE); + DrawText("Press F1 to toggle these controls", 10, 30, 10, RAYWHITE); + DrawText("Press [1 - 6] to change point of interest", 10, 45, 10, RAYWHITE); + DrawText("Press UP | DOWN to change number of iterations", 10, 60, 10, RAYWHITE); + DrawText("Press R to recenter the camera", 10, 75, 10, RAYWHITE); + } + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadShader(shader); // Unload shader + UnloadRenderTexture(target); // Unload render texture + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/shaders/shaders_mandelbrot_set.png b/examples/shaders/shaders_mandelbrot_set.png new file mode 100644 index 0000000000000000000000000000000000000000..6d112931d3a3b23a6cae48f6319b495164280359 GIT binary patch literal 71902 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%0D+lk`?xeW~R>$ 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 + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6} + Win32Proj + shaders_mandelbrot_set + 10.0 + shaders_mandelbrot_set + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index d0a886a4f..ef46f8fe1 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -393,6 +393,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_screen_recording", "ex EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_text_file_loading", "examples\core_text_file_loading.vcxproj", "{2F578155-D51F-4C03-AB7F-5C5122CA46CC}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shaders_mandelbrot_set", "examples\shaders_mandelbrot_set.vcxproj", "{1C829D1A-892C-451C-AF0B-AC65C85F5CC6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 From de8575b16e9042d2a0d7f291bb6e696098f7c54b Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 18 Oct 2025 19:54:35 +0200 Subject: [PATCH 03/25] Update raylib.sln --- projects/VS2022/raylib.sln | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index ef46f8fe1..ec35ed5e5 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -4873,6 +4873,30 @@ Global {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x64.Build.0 = Release|x64 {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x86.ActiveCfg = Release|Win32 {2F578155-D51F-4C03-AB7F-5C5122CA46CC}.Release|x86.Build.0 = Release|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|ARM64.Build.0 = Debug|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|x64.ActiveCfg = Debug|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|x64.Build.0 = Debug|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|x86.ActiveCfg = Debug|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Debug|x86.Build.0 = Debug|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|ARM64.ActiveCfg = Release|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|ARM64.Build.0 = Release|ARM64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x64.ActiveCfg = Release|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x64.Build.0 = Release|x64 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x86.ActiveCfg = Release|Win32 + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5070,6 +5094,8 @@ Global {EA4AD5A7-DB95-43C0-9A67-2D94146BCF91} = {278D8859-20B1-428F-8448-064F46E1F021} {1ACC8236-EF4E-44B0-BD0C-AB1D95D5890F} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {9DE2FC01-A839-4F89-8319-9071D4C54821} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {2F578155-D51F-4C03-AB7F-5C5122CA46CC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {1C829D1A-892C-451C-AF0B-AC65C85F5CC6} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From 7ae3eb5d3a25f865c2947bf4b6a0ed2dc101ab4d Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 20 Oct 2025 11:10:08 +0200 Subject: [PATCH 04/25] Create web_basic_window.png --- examples/others/web_basic_window.png | Bin 0 -> 10297 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/others/web_basic_window.png diff --git a/examples/others/web_basic_window.png b/examples/others/web_basic_window.png new file mode 100644 index 0000000000000000000000000000000000000000..346184417a436343a769ba38a9ad9cfda0195181 GIT binary patch literal 10297 zcmeHNdr(tX8ovPwtb*t`tzf8RsjKd8D-SDCgd{K^I$MIZ(%@J~s0vjWd4vcALV~zH zhS9L&w2Oko9cN)Tx8$nWqC65rEUZd04virSi8M&a4JJUiggozF5Nvk_{@Xuq=FUmZ zx#xV}m*4r$_qzAu0eX`Es*S4v0Px?xFX12nc#;5s(CzDujchpD+=qP;*aws10f{`= zfOWjG^0JDGGYgLc2Qp8bD%ws@JVZLZWBbnS5fMq2crpNN{oDS8xI-lwZ-$;- zCibqQeXd9OpHWA%eJ<;O5AKLh6qcwbVhLwrK7KTENc4x&zr?!{hgpVB)uFY5-~G=; zi>Rb}_VD`!1n)rBQnSia_Wp(f|JtB;BY~_}RMfu8YwfR-0$DdANi^sBPf;)7uZt_6 z3V_N_9)6kngK_Xxco*R5H{Uomt|fUS2;TMm!fS%ol5QOz%nh9#PS{1vez-XCTLK}J z=H>f#)xy%N1cJwrbIszDs@o4Tmjs;MNuqgO0N(WjS)?5#8tcg|b;3mZ#r!DwxkZWo zi_pC&1;&_`MH6&=m)UZ0uz3AQZoU7a zM2EaFw(Cd>lD%z?d3*Wh2K^`&r}uiFU&3KfCBSBRoL%a(1dF(2t-o#F(yiV_T$Jcv zei++O+HX+p!$t-pb5m2>FZ-`q!r>OQ0xtbqzSbt6Z7GOal;{8k>#{(L#iu_c1~1{j zVy=KoU2>1K&No|^yC~887B35QYU-Qls9j4qbV*mhC93WMkGxL)lX`LMX~$Op8Wg|a z+EkMKqJ zpKnFg3|_c|VpjGtc|%)8`)x0@I~B$0t@UsIEJ;-~OEuaZWeA7qa#BS+UMFMFQXhI! zxnSQ%WoYm?BjS#|n)6u4L>}C;cQ8WuNI_G*0yOK=E#)c>GgiT%@?%@=T#uf=4>8a+ zrEYmGmWF7fX^rH%VqO;=0_`@?#79aYiW}C6Z9F?iGta+RWx3{VoK1%uKeNSVP^xL~ zxOd9>+PyyOllk=1_SZ_Y`@y8x!Q^Q`q0D|SrG%{+*(uFwFt?f3>_GWYoZv2V=p-LO zPIFA0ALQEDym@Eccc#)%-K7ZG+(dDM{D(2a=9}o*Utph^bPEb!vcbkV8-=02B1ChBx6snl50FuP*bUaZv?NrY`_or)yLGH!O=6sLq6RYbwsBp^@OVb7!oW$U zisZ%7!gdj=*00qYSeTI{_{u@N1?ul`>0m{S zyWYHg>-B$PJMZ>B_MmGf?zTN~R4GK^)j!Q7Rv=hg4q9#FbrUeBf#;lw-WpcSBh-(f zmQo3(7Ca5oQLK2QW;bLgU`vXn!(O(>b~VGQG&;*ApgCZ`MYkMr*;?3FC9QVnfTg)% z;L{Z9hF}qkCiuAA<~-|(Y=k)2V;JHt@ zJ&wK64pEI?BSRsv`u`|kV{LG={CFD##b>q2XDr$JLaQZ3(aNPW^l*Vn8c;h>Hnthl zN*UqsoNVMeWm{vwHG5S-n`XvhsDPveS|nxwfo+hqVHiVImP8)8a^9f7^3tFmut4%m zWa>(l0pTNhqbj0*3g*$FGDJV%Y*fXj=OH$R$P5p~m|~gr{jP@ltLJM&UVW%v2V7NX z?~)n)c?d#v%o#Y4#@dci4zv=qTPr>UGeZ2E4AMqbgv+M9k;Zev2-G%t zuT1J&rz;!VhLLJrQzN4WHli+Dbr-B-P)p2QH$>US5TRhN?BsT#)C5t+&23skwfrU~ zSoK&7Tb8p)X{?o!KC^z&p2jIe++T*Y+M`Vkx*4trVI$VDbUn4-0vlz~Ny0M4h7)Xl zt_jm*-`>8aU}Z zHz<6`kr(%mD2XQU_fr$b+OgZ*%h2g^WVI7ER;YB4{E2RpqdVtRS${IdAuos|vhOWI zs{5KWB2}l#r{|CQDT9nGa@$i1MkyWChEE~4 zn~sLQzQh-j-2?KG87-0Ui*@X-_OSKgT|hnlq=~stRR~qsW`o5YqL=}e#_T{aecw{Wxhm;pAY Date: Mon, 20 Oct 2025 11:13:45 +0200 Subject: [PATCH 05/25] REXM: Update examples collection --- examples/Makefile | 1 + examples/Makefile.Web | 5 +++++ examples/README.md | 10 ++++++---- examples/examples_list.txt | 2 ++ tools/rexm/examples_report.md | 4 +++- tools/rexm/examples_report_issues.md | 2 +- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index a9732679d..ef7cb5948 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -664,6 +664,7 @@ SHADERS = \ shaders/shaders_hybrid_rendering \ shaders/shaders_julia_set \ shaders/shaders_lightmap_rendering \ + shaders/shaders_mandelbrot_set \ shaders/shaders_mesh_instancing \ shaders/shaders_model_shader \ shaders/shaders_multi_sample2d \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 88843af09..5d4f3ffab 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -664,6 +664,7 @@ SHADERS = \ shaders/shaders_hybrid_rendering \ shaders/shaders_julia_set \ shaders/shaders_lightmap_rendering \ + shaders/shaders_mandelbrot_set \ shaders/shaders_mesh_instancing \ shaders/shaders_model_shader \ shaders/shaders_multi_sample2d \ @@ -1307,6 +1308,10 @@ shaders/shaders_lightmap_rendering: shaders/shaders_lightmap_rendering.c --preload-file shaders/resources/cubicmap_atlas.png@resources/cubicmap_atlas.png \ --preload-file shaders/resources/spark_flame.png@resources/spark_flame.png +shaders/shaders_mandelbrot_set: shaders/shaders_mandelbrot_set.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file shaders/resources/shaders/glsl100/mandelbrot_set.fs@resources/shaders/glsl100/mandelbrot_set.fs + shaders/shaders_mesh_instancing: shaders/shaders_mesh_instancing.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file shaders/resources/shaders/glsl100/lighting_instancing.vs@resources/shaders/glsl100/lighting_instancing.vs \ diff --git a/examples/README.md b/examples/README.md index b40966a5a..19d110344 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,9 +17,9 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 185] +## EXAMPLES COLLECTION [TOTAL: 187] -### category: core [44] +### category: core [45] Examples using raylib[core](../src/rcore.c) platform functionality like window creation, inputs, drawing modes and system functionality. @@ -69,6 +69,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c | [core_highdpi_testbed](core/core_highdpi_testbed.c) | core_highdpi_testbed | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_screen_recording](core/core_screen_recording.c) | core_screen_recording | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | +| [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | ### category: shapes [31] @@ -104,7 +105,7 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_pie_chart](shapes/shapes_pie_chart.c) | shapes_pie_chart | ⭐⭐⭐☆ | 5.5 | 5.6 | [Gideon Serfontein](https://github.com/GideonSerf) | | [shapes_kaleidoscope](shapes/shapes_kaleidoscope.c) | shapes_kaleidoscope | ⭐⭐☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [shapes_clock_of_clocks](shapes/shapes_clock_of_clocks.c) | shapes_clock_of_clocks | ⭐⭐☆☆ | 5.5 | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | -| [shapes_mouse_trail](shapes/shapes_mouse_trail.c) | shapes_mouse_trail | ⭐☆☆☆ | 5.6 | 5.6-dev | [[Balamurugan R]](https://github.com/[Bala050814]) | +| [shapes_mouse_trail](shapes/shapes_mouse_trail.c) | shapes_mouse_trail | ⭐☆☆☆ | 5.6 | 5.6-dev | [Balamurugan R](https://github.com/Bala050814) | | [shapes_simple_particles](shapes/shapes_simple_particles.c) | shapes_simple_particles | ⭐⭐☆☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | | [shapes_starfield_effect](shapes/shapes_starfield_effect.c) | shapes_starfield_effect | ⭐⭐☆☆ | 5.5 | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | @@ -195,7 +196,7 @@ Examples using raylib models functionality, including models loading/generation | [models_basic_voxel](models/models_basic_voxel.c) | models_basic_voxel | ⭐⭐☆☆ | 5.5 | 5.5 | [Tim Little](https://github.com/timlittle) | | [models_rotating_cube](models/models_rotating_cube.c) | models_rotating_cube | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Jopestpe](https://github.com/jopestpe) | -### category: shaders [30] +### category: shaders [31] Examples using raylib shaders functionality, including shaders loading, parameters configuration and drawing using them (model shaders and postprocessing shaders). This functionality is directly provided by raylib [rlgl](../src/rlgl.c) module. @@ -231,6 +232,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete | [shaders_lightmap_rendering](shaders/shaders_lightmap_rendering.c) | shaders_lightmap_rendering | ⭐⭐⭐☆ | 4.5 | 4.5 | [Jussi Viitala](https://github.com/nullstare) | | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐⭐⭐☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | | [shaders_depth_rendering](shaders/shaders_depth_rendering.c) | shaders_depth_rendering | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | +| [shaders_mandelbrot_set](shaders/shaders_mandelbrot_set.c) | shaders_mandelbrot_set | ⭐⭐⭐☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | ### category: audio [8] diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 9989f940f..2a1b9c0b7 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -192,3 +192,5 @@ others;easings_testbed;★★★☆;2.5;3.0;2019;2025;"Juan Miguel López";@flas others;raylib_opengl_interop;★★★★;3.8;4.0;2021;2025;"Stephan Soller";@arkanis others;embedded_files_loading;★★☆☆;3.0;3.5;2020;2025;"Kristian Holmgren";@defutura others;web_basic_window;★☆☆☆;5.6-dev;5.6-dev;2014;2025;"Ramon Santamaria";@raysan5 +core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 +shaders;shaders_mandelbrot_set;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant diff --git a/tools/rexm/examples_report.md b/tools/rexm/examples_report.md index e058e09a7..fcc1f8839 100644 --- a/tools/rexm/examples_report.md +++ b/tools/rexm/examples_report.md @@ -204,4 +204,6 @@ Example elements validated: | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_text_file_loading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_mandelbrot_set | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/examples_report_issues.md b/tools/rexm/examples_report_issues.md index 3136c2709..5de76a17a 100644 --- a/tools/rexm/examples_report_issues.md +++ b/tools/rexm/examples_report_issues.md @@ -27,4 +27,4 @@ Example elements validated: | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 8604085b6ea6e6fb2345742a8a45c43534333267 Mon Sep 17 00:00:00 2001 From: JordSant <77529699+JordSant@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:18:15 +0200 Subject: [PATCH 06/25] [examples] Fixed `shaders_mandelbrot_set` for WebGL (#5286) --- .../shaders/glsl100/mandelbrot_set.fs | 35 ++++++++++--------- examples/shaders/shaders_mandelbrot_set.c | 5 +++ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs index aab8514e8..fb6dee8b3 100644 --- a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs +++ b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs @@ -17,6 +17,9 @@ uniform int maxIterations; // Max iterations per pixel const float max = 4.0; // We consider infinite as 4.0: if a point reaches a distance of 4.0 it will escape to infinity const float max2 = max*max; // Square of max to avoid computing square root +// WebGL shaders for loop iteration limit only const +const int maxIterationsLimit = 20000; + void main() { // The pixel coordinates are scaled so they are on the mandelbrot scale @@ -31,31 +34,29 @@ void main() // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i - int iter = 0; - while (iter < maxIterations) + for (int iter = 0; iter < maxIterationsLimit; ++iter) { float aa = a*a; float bb = b*b; + if (iter >= maxIterations) + { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + return; + } if (aa + bb > max2) - break; + { + float normR = float(iter - (iter/55)*55)/55.0; + float normG = float(iter - (iter/69)*69)/69.0; + float normB = float(iter - (iter/40)*40)/40.0; + + gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); + return; + } float twoab = 2.0*a*b; a = aa - bb + c.x; b = twoab + c.y; - - ++iter; } - if (iter >= maxIterations) - { - gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); - } - else - { - float normR = float(iter - (iter/55)*55)/55.0; - float normG = float(iter - (iter/69)*69)/69.0; - float normB = float(iter - (iter/40)*40)/40.0; - - gl_FragColor = vec4(sin(normR*PI), sin(normG*PI), sin(normB*PI), 1.0); - } + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); } diff --git a/examples/shaders/shaders_mandelbrot_set.c b/examples/shaders/shaders_mandelbrot_set.c index c8d0ce8d3..a373c518a 100644 --- a/examples/shaders/shaders_mandelbrot_set.c +++ b/examples/shaders/shaders_mandelbrot_set.c @@ -70,8 +70,13 @@ int main(void) float zoom = startingZoom; // Depending on the zoom the mximum number of iterations must be adapted to get more detail as we zzoom in // The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys +#if defined(PLATFORM_DESKTOP) int maxIterations = 333; float maxIterationsMultiplier = 166.5f; +#else + int maxIterations = 43; + float maxIterationsMultiplier = 22.0f; +#endif // Get variable (uniform) locations on the shader to connect with the program // NOTE: If uniform variable could not be found in the shader, function returns -1 From 74f2a899d945ec9014851ca75dbf1394687058f8 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 20 Oct 2025 19:09:37 +0200 Subject: [PATCH 07/25] Update rshapes.c --- src/rshapes.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rshapes.c b/src/rshapes.c index 55f7d7fb9..2b5854f86 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -1432,6 +1432,7 @@ void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) Rectangle shapeRect = GetShapesTextureRectangle(); rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); rlColor4ub(color.r, color.g, color.b, color.a); rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); @@ -1441,7 +1442,7 @@ void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) rlVertex2f(v2.x, v2.y); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); - rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(v3.x, v3.y); From ec3cb7045f710b202eb9ea15f64ca8fa9c779136 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 20 Oct 2025 19:09:56 +0200 Subject: [PATCH 08/25] Update rcore.c --- src/rcore.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index f87b68783..5dea97c22 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -679,12 +679,15 @@ void InitWindow(int width, int height, const char *title) // Initialize window data CORE.Window.screen.width = width; CORE.Window.screen.height = height; + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; + CORE.Window.eventWaiting = false; - CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default + CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; // Initialize global input state - memset(&CORE.Input, 0, sizeof(CORE.Input)); // Reset CORE.Input structure to 0 + memset(&CORE.Input, 0, sizeof(CORE.Input)); // Reset CORE.Input structure to 0 CORE.Input.Keyboard.exitKey = KEY_ESCAPE; CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; @@ -696,7 +699,7 @@ void InitWindow(int width, int height, const char *title) if (result != 0) { - TRACELOG(LOG_WARNING, "SYSTEM: Failed to initialize Platform"); + TRACELOG(LOG_WARNING, "SYSTEM: Failed to initialize platform"); return; } //-------------------------------------------------------------- From 166420429106692f2f66d9bec39121ed9617f00f Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 20 Oct 2025 19:10:41 +0200 Subject: [PATCH 09/25] REVIEWED: New Win32 platform backend to accomodate `rlsw` Software Renderer --- src/platforms/rcore_desktop_win32.c | 695 +++++++++++++++++----------- src/rlgl.h | 33 +- 2 files changed, 432 insertions(+), 296 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 3ccd4d3bd..7ac6138bd 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -91,22 +91,24 @@ typedef struct { HWND hwnd; // Window handler HDC hdc; // Graphic context handler HGLRC glContext; // OpenGL context handler + // Software renderer variables HDC hdcmem; // Memory graphic context handler HBITMAP hbitmap; // GDI bitmap handler unsigned int *pixels; // Pointer to pixel data buffer (BGRA format) - LARGE_INTEGER timerFrequency; unsigned int appScreenWidth; unsigned int appScreenHeight; unsigned int desiredFlags; - bool cursorEnabled; + + LARGE_INTEGER timerFrequency; } PlatformData; // Define WGL function pointer types (no wglext.h needed) typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *); typedef BOOL (WINAPI *PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC, const int *, const FLOAT *, UINT, int *, UINT *); typedef BOOL (WINAPI *PFNWGLSWAPINTERVALEXTPROC)(int); +typedef const char *(WINAPI *PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC hdc); //---------------------------------------------------------------------------------- // Global Variables Definition @@ -119,6 +121,7 @@ static PlatformData platform = { 0 }; // Platform specific data static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; +static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; // -------------------------------------------------------------------------------- // This part of the file contains pure functions that never access global state @@ -133,10 +136,10 @@ static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; // Defines and Macros //---------------------------------------------------------------------------------- #define A_TO_W_ALLOCA(outWstr, inAnsi) do { \ - size_t len = AToWLen(inAnsi); \ - outWstr = (WCHAR *)alloca(sizeof(WCHAR)*(len + 1)); \ - AToWCopy(outWstr, len, inAnsi); \ - outWstr[len] = 0; \ + size_t outLen = AToWLen(inAnsi); \ + outWstr = (WCHAR *)alloca(sizeof(WCHAR)*(outLen + 1)); \ + AToWCopy(inAnsi, outWstr, outLen); \ + outWstr[outLen] = 0; \ } while (0) #define STYLE_MASK_ALL 0xffffffff @@ -150,7 +153,7 @@ static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; #define WINDOW_STYLE_EX 0 -#define CLASS_NAME L"RaylibWindow" +#define CLASS_NAME L"raylibWindow" #define FLAG_MASK_OPTIONAL (FLAG_VSYNC_HINT) #define FLAG_MASK_REQUIRED ~(FLAG_MASK_OPTIONAL) @@ -196,93 +199,86 @@ static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -typedef enum { MIZED_NONE, MIZED_MIN, MIZED_MAX } Mized; - -typedef enum { - UPDATE_WINDOW_FIRST, - UPDATE_WINDOW_NORMAL, -} UpdateWindowKind; - -typedef enum { - SANITIZE_FLAGS_FIRST, - SANITIZE_FLAGS_NORMAL, -} SanitizeFlagsKind; - -typedef struct { - HMONITOR needle; - int index; - int matchIndex; - RECT rect; -} FindMonitorContext; +// Maximize-minimize request types +typedef enum { + MIZED_NONE, + MIZED_MIN, + MIZED_MAX +} Mized; +// Flag operations +// NOTE: Some ops need to be deferred typedef struct { DWORD set; DWORD clear; } FlagsOp; +// Monitor info type +typedef struct { + HMONITOR needle; + int index; + int matchIndex; + RECT rect; +} MonitorInfo; + //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- -static size_t AToWLen(const char *a) +// Get ASCII to WCHAR length +static size_t AToWLen(const char *ascii) { - int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, a, -1, NULL, 0); + int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, NULL, 0); - if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "Failed to calculate wide length, result=%d, error=%u", sizeNeeded, GetLastError()); + if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "WIN32: Failed to calculate wide length [ERROR: %u]", GetLastError()); return sizeNeeded; } -static void AToWCopy(wchar_t *outPtr, size_t outLen, const char *a) + +// Copy ASCII to WCHAR string +static void AToWCopy(const char *ascii, wchar_t *outPtr, size_t outLen) { - int size = MultiByteToWideChar(CP_UTF8, 0, a, -1, outPtr, (int)outLen); - if (size != outLen) TRACELOG(LOG_WARNING, "WIN32: Convert %zu UTF-8 chars to WCHAR but converted %zu", outLen, size); + int size = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, outPtr, (int)outLen); + if (size != outLen) TRACELOG(LOG_WARNING, "WIN32: Failed to convert %i UTF-8 chars to WCHAR, converted %i chars", outLen, size); } static bool DecoratedFromStyle(DWORD style) { if (style & STYLE_FLAGS_UNDECORATED_ON) { - if (style & STYLE_FLAGS_UNDECORATED_OFF) TRACELOG(LOG_ERROR, "FLAGS: Style 0x%x has both undecorated on/off flags", style); + if (style & STYLE_FLAGS_UNDECORATED_OFF) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x has both undecorated on/off flags", style); return false; // Not decorated } DWORD masked = (style & STYLE_FLAGS_UNDECORATED_OFF); - if (STYLE_FLAGS_UNDECORATED_OFF != masked) TRACELOG(LOG_ERROR, "FLAGS: Style 0x%x is missing these flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF); + if (STYLE_FLAGS_UNDECORATED_OFF != masked) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x is missing flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF); return true; // Decorated } -static Mized MizedFromStyle(DWORD style) -{ - // Minimized takes precedence over maximized - if (style & WS_MINIMIZE) return MIZED_MIN; - if (style & WS_MAXIMIZE) return MIZED_MAX; - return MIZED_NONE; -} - -static Mized MizedFromFlags(unsigned flags) -{ - // minimized takes precedence over maximized - if (FLAG_CHECK(flags, FLAG_WINDOW_MINIMIZED)) return MIZED_MIN; - if (flags & FLAG_WINDOW_MAXIMIZED) return MIZED_MAX; - return MIZED_NONE; -} - +// Get window style from required flags static DWORD MakeWindowStyle(unsigned flags) { - // we don't need this since we don't have any child windows, but I guess + // We don't need this since we don't have any child windows, but I guess // it improves efficiency, plus, windows adds this flag automatically anyway // so it keeps our flags in sync with the OS - DWORD style = WS_CLIPSIBLINGS ; + DWORD style = WS_CLIPSIBLINGS; style |= (flags & FLAG_WINDOW_HIDDEN)? 0 : WS_VISIBLE; style |= (flags & FLAG_WINDOW_RESIZABLE)? STYLE_FLAGS_RESIZABLE : 0; style |= (flags & FLAG_WINDOW_UNDECORATED)? STYLE_FLAGS_UNDECORATED_ON : STYLE_FLAGS_UNDECORATED_OFF; - switch (MizedFromFlags(flags)) + // Minimized takes precedence over maximized + int mized = MIZED_NONE; + if (FLAG_CHECK(flags, FLAG_WINDOW_MINIMIZED)) mized = MIZED_MIN; + if (flags & FLAG_WINDOW_MAXIMIZED) mized = MIZED_MAX; + + switch (mized) { case MIZED_NONE: break; case MIZED_MIN: style |= WS_MINIMIZE; break; @@ -293,15 +289,13 @@ static DWORD MakeWindowStyle(unsigned flags) return style; } -// Enforces that the actual window/platform state is in sync with raylib's flags +// Check flags state, enforces that the actual window/platform state is in sync with raylib's flags static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expectedStyle, DWORD styleCheckMask) { - //TRACELOG(LOG_INFO, "Verifying Flags 0x%x Style 0x%x Mask 0x%x", flags, expectedStyle & styleCheckMask, styleCheckMask); - DWORD styleFromFlags = MakeWindowStyle(flags); if ((styleFromFlags & styleCheckMask) != (expectedStyle & styleCheckMask)) { - TRACELOG(LOG_ERROR, "%s: window flags (0x%x) produced style 0x%x which != expected 0x%x (diff=0x%x, mask=0x%x)", + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: window flags (0x%x) produced style 0x%x which != expected 0x%x (diff=0x%x, mask=0x%x)", context, flags, styleFromFlags & styleCheckMask, expectedStyle & styleCheckMask, (styleFromFlags & styleCheckMask) ^ (expectedStyle & styleCheckMask), styleCheckMask); } @@ -310,7 +304,7 @@ static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expect LONG actualStyle = (LONG)GetWindowLongPtrW(hwnd, GWL_STYLE); if ((actualStyle & styleCheckMask) != (expectedStyle & styleCheckMask)) { - TRACELOG(LOG_ERROR, "%s: expected style 0x%x but got 0x%x (diff=0x%x, mask=0x%x, lasterror=%lu)", + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: expected style 0x%x but got 0x%x (diff=0x%x, mask=0x%x, lasterror=%lu)", context, expectedStyle & styleCheckMask, actualStyle & styleCheckMask, (expectedStyle & styleCheckMask) ^ (actualStyle & styleCheckMask), styleCheckMask, GetLastError()); @@ -320,7 +314,7 @@ static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expect { bool isIconic = IsIconic(hwnd); bool styleMinimized = !!(WS_MINIMIZE & actualStyle); - if (isIconic != styleMinimized) TRACELOG(LOG_ERROR, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); + if (isIconic != styleMinimized) TRACELOG(LOG_ERROR, "WIN32: FLAGS: IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); } if (styleCheckMask & WS_MAXIMIZE) @@ -329,49 +323,32 @@ static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expect placement.length = sizeof(placement); if (!GetWindowPlacement(hwnd, &placement)) { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetWindowPlacement", GetLastError()); + TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s failed, error=%lu", "GetWindowPlacement", GetLastError()); } bool placementMaximized = (placement.showCmd == SW_SHOWMAXIMIZED); bool styleMaximized = WS_MAXIMIZE & actualStyle; if (placementMaximized != styleMaximized) { - TRACELOG(LOG_ERROR, "maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d", + TRACELOG(LOG_ERROR, "WIN32: FLAGS: Maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d", placementMaximized, placement.showCmd, styleMaximized); } } } -static SIZE PxFromPt2(float dpiScale, bool highdpiEnabled, int screenWidth, int screenHeight) -{ - // Get size in pixels from points - return (SIZE){ - highdpiEnabled? (int)((float)screenWidth*dpiScale) : screenWidth, - highdpiEnabled? (int)((float)screenHeight*dpiScale) : screenHeight, - }; -} - -static SIZE GetClientSize(HWND hwnd) -{ - RECT rect = { 0 }; - - if (GetClientRect(hwnd, &rect) == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetClientRect", GetLastError()); - - return (SIZE){ rect.right, rect.bottom }; -} - +// Calculate window size (with borders, title-bar...) from desired client size (framebuffer size) static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style) { RECT rect = { 0, 0, clientSize.cx, clientSize.cy }; int result = AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi); - - if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "AdjustWindowRect", GetLastError()); + if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to adjust window rect [ERROR: %lu]", GetLastError()); return (SIZE){ rect.right - rect.left, rect.bottom - rect.top }; } -// returns true if the window size was updated, false otherwise -static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int height, unsigned flags) +// Update window size if required +// NOTE: Returns true if the window size was updated, false otherwise +static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigned flags) { if (flags & FLAG_WINDOW_MINIMIZED) return false; @@ -386,10 +363,10 @@ static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int he MONITORINFO info = { 0 }; HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); info.cbSize = sizeof(info); - if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetMonitorInfo", GetLastError()); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError()); RECT windowRect = { 0 }; - if (!GetWindowRect(hwnd, &windowRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetWindowRect", GetLastError()); + if (!GetWindowRect(hwnd, &windowRect)) TRACELOG(LOG_ERROR, "WIN32: Failed to get window rect [ERROR: %lu]", GetLastError()); if ((windowRect.left == info.rcMonitor.left) && (windowRect.top == info.rcMonitor.top) && @@ -402,33 +379,45 @@ static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int he info.rcMonitor.bottom - info.rcMonitor.top, SWP_NOOWNERZORDER)) { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowPos", GetLastError()); + TRACELOG(LOG_ERROR, "WIN32: Failed to set window position [ERROR: %lu]", GetLastError()); } return true; } + // Get size in pixels from points, considering high-dpi UINT dpi = GetDpiForWindow(hwnd); float dpiScale = ((float)dpi)/96.0f; bool dpiScaling = flags & FLAG_WINDOW_HIGHDPI; - SIZE desired = PxFromPt2(dpiScale, dpiScaling, width, height); - SIZE actual = GetClientSize(hwnd); - if ((actual.cx == desired.cx) || (actual.cy == desired.cy)) return false; + SIZE desiredSize = { + .cx = dpiScaling? (int)((float)width*dpiScale) : width, + .cy = dpiScaling? (int)((float)height*dpiScale) : height + }; - TRACELOG(LOG_INFO, "Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)", - actual.cx, actual.cy, desired.cx, desired.cy, dpi, dpiScaling, width, height); + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; - SIZE windowSize = CalcWindowSize(dpi, desired, MakeWindowStyle(flags)); - POINT windowPos = (POINT){ 0, 0 }; + // If client size is alread desired size, no need to update + if ((clientSize.cx == desiredSize.cx) || (clientSize.cy == desiredSize.cy)) return false; + + TRACELOG(LOG_INFO, "WIN32: Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)", + clientSize.cx, clientSize.cy, desiredSize.cx, desiredSize.cy, dpi, dpiScaling, width, height); + + // Calculate window size from desired framebuffer size and window flags + SIZE windowSize = CalcWindowSize(dpi, desiredSize, MakeWindowStyle(flags)); + POINT windowPos = { 0 }; UINT swpFlags = SWP_NOZORDER | SWP_FRAMECHANGED; - if (kind == UPDATE_WINDOW_FIRST) + + if (mode == 0) // UPDATE_WINDOW_FIRST { HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); - if (!monitor) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "MonitorFromWindow", GetLastError()); + if (!monitor) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor from window [ERROR: %lu]", GetLastError()); - MONITORINFO info; + MONITORINFO info = { 0 }; info.cbSize = sizeof(info); - if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetMonitorInfo", GetLastError()); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError()); #define MAX(a,b) (((a)>(b))? (a):(b)) @@ -446,15 +435,10 @@ static bool UpdateWindowSize(UpdateWindowKind kind, HWND hwnd, int width, int he //AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0); //SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER); - // Old code - //if (!SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, windowSize.cx, windowSize.cy, swpFlags)) - //{ - // TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowPos", GetLastError()); - //} - return true; } +// Verify if we are running in Windows 10 version 1703 (Creators Update) static BOOL IsWindows10Version1703OrGreaterWin32(void) { HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); @@ -463,7 +447,7 @@ static BOOL IsWindows10Version1703OrGreaterWin32(void) (DWORD (*)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG))GetProcAddress(ntdll, "RtlVerifyVersionInfo"); if (!Verify) { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetProcAddress 'RtlVerifyVersionInfo'", GetLastError()); + TRACELOG(LOG_ERROR, "WIN32: Failed to verify Windows version [ERROR: %lu]", GetLastError()); return 0; } @@ -472,6 +456,7 @@ static BOOL IsWindows10Version1703OrGreaterWin32(void) osvi.dwMajorVersion = 10; osvi.dwMinorVersion = 0; osvi.dwBuildNumber = 15063; // Build 15063 corresponds to Windows 10 version 1703 (Creators Update) + DWORDLONG cond = 0; VER_SET_CONDITION(cond, VER_MAJORVERSION, VER_GREATER_EQUAL); VER_SET_CONDITION(cond, VER_MINORVERSION, VER_GREATER_EQUAL); @@ -504,7 +489,8 @@ static void *WglGetProcAddress(const char *procname) return proc; } -static KeyboardKey KeyFromWparam(WPARAM wparam) +// Get key from wparam (mapping) +static KeyboardKey GetKeyFromWparam(WPARAM wparam) { switch (wparam) { @@ -687,6 +673,7 @@ static KeyboardKey KeyFromWparam(WPARAM wparam) } } +// Get cursor name static LPCWSTR GetCursorName(int cursor) { LPCWSTR name = (LPCWSTR)IDC_ARROW; @@ -710,37 +697,46 @@ static LPCWSTR GetCursorName(int cursor) return name; } -static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC _, LPRECT rect, LPARAM lparam) +// Count monitors process +// NOTE: Required by GetMonitorCount() +static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam) { int *count = (int *)lparam; *count += 1; + // Always return TRUE to continue the loop, otherwise, the caller // can't distinguish between stopping the loop and an error return TRUE; } -static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC _, LPRECT rect, LPARAM lparam) +// Find monitor process +// NOTE: Required by GetCurrentMonitor() +static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam) { - FindMonitorContext *c = (FindMonitorContext*)lparam; - if (handle == c->needle) + MonitorInfo *monitor = (MonitorInfo *)lparam; + + if (handle == monitor->needle) { - c->matchIndex = c->index; - c->rect = *rect; + monitor->matchIndex = monitor->index; + monitor->rect = *rect; } - c->index += 1; + monitor->index += 1; + // Always return TRUE to continue the loop, otherwise, the caller // can't distinguish between stopping the loop and an error return TRUE; } -static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, FlagsOp *deferredFlags) +// Get style changed required operations flags +// NOTE: Required for deferred operations +static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *style, FlagsOp *deferredFlags) { // Check window resizable flag change bool resizable = (coreWindowFlags & FLAG_WINDOW_RESIZABLE); - bool resizableOld = ((ss->styleOld & STYLE_FLAGS_RESIZABLE) != 0); - bool resizableNew = ((ss->styleNew & STYLE_FLAGS_RESIZABLE) != 0); - if (resizable != resizableOld) TRACELOG(LOG_ERROR, "expected resizable %u but got %u", resizable, resizableOld); + bool resizableOld = ((style->styleOld & STYLE_FLAGS_RESIZABLE) != 0); + bool resizableNew = ((style->styleNew & STYLE_FLAGS_RESIZABLE) != 0); + if (resizable != resizableOld) TRACELOG(LOG_ERROR, "WIN32: Expected resizable %u but got %u", resizable, resizableOld); if (resizableOld != resizableNew) { if (resizableNew) deferredFlags->set |= FLAG_WINDOW_RESIZABLE; @@ -749,9 +745,9 @@ static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, FlagsO // Check window decorated flag change bool decorated = (0 == (coreWindowFlags & FLAG_WINDOW_UNDECORATED)); - bool decoratedOld = DecoratedFromStyle(ss->styleOld); - bool decoratedNew = DecoratedFromStyle(ss->styleNew); - if (decorated != decoratedOld) TRACELOG(LOG_ERROR, "expected decorated %u but got %u", decorated, decoratedOld); + bool decoratedOld = DecoratedFromStyle(style->styleOld); + bool decoratedNew = DecoratedFromStyle(style->styleNew); + if (decorated != decoratedOld) TRACELOG(LOG_ERROR, "WIN32: Expected decorated %u but got %u", decorated, decoratedOld); if (decoratedOld != decoratedNew) { if (decoratedNew) deferredFlags->clear |= FLAG_WINDOW_UNDECORATED; @@ -760,9 +756,9 @@ static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, FlagsO // Check window hidden flag change bool hidden = (coreWindowFlags & FLAG_WINDOW_HIDDEN); - bool hiddenOld = ((ss->styleOld & WS_VISIBLE) == 0); - bool hiddenNew = ((ss->styleNew & WS_VISIBLE) == 0); - if (hidden != hiddenOld) TRACELOG(LOG_ERROR, "expected hidden %u but got %u", hidden, hiddenOld); + bool hiddenOld = ((style->styleOld & WS_VISIBLE) == 0); + bool hiddenNew = ((style->styleNew & WS_VISIBLE) == 0); + if (hidden != hiddenOld) TRACELOG(LOG_ERROR, "WIN32: Expected hidden %u but got %u", hidden, hiddenOld); if (hiddenOld != hiddenNew) { if (hiddenNew) deferredFlags->set |= FLAG_WINDOW_HIDDEN; @@ -770,7 +766,9 @@ static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *ss, FlagsO } } -// Call when the window is rezised, returns true if the new window size should update the desired app size +// Adopt window resize +// NOTE: Call when the window is rezised, returns true +// if the new window size should update the desired app size static bool AdoptWindowResize(unsigned flags) { if (flags & FLAG_WINDOW_MINIMIZED) return false; @@ -805,9 +803,12 @@ static void HandleRawInput(LPARAM lparam); static void HandleWindowResize(HWND hwnd, int *width, int *height); static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags); -static unsigned SanitizeFlags(SanitizeFlagsKind kind, unsigned flags); +static unsigned SanitizeFlags(int mode, unsigned flags); static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height); // Update window flags +// Check if OpenGL extension is available +static bool IsWglExtensionAvailable(HDC hdc, const char *extension); + //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- @@ -826,20 +827,14 @@ bool WindowShouldClose(void) // Toggle fullscreen mode void ToggleFullscreen(void) { - TRACELOG(LOG_WARNING, "ToggleFullscreen not implemented"); + TRACELOG(LOG_WARNING, "WIN32: Toggle full screen functionality not implemented"); } // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { - if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) - { - ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE); - } - else - { - SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE); - } + if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE); + else SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE); } // Set window state: maximized, if resizable @@ -858,40 +853,95 @@ void MinimizeWindow(void) void RestoreWindow(void) { if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) && - (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) - ) { - ClearWindowState(FLAG_WINDOW_MINIMIZED); - } - else - { - ClearWindowState(FLAG_WINDOW_MINIMIZED|FLAG_WINDOW_MAXIMIZED); - } + (CORE.Window.flags & FLAG_WINDOW_MINIMIZED)) ClearWindowState(FLAG_WINDOW_MINIMIZED); + else ClearWindowState(FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED); } // Set window configuration state using flags void SetWindowState(unsigned int flags) { - platform.desiredFlags = SanitizeFlags(SANITIZE_FLAGS_NORMAL, CORE.Window.flags | flags); + platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags | flags); UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); } // Clear window configuration state flags void ClearWindowState(unsigned int flags) { - platform.desiredFlags = SanitizeFlags(SANITIZE_FLAGS_NORMAL, CORE.Window.flags & ~flags); + platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags & ~flags); UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); } // Set icon for window void SetWindowIcon(Image image) { - TRACELOG(LOG_WARNING, "SetWindowIcon not implemented"); + if (!platform.hwnd || (image.data == NULL) || (image.width <= 0) || (image.height <= 0)) return; + + HDC hdc = GetDC(platform.hwnd); + + // Create 32-bit BGRA DIB for color + BITMAPV5HEADER bi = { 0 }; + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = image.width; + bi.bV5Height = -image.height; // Negative = top-down bitmap + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + unsigned char *targetBits = NULL; + HBITMAP hColorBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&targetBits, NULL, 0); + if (!hColorBitmap) + { + ReleaseDC(platform.hwnd, hdc); + return; + } + + // Copy RGBA > BGRA (Win32 expects BGRA) + for (int y = 0; y < image.height; y++) + { + for (int x = 0; x < image.width; x++) + { + int i = (y*image.width + x)*4; + targetBits[i + 0] = ((unsigned char *)image.data)[i + 2]; // B + targetBits[i + 1] = ((unsigned char *)image.data)[i + 1]; // G + targetBits[i + 2] = ((unsigned char *)image.data)[i + 0]; // R + targetBits[i + 3] = ((unsigned char *)image.data)[i + 3]; // A + } + } + + // Create mask bitmap (1-bit, all opaque) + HBITMAP hMaskBitmap = CreateBitmap(image.width, image.height, 1, 1, NULL); + + // Build icon info + ICONINFO ii = { 0 }; + ZeroMemory(&ii, sizeof(ii)); + ii.fIcon = TRUE; + ii.hbmMask = hMaskBitmap; + ii.hbmColor = hColorBitmap; + + HICON hIcon = CreateIconIndirect(&ii); + + // Clean up GDI bitmaps (icon keeps copies internally) + DeleteObject(hColorBitmap); + DeleteObject(hMaskBitmap); + ReleaseDC(platform.hwnd, hdc); + + if (hIcon) + { + // Set both large and small icons + SendMessage(platform.hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + SendMessage(platform.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + } } // Set icon for window void SetWindowIcons(Image *images, int count) { - TRACELOG(LOG_WARNING, "SetWindowIcons not implemented"); + // TODO. } void SetWindowTitle(const char *title) @@ -902,13 +952,24 @@ void SetWindowTitle(const char *title) A_TO_W_ALLOCA(titleWide, CORE.Window.title); int result = SetWindowTextW(platform.hwnd, titleWide); - if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowText", GetLastError()); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to set window title [ERROR: %lu]", GetLastError()); } // Set window position on screen (windowed mode) void SetWindowPosition(int x, int y) { - TRACELOG(LOG_WARNING, "SetWindowPosition not implemented"); + if (platform.hwnd != NULL) + { + RECT rect = { 0 }; + if (GetWindowRect(platform.hwnd, &rect)) + { + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + + // Move the window to the new position (keeping size and z-order) + SetWindowPos(platform.hwnd, NULL, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE); + } + } } // Set monitor for the current window @@ -975,15 +1036,15 @@ int GetCurrentMonitor(void) HMONITOR monitor = MonitorFromWindow(platform.hwnd, MONITOR_DEFAULTTOPRIMARY); if (!monitor) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "MonitorFromWindow", GetLastError()); - FindMonitorContext context; - context.needle = monitor; - context.index = 0; - context.matchIndex = -1; + MonitorInfo info = { 0 }; + info.needle = monitor; + info.index = 0; + info.matchIndex = -1; - int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&context); + int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&info); if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError()); - return context.matchIndex; + return info.matchIndex; } // Get selected monitor position @@ -1091,10 +1152,9 @@ void HideCursor(void) // Enables cursor (unlock cursor) void EnableCursor(void) { - if (platform.cursorEnabled) TRACELOG(LOG_INFO, "EnableCursor: already enabled"); - else + if (CORE.Input.Mouse.cursorLocked) { - if (!ClipCursor(NULL)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClipCursor", GetLastError()); + if (!ClipCursor(NULL)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError()); RAWINPUTDEVICE rid = { 0 }; rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC @@ -1102,18 +1162,17 @@ void EnableCursor(void) rid.dwFlags = RIDEV_REMOVE; // Add to this window even in background rid.hwndTarget = NULL; int result = RegisterRawInputDevices(&rid, 1, sizeof(rid)); - if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "RegisterRawInputDevices", GetLastError()); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError()); ShowCursor(); - platform.cursorEnabled = true; - TRACELOG(LOG_INFO, "EnableCursor: enabled"); + CORE.Input.Mouse.cursorLocked = false; } } // Disables cursor (lock cursor) void DisableCursor(void) { - if (platform.cursorEnabled) + if (!CORE.Input.Mouse.cursorLocked) { RAWINPUTDEVICE rid = { 0 }; rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC @@ -1121,33 +1180,31 @@ void DisableCursor(void) rid.dwFlags = RIDEV_INPUTSINK; // Add to this window even in background rid.hwndTarget = platform.hwnd; int result = RegisterRawInputDevices(&rid, 1, sizeof(rid)); - if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "RegisterRawInputDevices", GetLastError()); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError()); RECT clientRect = { 0 }; - if (!GetClientRect(platform.hwnd, &clientRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetClientRect", GetLastError()); + if (!GetClientRect(platform.hwnd, &clientRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client rectangle [ERROR: %lu]", GetLastError()); POINT topleft = { clientRect.left, clientRect.top }; - if (!ClientToScreen(platform.hwnd, &topleft)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClientToScreen", GetLastError()); + if (!ClientToScreen(platform.hwnd, &topleft)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client to screen size [ERROR: %lu]", GetLastError()); LONG width = clientRect.right - clientRect.left; LONG height = clientRect.bottom - clientRect.top; - TRACELOG(LOG_INFO, "ClipCursor client %d,%d %d,%d (topleft %d,%d)", + TRACELOG(LOG_INFO, "WIN32: Clip cursor client rect: [%d,%d %d,%d], top-left: (%d,%d)", clientRect.left, clientRect.top, clientRect.right, clientRect.bottom, topleft.x, topleft.y); LONG centerX = topleft.x + width/2; LONG centerY = topleft.y + height/2; RECT clipRect = { centerX, centerY, centerX + 1, centerY + 1 }; - if (!ClipCursor(&clipRect)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ClipCursor", GetLastError()); + if (!ClipCursor(&clipRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError()); CORE.Input.Mouse.previousPosition = (Vector2){ 0, 0 }; CORE.Input.Mouse.currentPosition = (Vector2){ 0, 0 }; HideCursor(); - platform.cursorEnabled = false; - TRACELOG(LOG_INFO, "DisableCursor: disabled"); + CORE.Input.Mouse.cursorLocked = true; } - else TRACELOG(LOG_INFO, "DisableCursor: already disabled"); } // Swap back buffer with front buffer (screen drawing) @@ -1163,8 +1220,8 @@ void SwapScreenBuffer(void) InvalidateRect(platform.hwnd, NULL, FALSE); UpdateWindow(platform.hwnd); #else - if (!SwapBuffers(platform.hdc)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SwapBuffers", GetLastError()); - if (!ValidateRect(platform.hwnd, NULL)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ValidateRect", GetLastError()); + if (!SwapBuffers(platform.hdc)) TRACELOG(LOG_ERROR, "WIN32: Failed to swap buffers [ERROR: %lu]", GetLastError()); + if (!ValidateRect(platform.hwnd, NULL)) TRACELOG(LOG_ERROR, "WIN32: Failed to validate screen rect [ERROR: %lu]", GetLastError()); #endif } @@ -1216,7 +1273,7 @@ void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float d // Set mouse position XY void SetMousePosition(int x, int y) { - if (platform.cursorEnabled) + if (!CORE.Input.Mouse.cursorLocked) { CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; @@ -1230,7 +1287,7 @@ void SetMouseCursor(int cursor) { LPCWSTR cursorName = GetCursorName(cursor); HCURSOR hcursor = LoadCursorW(NULL, cursorName); - if (!hcursor) TRACELOG(LOG_ERROR, "LoadCursor %d (win32 %d) failed, error=%lu", cursor, (size_t)cursorName, GetLastError()); + if (!hcursor) TRACELOG(LOG_ERROR, "WIN32: Failed to load requested cursor [ERROR: %lu]", GetLastError()); SetCursor(hcursor); CORE.Input.Mouse.cursorHidden = false; @@ -1319,6 +1376,7 @@ HGLRC InitOpenGL(HWND hwnd, HDC hdc) wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB"); wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT"); + wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB"); // Setup modern pixel format if extension is available if (wglChoosePixelFormatARB) @@ -1353,10 +1411,52 @@ HGLRC InitOpenGL(HWND hwnd, HDC hdc) HGLRC realContext = NULL; if (wglCreateContextAttribsARB) { + int glContextVersionMajor = 1; + int glContextVersionMinor = 1; + int glContextProfile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; + + if (rlGetVersion() == RL_OPENGL_21) // Request OpenGL 2.1 context + { + glContextVersionMajor = 2; + glContextVersionMinor = 1; + } + else if (rlGetVersion() == RL_OPENGL_33) // Request OpenGL 3.3 context + { + glContextVersionMajor = 3; + glContextVersionMinor = 3; + } + else if (rlGetVersion() == RL_OPENGL_43) // Request OpenGL 4.3 context + { + glContextVersionMajor = 4; + glContextVersionMinor = 3; + } + else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context + { + if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") || + IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile")) + { + glContextVersionMajor = 2; + glContextVersionMinor = 0; + glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT; + } + else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU"); + } + else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context + { + if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") || + IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile")) + { + glContextVersionMajor = 3; + glContextVersionMinor = 0; + glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT; + } + else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU"); + } + int contextAttribs[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, 3, - WGL_CONTEXT_MINOR_VERSION_ARB, 3, - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, WGL_CONTEXT_ES_PROFILE_BIT_EXT (if supported) + WGL_CONTEXT_MAJOR_VERSION_ARB, glContextVersionMajor, + WGL_CONTEXT_MINOR_VERSION_ARB, glContextVersionMinor, + WGL_CONTEXT_PROFILE_MASK_ARB, glContextProfile, // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, WGL_CONTEXT_ES_PROFILE_BIT_EXT (if supported) //WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB [glDebugMessageCallback()] 0 // Terminator }; @@ -1387,9 +1487,11 @@ HGLRC InitOpenGL(HWND hwnd, HDC hdc) // Initialize platform: graphics, inputs and more int InitPlatform(void) { - platform.desiredFlags = SanitizeFlags(SANITIZE_FLAGS_FIRST, CORE.Window.flags); + int result = 0; + platform.appScreenWidth = CORE.Window.screen.width; platform.appScreenHeight = CORE.Window.screen.height; + platform.desiredFlags = SanitizeFlags(0 /*SANITIZE_FLAGS_FIRST*/, CORE.Window.flags); // NOTE: From this point CORE.Window.flags should always reflect the actual state of the window CORE.Window.flags = FLAG_WINDOW_HIDDEN | (platform.desiredFlags & FLAG_MASK_NO_UPDATE); @@ -1411,37 +1513,56 @@ int InitPlatform(void) } */ + HINSTANCE hInstance = GetModuleHandleW(0); + + // Define window class WNDCLASSEXW windowClass = { .cbSize = sizeof(WNDCLASSEXW), .style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC, .lpfnWndProc = WndProc, // Custom procedure assigned .cbWndExtra = sizeof(LONG_PTR), // extra space for the Tuple object ptr - .hInstance = GetModuleHandleW(0), + .hInstance = hInstance, .hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW), // TODO: Audit if we want to set this since we're implementing WM_SETCURSOR - .lpszClassName = CLASS_NAME //L"GLWindowClass"; + .lpszClassName = CLASS_NAME // Class name: L"raylibWindow" }; - // Register window class - if (RegisterClassExW(&windowClass) == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "RegisterClass", GetLastError()); + // Load user-provided icon if available + // NOTE: raylib resource file defaults to GLFW_ICON id, so looking for same identifier + windowClass.hIcon = LoadImageW(hInstance, L"GLFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + if (!windowClass.hIcon) windowClass.hIcon = LoadImageW(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); -/* - // TODO: Remove or move this code that sets the display size; should maybe go somewhere in WndProc? + // Register window class + result = (int)RegisterClassExW(&windowClass); + if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to register window class [ERROR: %lu]", GetLastError()); + + // Get primary monitor info POINT primaryTopLeft = { 0 }; HMONITOR monitor = MonitorFromPoint(primaryTopLeft, MONITOR_DEFAULTTOPRIMARY); if (monitor != NULL) { - MONITORINFO info; + MONITORINFO info = { 0 }; info.cbSize = sizeof(info); - if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_WARNING, "%s failed, error: %u", "GetMonitorInfo", GetLastError()); + result = (int)GetMonitorInfoW(monitor, &info); + + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: DISPLAY: Failed to get monitor info [ERROR: %u]", GetLastError()); else { CORE.Window.display.width = info.rcMonitor.right - info.rcMonitor.left; CORE.Window.display.height = info.rcMonitor.bottom - info.rcMonitor.top; } } - else TRACELOG(LOG_WARNING, "MonitorFromPoint, error: %s", GetLastError()); -*/ - + else TRACELOG(LOG_WARNING, "WIN32: DISPLAY: Failed to get primary monitor from point [ERROR: %u]", GetLastError()); + + // Adjust the window rectangle so the *client area* matches desired size + // NOTE: Window width/height includes borders and title-bar + DWORD style = WS_OVERLAPPEDWINDOW; + RECT rect = { 0, 0, platform.appScreenWidth, platform.appScreenHeight }; + AdjustWindowRect(&rect, style, FALSE); + //AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, FALSE, WINDOW_STYLE_EX); + //AdjustWindowRectExForDpi(&rect, style, FALSE, WINDOW_STYLE_EX, dpi); + int windowWidth = rect.right - rect.left; + int windowHeight = rect.bottom - rect.top; + // Create window // NOTE: Title string needs to be converted to WCHAR WCHAR *titleWide = NULL; @@ -1454,13 +1575,13 @@ int InitPlatform(void) titleWide, MakeWindowStyle(CORE.Window.flags), // WS_OVERLAPPEDWINDOW | WS_VISIBLE CW_USEDEFAULT, CW_USEDEFAULT, - platform.appScreenWidth, platform.appScreenHeight, // TODO: Window size [width, height], needs to be updated? + windowWidth, windowHeight, // TODO: Window size [width, height], needs to be updated? NULL, NULL, GetModuleHandleW(NULL), NULL); if (!platform.hwnd) { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "CreateWindow", GetLastError()); + TRACELOG(LOG_ERROR, "WIN32: WINDOW: Failed to create window [ERROR: %lu]", GetLastError()); return -1; } @@ -1475,11 +1596,11 @@ int InitPlatform(void) // Initialize software framebuffer BITMAPINFO bmi = { 0 }; ZeroMemory(&bmi, sizeof(bmi)); - bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = platform.appScreenWidth; - bmi.bmiHeader.biHeight = -(int)(platform.appScreenHeight); // Top-down bitmap - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; // 32-bit BGRA + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = platform.appScreenWidth; + bmi.bmiHeader.biHeight = -(int)(platform.appScreenHeight); // Top-down bitmap + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // 32-bit BGRA bmi.bmiHeader.biCompression = BI_RGB; platform.hdcmem = CreateCompatibleDC(platform.hdc); @@ -1504,6 +1625,8 @@ int InitPlatform(void) //UpdateWindowSize(UPDATE_WINDOW_FIRST, platform.hwnd, platform.appScreenWidth, platform.appScreenHeight, platform.desiredFlags); UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; CORE.Window.currentFbo.width = CORE.Window.render.width; CORE.Window.currentFbo.height = CORE.Window.render.height; TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); @@ -1512,17 +1635,29 @@ int InitPlatform(void) TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); - CORE.Storage.basePath = GetWorkingDirectory(); + if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer + { + TRACELOG(LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(LOG_INFO, " > Vendor: %s", "raylib"); + TRACELOG(LOG_INFO, " > Renderer: %s", "rlsw - OpenGL 1.1 Software Renderer"); + TRACELOG(LOG_INFO, " > Version: %s", "1.0"); + TRACELOG(LOG_INFO, " > GLSL: %s", "NOT SUPPORTED"); + } + // Initialize timming system + //---------------------------------------------------------------------------- LARGE_INTEGER time = { 0 }; QueryPerformanceCounter(&time); QueryPerformanceFrequency(&platform.timerFrequency); CORE.Time.base = time.QuadPart; InitTimer(); - - // TODO: Enable cursor? -> Use default value as 0 - platform.cursorEnabled = true; + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully"); @@ -1535,7 +1670,7 @@ void ClosePlatform(void) if (platform.hwnd) { int result = DestroyWindow(platform.hwnd); - if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Error on window destroy: %u", GetLastError()); + if (result == 0) TRACELOG(LOG_WARNING, "WIN32: WINDOW: Failed on window destroy [ERROR: %u]", GetLastError()); platform.hwnd = NULL; } } @@ -1617,7 +1752,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara { // TODO: Enforce min/max size } - else TRACELOG(LOG_WARNING, "WINDOW: Trying to resize a non-resizable window"); + else TRACELOG(LOG_WARNING, "WIN32: WINDOW: Trying to resize a non-resizable window"); result = TRUE; } break; @@ -1629,19 +1764,22 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags); UINT dpi = GetDpiForWindow(hwnd); - SIZE clientSize = GetClientSize(hwnd); + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; SIZE oldSize = CalcWindowSize(dpi, clientSize, ss->styleOld); SIZE newSize = CalcWindowSize(dpi, clientSize, ss->styleNew); if (oldSize.cx != newSize.cx || oldSize.cy != newSize.cy) { - TRACELOG(LOG_INFO, "resize from style change: %dx%d to %dx%d", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy); + TRACELOG(LOG_INFO, "WIN32: WINDOW: Resize from style change [%dx%d] to [%dx%d]", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy); if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) { // looks like windows will automatically "unminimize" a window // if a style changes modifies it's size - TRACELOG(LOG_INFO, "style change modifed window size, removing maximized flag"); + TRACELOG(LOG_INFO, "WIN32: WINDOW: Style change modifed window size, removing maximized flag"); deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED; } } @@ -1650,27 +1788,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara case WM_WINDOWPOSCHANGING: { WINDOWPOS *pos = (WINDOWPOS *)lparam; - if (pos->flags & SWP_SHOWWINDOW) - { - //if (pos->flags & SWP_HIDEWINDOW) abort(); - deferredFlags->clear |= FLAG_WINDOW_HIDDEN; - } - else if (pos->flags & SWP_HIDEWINDOW) - { - deferredFlags->set |= FLAG_WINDOW_HIDDEN; - } + if (pos->flags & SWP_SHOWWINDOW) deferredFlags->clear |= FLAG_WINDOW_HIDDEN; + else if (pos->flags & SWP_HIDEWINDOW) deferredFlags->set |= FLAG_WINDOW_HIDDEN; Mized mized = MIZED_NONE; bool isIconic = IsIconic(hwnd); bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE)); - if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); + if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "WIN32: IsIconic state different from WS_MINIMIZED state"); if (isIconic) mized = MIZED_MIN; else { WINDOWPLACEMENT placement; placement.length = sizeof(placement); - if (!GetWindowPlacement(hwnd, &placement)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetWindowPlacement", GetLastError()); + if (!GetWindowPlacement(hwnd, &placement)) TRACELOG(LOG_ERROR, "WIN32: WINDOW: FAiled to get monitor placement [ERROR: %lu]", GetLastError()); if (placement.showCmd == SW_SHOWMAXIMIZED) mized = MIZED_MAX; } @@ -1683,19 +1814,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); MONITORINFO info; info.cbSize = sizeof(info); - if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetMonitorInfo", GetLastError()); + if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: MONITOR: Failed to get monitor info [ERROR: %lu]", GetLastError()); if ((pos->x == info.rcMonitor.left) && (pos->y == info.rcMonitor.top) && (pos->cx == (info.rcMonitor.right - info.rcMonitor.left)) && - (pos->cy == (info.rcMonitor.bottom - info.rcMonitor.top))) - { - deferredFlags->set |= FLAG_BORDERLESS_WINDOWED_MODE; - } - else - { - deferredFlags->clear |= FLAG_BORDERLESS_WINDOWED_MODE; - } + (pos->cy == (info.rcMonitor.bottom - info.rcMonitor.top))) deferredFlags->set |= FLAG_BORDERLESS_WINDOWED_MODE; + else deferredFlags->clear |= FLAG_BORDERLESS_WINDOWED_MODE; } break; case MIZED_MIN: @@ -1739,7 +1864,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara float dpiScale = ((float)newDpi)/96.0f; bool dpiScaling = CORE.Window.flags & FLAG_WINDOW_HIGHDPI; - SIZE desired = PxFromPt2(dpiScale, dpiScaling, platform.appScreenWidth, platform.appScreenHeight); + // Get size in pixels from points + SIZE desired = { + .cx = dpiScaling? (int)((float)platform.appScreenWidth*dpiScale) : platform.appScreenWidth, + .cy = dpiScaling? (int)((float)platform.appScreenHeight*dpiScale) : platform.appScreenHeight + }; inoutSize->cx = desired.cx; inoutSize->cy = desired.cy; @@ -1747,18 +1876,14 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_DPICHANGED: { - RECT *suggestedRect = (RECT*)lparam; + RECT *suggestedRect = (RECT *)lparam; + // Never set the window size to anything other than the suggested rect here // Doing so can cause a window to stutter between monitors when transitioning between them - if (!SetWindowPos(hwnd, NULL, - suggestedRect->left, - suggestedRect->top, - suggestedRect->right - suggestedRect->left, - suggestedRect->bottom - suggestedRect->top, - SWP_NOZORDER | SWP_NOACTIVATE)) - { - TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetWindowPos", GetLastError()); - } + int result = (int)SetWindowPos(hwnd, NULL, suggestedRect->left, suggestedRect->top, + suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top, SWP_NOZORDER | SWP_NOACTIVATE); + if (result == 0) TRACELOG(LOG_ERROR, "Failed to set window position [ERROR: %lu]", GetLastError()); + } break; case WM_SETCURSOR: { @@ -1789,7 +1914,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_MOUSEMOVE: { - if (platform.cursorEnabled) + if (!CORE.Input.Mouse.cursorLocked) { CORE.Input.Mouse.currentPosition.x = (float)GET_X_LPARAM(lparam); CORE.Input.Mouse.currentPosition.y = (float)GET_Y_LPARAM(lparam); @@ -1833,32 +1958,23 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } //------------------------------------------------------------------------------------ - // Sanity check - if (platform.hwnd == hwnd) - { - CheckFlags("After WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask); - } + // Sanity check for flags + if (platform.hwnd == hwnd) CheckFlags("After WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask); // Operations to execute after the above check - if (flagsOp.set & flagsOp.clear) - { - TRACELOG(LOG_ERROR, "the flags 0x%x were both set and cleared!", flagsOp.set & flagsOp.clear); - } + if (flagsOp.set & flagsOp.clear) TRACELOG(LOG_WARNING, "WIN32: FLAGS: Flags 0x%x were both set and cleared", flagsOp.set & flagsOp.clear); DWORD save = CORE.Window.flags; CORE.Window.flags |= flagsOp.set; CORE.Window.flags &= ~flagsOp.clear; - if (save != CORE.Window.flags) - { - TRACELOG(LOG_DEBUG, "DeferredFlags: 0x%x > 0x%x (diff 0x%x)", save, CORE.Window.flags, save ^ CORE.Window.flags); - } + if (save != CORE.Window.flags) TRACELOG(LOG_DEBUG, "WIN32: FLAGS: Current deferred flags: 0x%x > 0x%x (diff 0x%x)", save, CORE.Window.flags, save ^ CORE.Window.flags); return result; } static void HandleKey(WPARAM wparam, LPARAM lparam, char state) { - KeyboardKey key = KeyFromWparam(wparam); + KeyboardKey key = GetKeyFromWparam(wparam); // TODO: Use scancode? //BYTE scancode = lparam >> 16; @@ -1888,9 +2004,9 @@ static void HandleRawInput(LPARAM lparam) UINT inputSize = sizeof(input); UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER)); - if (size == (UINT)-1) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "GetRawInputData", GetLastError()); + if (size == (UINT)-1) TRACELOG(LOG_ERROR, "WIN32: Failed to get raw input data [ERROR: %lu]", GetLastError()); - if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "Unexpected WM_INPUT type %lu", input.header.dwType); + if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "WIN32: Unexpected WM_INPUT type %lu", input.header.dwType); if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!"); @@ -1908,12 +2024,14 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) { if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return; - SIZE clientSize = GetClientSize(hwnd); + // Get client size (framebuffer inside the window) + RECT rect = { 0 }; + GetClientRect(hwnd, &rect); + SIZE clientSize = { rect.right, rect.bottom }; - //TRACELOG(LOG_DEBUG, "WINDOW: New widow client size: [%lux%lu]", clientSize.cx, clientSize.cy); - - //CORE.Window.currentFbo.width = clientSize.cx; - //CORE.Window.currentFbo.height = clientSize.cy; + // TODO: Update framebuffer on resize + CORE.Window.currentFbo.width = (int)clientSize.cx; + CORE.Window.currentFbo.height = (int)clientSize.cy; //glViewport(0, 0, clientSize.cx, clientSize.cy); //SetupFramebuffer(0, 0); @@ -1928,7 +2046,7 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) if (AdoptWindowResize(CORE.Window.flags)) { - TRACELOG(LOG_DEBUG, "WINDOW: Updating app size to %ix%i from window resize", screenWidth, screenHeight); + TRACELOG(LOG_DEBUG, "WIN32: WINDOW: Updating app size to [%ix%i] from window resize", screenWidth, screenHeight); *width = screenWidth; *height = screenHeight; } @@ -1951,15 +2069,21 @@ static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) DWORD previous = STYLE_MASK_WRITABLE & SetWindowLongPtrW(hwnd, GWL_STYLE, desired); if (previous != current) { - TRACELOG(LOG_ERROR, "SetWindowLong returned writable flags 0x%x but expected 0x%x (diff=0x%x, error=%lu)", + TRACELOG(LOG_ERROR, "WIN32: WINDOW: SetWindowLongPtr() returned writable flags 0x%x but expected 0x%x (diff=0x%x, error=%lu)", previous, current, previous ^ current, GetLastError()); } CheckFlags("UpdateWindowStyle", hwnd, desiredFlags, desired, STYLE_MASK_WRITABLE); } - Mized currentMized = MizedFromStyle(MakeWindowStyle(CORE.Window.flags)); - Mized desiredMized = MizedFromStyle(MakeWindowStyle(desiredFlags)); + // Minimized takes precedence over maximized + Mized currentMized = MIZED_NONE; + Mized desiredMized = MIZED_NONE; + if (CORE.Window.flags & WS_MINIMIZE) currentMized = MIZED_MIN; + else if (CORE.Window.flags & WS_MAXIMIZE) currentMized = MIZED_MAX; + if (desiredFlags & WS_MINIMIZE) currentMized = MIZED_MIN; + else if (desiredFlags & WS_MAXIMIZE) currentMized = MIZED_MAX; + if (currentMized != desiredMized) { switch (desiredMized) @@ -1972,24 +2096,21 @@ static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) } // Sanitize flags -static unsigned SanitizeFlags(SanitizeFlagsKind kind, unsigned flags) +static unsigned SanitizeFlags(int mode, unsigned flags) { if ((flags & FLAG_WINDOW_MAXIMIZED) && (flags & FLAG_BORDERLESS_WINDOWED_MODE)) { - TRACELOG(LOG_INFO, "borderless windows mode is overriding maximized"); + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Borderless windows mode overriding maximized window flag"); flags &= ~FLAG_WINDOW_MAXIMIZED; } - switch (kind) + if (mode == 1) { - case SANITIZE_FLAGS_FIRST: break; - case SANITIZE_FLAGS_NORMAL: - if ((flags & FLAG_MSAA_4X_HINT) && (!(CORE.Window.flags & FLAG_MSAA_4X_HINT))) - { - TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); - flags &= ~FLAG_MSAA_4X_HINT; - } - break; + if ((flags & FLAG_MSAA_4X_HINT) && (!(CORE.Window.flags & FLAG_MSAA_4X_HINT))) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: MSAA can only be configured before window initialization"); + flags &= ~FLAG_MSAA_4X_HINT; + } } return flags; @@ -2018,16 +2139,15 @@ static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) CORE.Window.flags |= (desiredFlags & FLAG_MASK_NO_UPDATE); int vsync = (CORE.Window.flags & FLAG_VSYNC_HINT)? 1 : 0; - PFNWGLSWAPINTERVALEXTPROC wglSwapInterval = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT"); - if (wglSwapInterval) + if (wglSwapIntervalEXT) { - (*wglSwapInterval)(vsync); + (*wglSwapIntervalEXT)(vsync); if (vsync) CORE.Window.flags |= FLAG_VSYNC_HINT; else CORE.Window.flags &= ~FLAG_VSYNC_HINT; } // TODO: Review all this code... - DWORD previousStyle; + DWORD previousStyle = 0; for (unsigned attempt = 1; ; attempt++) { CheckFlags("UpdateFlags", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), STYLE_MASK_ALL); @@ -2035,15 +2155,14 @@ static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) bool windowSizeUpdated = false; if (MakeWindowStyle(CORE.Window.flags) == MakeWindowStyle(desiredFlags)) { - windowSizeUpdated = UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, width, height, desiredFlags); + windowSizeUpdated = UpdateWindowSize(1, hwnd, width, height, desiredFlags); if ((FLAG_MASK_REQUIRED & desiredFlags) == (FLAG_MASK_REQUIRED & CORE.Window.flags)) break; } - if ((attempt > 1) && - (previousStyle == MakeWindowStyle(CORE.Window.flags)) && - !windowSizeUpdated) + + if ((attempt > 1) && (previousStyle == MakeWindowStyle(CORE.Window.flags)) && !windowSizeUpdated) { - TRACELOG(LOG_ERROR, "WINDOW: UpdateFlags() failed after %u attempt(s) wanted 0x%x but is 0x%x (diff=0x%x)", + TRACELOG(LOG_ERROR, "WIN32: WINDOW: UpdateFlags() failed after %u attempt(s) wanted 0x%x but is 0x%x (diff=0x%x)", attempt, desiredFlags, CORE.Window.flags, desiredFlags ^ CORE.Window.flags); } @@ -2051,3 +2170,21 @@ static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) UpdateWindowStyle(hwnd, desiredFlags); } } + +// Check if OpenGL extension is available +static bool IsWglExtensionAvailable(HDC hdc, const char *extension) +{ + bool result = false; + + if (wglGetExtensionsStringARB != NULL) + { + const char *extList = wglGetExtensionsStringARB(hdc); + if (extList != NULL) + { + // Simple substring search (could use strtok or strstr) + if (strstr(extList, extension) != NULL) result = true; + } + } + + return result; +} \ No newline at end of file diff --git a/src/rlgl.h b/src/rlgl.h index f587d81a2..e6a1c9432 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -2329,6 +2329,16 @@ void rlglInit(int width, int height) RLGL.State.currentMatrix = &RLGL.State.modelview; #endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) + // Initialize software renderer backend + int result = swInit(width, height); + if (result == 0) + { + TRACELOG(RL_LOG_ERROR, "RLSW: Software renderer initialization failed!"); + exit(-1); + } +#endif + // Initialize OpenGL default states //---------------------------------------------------------- // Init state: Depth test @@ -2345,39 +2355,28 @@ void rlglInit(int width, int height) glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) glEnable(GL_CULL_FACE); // Enable backface culling - // Init state: Cubemap seamless -#if defined(GRAPHICS_API_OPENGL_33) - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) -#endif - #if defined(GRAPHICS_API_OPENGL_11) // Init state: Color hints (deprecated in OpenGL 3.0+) glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) #endif - -#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) - int result = swInit(width, height); // Initialize software renderer backend - if (result == 0) - { - TRACELOG(RL_LOG_ERROR, "RLSW: Software renderer initialization failed!"); - exit(-1); - } +#if defined(GRAPHICS_API_OPENGL_33) + // Init state: Cubemap seamless + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) #endif - #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Store screen size into global variables RLGL.State.framebufferWidth = width; RLGL.State.framebufferHeight = height; - - TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); - //---------------------------------------------------------- #endif // Init state: Color/Depth buffers clear glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) glClearDepth(1.0f); // Set clear depth value (default) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) + + TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- } // Vertex Buffer Object deinitialization (memory free) From 79b7cd6b9bfa193aa920cc7dfca64e3822e2884a Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2025 10:08:36 +0200 Subject: [PATCH 10/25] Format tweaks --- src/external/rlsw.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 49050f848..904bf1210 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -6,7 +6,7 @@ * rlsw is a custom OpenGL 1.1-style implementation on software, intended to provide all * functionality available on rlgl.h library used by raylib, becoming a direct software * rendering replacement for OpenGL 1.1 backend and allowing to run raylib on GPU-less -* devices when required. +* devices when required * * FEATURES: * - Rendering to custom internal framebuffer with multiple color modes supported: @@ -2892,8 +2892,8 @@ static inline bool sw_quad_is_axis_aligned(void) float dx = v1[0] - v0[0]; float dy = v1[1] - v0[1]; - if (fabsf(dx) > 1e-6f && fabsf(dy) < 1e-6f) horizontal++; - else if (fabsf(dy) > 1e-6f && fabsf(dx) < 1e-6f) vertical++; + if ((fabsf(dx) > 1e-6f) && (fabsf(dy) < 1e-6f)) horizontal++; + else if ((fabsf(dy) > 1e-6f) && (fabsf(dx) < 1e-6f)) vertical++; else return false; // Diagonal edge -> not axis-aligned } @@ -2933,8 +2933,8 @@ static inline void sw_quad_sort_cw(const sw_vertex_t* *output) // Separate vertices based on Y-coordinate for (int i = 0; i < 4; i++) { - if (input[i].screen[1] == minY && topCount < 2) top[topCount++] = &input[i]; - else if (input[i].screen[1] == maxY && bottomCount < 2) bottom[bottomCount++] = &input[i]; + if ((input[i].screen[1] == minY) && (topCount < 2)) top[topCount++] = &input[i]; + else if ((input[i].screen[1] == maxY) && (bottomCount < 2)) bottom[bottomCount++] = &input[i]; } // If we don't have enough top/bottom vertices (e.g., Y values are all different), @@ -2960,7 +2960,7 @@ static inline void sw_quad_sort_cw(const sw_vertex_t* *output) } // Sort bottom vertices by X (left to right) - if (bottomCount == 2 && bottom[0]->screen[0] > bottom[1]->screen[0]) + if ((bottomCount == 2) && (bottom[0]->screen[0] > bottom[1]->screen[0])) { const sw_vertex_t *temp = bottom[0]; bottom[0] = bottom[1]; @@ -2997,7 +2997,7 @@ static inline void FUNC_NAME(void) int width = xMax - xMin; \ int height = yMax - yMin; \ \ - if (width == 0 || height == 0) return; \ + if ((width == 0) || (height == 0)) return; \ \ float wRcp = (width > 0.0f)? 1.0f/width : 0.0f; \ float hRcp = (height > 0.0f)? 1.0f/height : 0.0f; \ @@ -3157,7 +3157,7 @@ static inline void sw_quad_render(void) if (RLSW.vertexCounter < 3) return; - if (RLSW.vertexCounter == 4 && sw_quad_is_axis_aligned()) + if ((RLSW.vertexCounter == 4) && sw_quad_is_axis_aligned()) { if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND(); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_DEPTH_BLEND(); @@ -3304,7 +3304,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ int dy = y2 - y1; \ \ /* Handling of lines that are more horizontal or vertical */ \ - if (dx == 0 && dy == 0) \ + if ((dx == 0) && (dy == 0)) \ { \ /* TODO: A point should be rendered here */ \ return; \ @@ -3444,7 +3444,7 @@ void FUNC_NAME(const sw_vertex_t *v1, const sw_vertex_t *v2) \ \ RASTER_FUNC(v1, v2); \ \ - if (dx != 0 && abs(dy/dx) < 1) \ + if ((dx != 0) && (abs(dy/dx) < 1)) \ { \ int wy = (int)((RLSW.lineWidth - 1.0f)*abs(dx)/sqrtf(dx*dx + dy*dy)); \ wy >>= 1; \ @@ -3551,13 +3551,13 @@ static inline void FUNC_NAME(int x, int y, float z, const float color[4]) \ { \ if (CHECK_BOUNDS == 1) \ { \ - if (x < RLSW.vpMin[0] || x >= RLSW.vpMax[0]) return; \ - if (y < RLSW.vpMin[1] || y >= RLSW.vpMax[1]) return; \ + if ((x < RLSW.vpMin[0]) || (x >= RLSW.vpMax[0])) return; \ + if ((y < RLSW.vpMin[1]) || (y >= RLSW.vpMax[1])) return; \ } \ else if (CHECK_BOUNDS == SW_SCISSOR_TEST) \ { \ - if (x < RLSW.scMin[0] || x >= RLSW.scMax[0]) return; \ - if (y < RLSW.scMin[1] || y >= RLSW.scMax[1]) return; \ + if ((x < RLSW.scMin[0]) || (x >= RLSW.scMax[0])) return; \ + if ((y < RLSW.scMin[1]) || (y >= RLSW.scMax[1])) return; \ } \ \ int offset = y*RLSW.framebuffer.width + x; \ @@ -3718,12 +3718,12 @@ static inline bool sw_is_texture_valid(uint32_t id) static inline bool sw_is_texture_filter_valid(int filter) { - return (filter == SW_NEAREST || filter == SW_LINEAR); + return ((filter == SW_NEAREST) || (filter == SW_LINEAR)); } static inline bool sw_is_texture_wrap_valid(int wrap) { - return (wrap == SW_REPEAT || wrap == SW_CLAMP); + return ((wrap == SW_REPEAT) || (wrap == SW_CLAMP)); } static inline bool sw_is_draw_mode_valid(int mode) @@ -4392,7 +4392,7 @@ void swRotatef(float angle, float x, float y, float z) float lengthSq = x*x + y*y + z*z; - if (lengthSq != 1.0f && lengthSq != 0.0f) + if ((lengthSq != 1.0f) && (lengthSq != 0.0f)) { float invLength = 1.0f/sqrtf(lengthSq); x *= invLength; From 3c5b3f1c1737cc20940f41ec338669d8b032abdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20=E2=9D=A4=EF=B8=8F?= Date: Tue, 21 Oct 2025 04:11:19 -0400 Subject: [PATCH 11/25] [examples] Added `shapes_lines_drawing` (#5283) * Added shapes_lines_drawing Example * store result of clamp * conventions * fixed more brackets * buffer comments --- examples/shapes/shapes_lines_drawing.c | 146 +++++++++++++++++++++++ examples/shapes/shapes_lines_drawing.png | Bin 0 -> 34456 bytes 2 files changed, 146 insertions(+) create mode 100644 examples/shapes/shapes_lines_drawing.c create mode 100644 examples/shapes/shapes_lines_drawing.png diff --git a/examples/shapes/shapes_lines_drawing.c b/examples/shapes/shapes_lines_drawing.c new file mode 100644 index 000000000..9347acaf3 --- /dev/null +++ b/examples/shapes/shapes_lines_drawing.c @@ -0,0 +1,146 @@ +/******************************************************************************************* +* +* raylib [shapes] example - lines drawing +* +* Example complexity rating: [★☆☆☆] 1/4 +* +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6 +* +* Example contributed by Robin (@RobinsAviary) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2025-2025 Robin (@RobinsAviary) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - lines drawing"); + + // Hint text that shows before you click the screen + bool startText = true; + + // The mouse's position on the previous frame + Vector2 mousePositionPrevious = GetMousePosition(); + + // The canvas to draw lines on + RenderTexture canvas = LoadRenderTexture(screenWidth, screenHeight); + + // The background color of the canvas + const Color backgroundColor = RAYWHITE; + + // The line's thickness + float lineThickness = 8.0f; + // The lines hue (in HSV, from 0-360) + float lineHue = 0.0f; + + // Clear the canvas to the background color + BeginTextureMode(canvas); + ClearBackground(backgroundColor); + EndTextureMode(); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + // Disable the hint text once the user clicks + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && startText) + { + startText = false; + } + + // Clear the canvas when the user middle-clicks + if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) + { + BeginTextureMode(canvas); + ClearBackground(backgroundColor); + EndTextureMode(); + } + + // Store whether the left and right buttons are down + bool leftButtonDown = IsMouseButtonDown(MOUSE_BUTTON_LEFT); + bool rightButtonDown = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); + + if (leftButtonDown || rightButtonDown) + { + // The color for the line + Color drawColor; + + if (leftButtonDown) + { + // Increase the hue value by the distance our cursor has moved since the last frame (divided by 3) + lineHue += Vector2Distance(mousePositionPrevious, GetMousePosition())/3.0f; + + // While the hue is >=360, subtract it to bring it down into the range 0-360 + // This is more visually accurate than resetting to zero + while (lineHue >= 360.0f) + { + lineHue -= 360.0f; + } + + // Create the final color + drawColor = ColorFromHSV(lineHue, 1.0f, 1.0f); + } + else if (rightButtonDown) + { + // Use the background color as an "eraser" + drawColor = backgroundColor; + } + + // Draw the line onto the canvas + BeginTextureMode(canvas); + // Circles act as "caps", smoothing corners + DrawCircleV(mousePositionPrevious, lineThickness/2.0f, drawColor); + DrawCircleV(GetMousePosition(), lineThickness/2.0f, drawColor); + DrawLineEx(mousePositionPrevious, GetMousePosition(), lineThickness, drawColor); + EndTextureMode(); + } + + // Update line thickness based on mousewheel + lineThickness += GetMouseWheelMove(); + lineThickness = Clamp(lineThickness, 1.0, 500.0f); + + // Update mouse's previous position + mousePositionPrevious = GetMousePosition(); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + // Draw the render texture to the screen, flipped vertically to make it appear top-side up + DrawTextureRec(canvas.texture, (Rectangle){ 0.0f, 0.0f, (float)canvas.texture.width,(float)-canvas.texture.height }, Vector2Zero(), WHITE); + + // Draw the preview circle + if (!leftButtonDown) DrawCircleLinesV(GetMousePosition(), lineThickness/2.0f, (Color){ 127, 127, 127, 127 }); + + // Draw the hint text + if (startText) DrawText("try clicking and dragging!", 275, 215, 20, LIGHTGRAY); + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + // Unload the canvas render texture + UnloadRenderTexture(canvas); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/shapes/shapes_lines_drawing.png b/examples/shapes/shapes_lines_drawing.png new file mode 100644 index 0000000000000000000000000000000000000000..59ef6593439152c128657ecacb80cc91eb146e07 GIT binary patch literal 34456 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%0Dx%j?@0GXzgbsVf3o;ci0R2uE<-DD}`XSk`QvzIc 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 Date: Tue, 21 Oct 2025 13:51:03 +0200 Subject: [PATCH 12/25] Removed trailing spaces --- .github/workflows/build_windows.yml | 12 +-- examples/core/core_3d_camera_first_person.c | 1 - examples/core/core_render_texture.c | 16 ++-- examples/core/core_text_file_loading.c | 10 +-- examples/shapes/shapes_simple_particles.c | 8 +- src/platforms/rcore_android.c | 2 +- src/platforms/rcore_desktop_glfw.c | 7 +- src/platforms/rcore_desktop_rgfw.c | 12 +-- src/platforms/rcore_desktop_win32.c | 84 ++++++++++----------- src/platforms/rcore_drm.c | 14 ++-- src/rcore.c | 4 +- src/rtext.c | 6 +- tools/rexm/rexm.c | 8 +- 13 files changed, 91 insertions(+), 93 deletions(-) diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index 9e87b5870..7a92c208a 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -40,10 +40,10 @@ jobs: ziptarget: "win64" - compiler: msvc16 ARCH: "x86" - VSARCHPATH: "Win32" + VSARCHPATH: "Win32" ziptarget: "win32" - compiler: msvc16 - ARCH: "x64" + ARCH: "x64" VSARCHPATH: "x64" ziptarget: "win64" - compiler: msvc16 @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@master - + - name: Setup Release Version run: | echo "RELEASE_NAME=raylib-${{ github.event.release.tag_name }}_${{ matrix.ziptarget }}_${{ matrix.compiler }}" >> $GITHUB_ENV @@ -69,7 +69,7 @@ jobs: if: github.event_name == 'release' && github.event.action == 'published' - name: Setup Environment - run: | + run: | dir mkdir build cd build @@ -98,7 +98,7 @@ jobs: if: | matrix.compiler == 'mingw-w64' && matrix.arch == 'i686' - + - name: Build Library (MinGW-w64 64bit) run: | cd src @@ -144,7 +144,7 @@ jobs: with: name: ${{ env.RELEASE_NAME }}.zip path: ./build/${{ env.RELEASE_NAME }}.zip - + - name: Upload Artifact to Release uses: softprops/action-gh-release@v1 with: diff --git a/examples/core/core_3d_camera_first_person.c b/examples/core/core_3d_camera_first_person.c index 8f4dc70fb..64368c4aa 100644 --- a/examples/core/core_3d_camera_first_person.c +++ b/examples/core/core_3d_camera_first_person.c @@ -119,7 +119,6 @@ int main(void) // Some default standard keyboard/mouse inputs are hardcoded to simplify use // For advanced camera controls, it's recommended to compute camera movement manually UpdateCamera(&camera, cameraMode); // Update camera - /* // Camera PRO usage example (EXPERIMENTAL) // This new camera function allows custom movement/rotation values to be directly provided diff --git a/examples/core/core_render_texture.c b/examples/core/core_render_texture.c index 47dc66e0f..e30220a73 100644 --- a/examples/core/core_render_texture.c +++ b/examples/core/core_render_texture.c @@ -26,7 +26,7 @@ int main(void) const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [core] example - render texture"); - + // Define a render texture to render int renderTextureWidth = 300; int renderTextureHeight = 300; @@ -62,14 +62,14 @@ int main(void) //----------------------------------------------------- // Draw our scene to the render texture BeginTextureMode(target); - + ClearBackground(SKYBLUE); - + DrawRectangle(0, 0, 20, 20, RED); DrawCircleV(ballPosition, (float)ballRadius, MAROON); - + EndTextureMode(); - + // Draw render texture to main framebuffer BeginDrawing(); @@ -77,14 +77,14 @@ int main(void) // Draw our render texture with rotation applied // NOTE 1: We set the origin of the texture to the center of the render texture - // NOTE 2: We flip vertically the texture setting negative source rectangle height - DrawTexturePro(target.texture, + // NOTE 2: We flip vertically the texture setting negative source rectangle height + DrawTexturePro(target.texture, (Rectangle){ 0, 0, (float)target.texture.width, (float)-target.texture.height }, (Rectangle){ screenWidth/2.0f, screenHeight/2.0f, (float)target.texture.width, (float)target.texture.height }, (Vector2){ target.texture.width/2.0f, target.texture.height/2.0f }, rotation, WHITE); DrawText("DRAWING BOUNCING BALL INSIDE RENDER TEXTURE!", 10, screenHeight - 40, 20, BLACK); - + DrawFPS(10, 10); diff --git a/examples/core/core_text_file_loading.c b/examples/core/core_text_file_loading.c index 4b334e811..033fd4dc6 100644 --- a/examples/core/core_text_file_loading.c +++ b/examples/core/core_text_file_loading.c @@ -64,7 +64,7 @@ int main(void) if (lines[i][j] == ' ') { // Making a C Style string by adding a '\0' at the required location so that we can use the MeasureText function - lines[i][j] = '\0'; + lines[i][j] = '\0'; // Checking if the text has crossed the wrapWidth, then going back and inserting a newline if (MeasureText(lines[i] + lastWrapStart, fontSize) > wrapWidth) @@ -112,8 +112,8 @@ int main(void) cam.target.y -= scroll*fontSize*1.5f; // Choosing an arbitrary speed for scroll if (cam.target.y < 0) cam.target.y = 0; // Snapping to 0 if we go too far back - - // Ensuring that the camera does not scroll past all text + + // Ensuring that the camera does not scroll past all text if (cam.target.y > textHeight - screenHeight + textTop) cam.target.y = textHeight - screenHeight + textTop; @@ -133,10 +133,10 @@ int main(void) { // Each time we go through and calculate the height of the text to move the cursor appropriately Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], fontSize, 2); - + DrawText(lines[i], 10, t, fontSize, RED); - // Inserting extra space for real newlines, + // Inserting extra space for real newlines, // wrapped lines are rendered closer together t += size.y + 10; } diff --git a/examples/shapes/shapes_simple_particles.c b/examples/shapes/shapes_simple_particles.c index 7c2a598c2..c5d9612c3 100644 --- a/examples/shapes/shapes_simple_particles.c +++ b/examples/shapes/shapes_simple_particles.c @@ -74,7 +74,7 @@ int main(void) // Definition of particles Particle *particles = (Particle*)RL_CALLOC(MAX_PARTICLES, sizeof(Particle)); // Particle array CircularBuffer circularBuffer = { 0, 0, particles }; - + // Particle emitter parameters int emissionRate = -2; // Negative: on average every -X frames. Positive: particles per frame ParticleType currentType = WATER; @@ -100,7 +100,7 @@ int main(void) // Update the parameters of each particle UpdateParticles(&circularBuffer, screenWidth, screenHeight); - + // Remove dead particles from the circular buffer UpdateCircularBuffer(&circularBuffer); @@ -252,10 +252,10 @@ static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int // Disable particle when out of screen Vector2 center = circularBuffer->buffer[i].position; float radius = circularBuffer->buffer[i].radius; - + if ((center.x < -radius) || (center.x > (screenWidth + radius)) || (center.y < -radius) || (center.y > (screenHeight + radius))) - { + { circularBuffer->buffer[i].alive = false; } } diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 6ecc41888..47dcce32b 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -461,7 +461,7 @@ int GetCurrentMonitor(void) if (display == NULL) { TRACELOG(LOG_ERROR, "GetCurrentMonitor() couldn't get the display object"); - } + } else { jclass displayClass = (*env)->FindClass(env, "android/view/Display"); diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 729ca308c..5dde1df67 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1048,6 +1048,7 @@ Image GetClipboardImage(void) void ShowCursor(void) { glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + CORE.Input.Mouse.cursorHidden = false; } @@ -1055,6 +1056,7 @@ void ShowCursor(void) void HideCursor(void) { glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + CORE.Input.Mouse.cursorHidden = true; } @@ -1075,13 +1077,10 @@ void EnableCursor(void) void DisableCursor(void) { // Reset mouse position within the window area before disabling cursor - SetMousePosition(CORE.Window.screen.width, CORE.Window.screen.height); + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); glfwSetInputMode(platform.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); - // Set cursor position in the middle - SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); - if (glfwRawMouseMotionSupported()) glfwSetInputMode(platform.handle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); CORE.Input.Mouse.cursorLocked = true; diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 52ec6ac94..86842140d 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -80,7 +80,7 @@ void CloseWindow(void); #define CloseWindow CloseWindow_win32 #define ShowCursor __imp_ShowCursor #define _APISETSTRING_ - + #undef MAX_PATH #if defined(__cplusplus) @@ -262,7 +262,7 @@ static int RGFW_gpConvTable[18] = { [RGFW_gamepadRight] = GAMEPAD_BUTTON_LEFT_FACE_RIGHT, [RGFW_gamepadDown] = GAMEPAD_BUTTON_LEFT_FACE_DOWN, [RGFW_gamepadLeft] = GAMEPAD_BUTTON_LEFT_FACE_LEFT, - [RGFW_gamepadL3] = GAMEPAD_BUTTON_LEFT_THUMB, + [RGFW_gamepadL3] = GAMEPAD_BUTTON_LEFT_THUMB, [RGFW_gamepadR3] = GAMEPAD_BUTTON_RIGHT_THUMB, }; @@ -933,7 +933,7 @@ void PollInputEvents(void) // because ProcessGestureEvent() is just called on an event, not every frame UpdateGestures(); #endif - + // Reset keys/chars pressed registered CORE.Input.Keyboard.keyPressedQueueCount = 0; CORE.Input.Keyboard.charPressedQueueCount = 0; @@ -1025,7 +1025,7 @@ void PollInputEvents(void) CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event->droppedFiles[i]); - + CORE.Window.dropFileCount++; } else if (CORE.Window.dropFileCount < 1024) @@ -1229,7 +1229,7 @@ void PollInputEvents(void) int button = (axis == GAMEPAD_AXIS_LEFT_TRIGGER)? GAMEPAD_BUTTON_LEFT_TRIGGER_2 : GAMEPAD_BUTTON_RIGHT_TRIGGER_2; int pressed = (value > 0.1f); CORE.Input.Gamepad.currentButtonState[event->gamepad][button] = pressed; - + if (pressed) CORE.Input.Gamepad.lastButtonPressed = button; else if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; } @@ -1345,7 +1345,7 @@ int InitPlatform(void) // TODO: Is this needed by raylib now? // If so, rcore_desktop_sdl should be updated too //SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); - + if (CORE.Window.flags & FLAG_VSYNC_HINT) RGFW_window_swapInterval(platform.window, 1); RGFW_window_makeCurrent(platform.window); diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 7ac6138bd..de9a66911 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -84,8 +84,8 @@ // Types and Structures Definition //---------------------------------------------------------------------------------- -// NOTE: appScreenSize is the last screen size requested by the app, -// the backend must keep the client area this size (after DPI scaling is applied) +// NOTE: appScreenSize is the last screen size requested by the app, +// the backend must keep the client area this size (after DPI scaling is applied) // when the window isn't fullscreen/maximized/minimized typedef struct { HWND hwnd; // Window handler @@ -141,7 +141,7 @@ static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; AToWCopy(inAnsi, outWstr, outLen); \ outWstr[outLen] = 0; \ } while (0) - + #define STYLE_MASK_ALL 0xffffffff #define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE) #define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY) @@ -206,10 +206,10 @@ static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; // Types and Structures Definition //---------------------------------------------------------------------------------- // Maximize-minimize request types -typedef enum { - MIZED_NONE, - MIZED_MIN, - MIZED_MAX +typedef enum { + MIZED_NONE, + MIZED_MIN, + MIZED_MAX } Mized; // Flag operations @@ -234,7 +234,7 @@ typedef struct { static size_t AToWLen(const char *ascii) { int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, NULL, 0); - + if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "WIN32: Failed to calculate wide length [ERROR: %u]", GetLastError()); return sizeNeeded; @@ -268,7 +268,7 @@ static DWORD MakeWindowStyle(unsigned flags) // it improves efficiency, plus, windows adds this flag automatically anyway // so it keeps our flags in sync with the OS DWORD style = WS_CLIPSIBLINGS; - + style |= (flags & FLAG_WINDOW_HIDDEN)? 0 : WS_VISIBLE; style |= (flags & FLAG_WINDOW_RESIZABLE)? STYLE_FLAGS_RESIZABLE : 0; style |= (flags & FLAG_WINDOW_UNDECORATED)? STYLE_FLAGS_UNDECORATED_ON : STYLE_FLAGS_UNDECORATED_OFF; @@ -339,7 +339,7 @@ static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expect static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style) { RECT rect = { 0, 0, clientSize.cx, clientSize.cy }; - + int result = AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi); if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to adjust window rect [ERROR: %lu]", GetLastError()); @@ -442,8 +442,8 @@ static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigne static BOOL IsWindows10Version1703OrGreaterWin32(void) { HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); - - DWORD (*Verify)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG) = + + DWORD (*Verify)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG) = (DWORD (*)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG))GetProcAddress(ntdll, "RtlVerifyVersionInfo"); if (!Verify) { @@ -461,7 +461,7 @@ static BOOL IsWindows10Version1703OrGreaterWin32(void) VER_SET_CONDITION(cond, VER_MAJORVERSION, VER_GREATER_EQUAL); VER_SET_CONDITION(cond, VER_MINORVERSION, VER_GREATER_EQUAL); VER_SET_CONDITION(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL); - + return 0 == (*Verify)(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, cond); } @@ -473,8 +473,8 @@ static void *WglGetProcAddress(const char *procname) if ((proc == NULL) || // NOTE: Some GPU drivers could return following // invalid sentinel values instead of NULL - (proc == (void *)0x1) || - (proc == (void *)0x2) || + (proc == (void *)0x1) || + (proc == (void *)0x2) || (proc == (void *)0x3) || (proc == (void *)-1)) { @@ -767,7 +767,7 @@ static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *style, Fla } // Adopt window resize -// NOTE: Call when the window is rezised, returns true +// NOTE: Call when the window is rezised, returns true // if the new window size should update the desired app size static bool AdoptWindowResize(unsigned flags) { @@ -776,7 +776,7 @@ static bool AdoptWindowResize(unsigned flags) if (flags & FLAG_FULLSCREEN_MODE) return false; if (flags & FLAG_BORDERLESS_WINDOWED_MODE) return false; if (!(flags & FLAG_WINDOW_RESIZABLE)) return false; - + return true; } @@ -947,10 +947,10 @@ void SetWindowIcons(Image *images, int count) void SetWindowTitle(const char *title) { CORE.Window.title = title; - + WCHAR *titleWide = NULL; A_TO_W_ALLOCA(titleWide, CORE.Window.title); - + int result = SetWindowTextW(platform.hwnd, titleWide); if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to set window title [ERROR: %lu]", GetLastError()); } @@ -1023,7 +1023,7 @@ void *GetWindowHandle(void) int GetMonitorCount(void) { int count = 0; - + int result = EnumDisplayMonitors(NULL, NULL, CountMonitorsProc, (LPARAM)&count); if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError()); @@ -1040,7 +1040,7 @@ int GetCurrentMonitor(void) info.needle = monitor; info.index = 0; info.matchIndex = -1; - + int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&info); if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError()); @@ -1127,7 +1127,7 @@ const char *GetClipboardText(void) Image GetClipboardImage(void) { Image image = { 0 }; - + TRACELOG(LOG_WARNING, "GetClipboardText not implemented"); return image; @@ -1193,7 +1193,7 @@ void DisableCursor(void) TRACELOG(LOG_INFO, "WIN32: Clip cursor client rect: [%d,%d %d,%d], top-left: (%d,%d)", clientRect.left, clientRect.top, clientRect.right, clientRect.bottom, topleft.x, topleft.y); - + LONG centerX = topleft.x + width/2; LONG centerY = topleft.y + height/2; RECT clipRect = { centerX, centerY, centerX + 1, centerY + 1 }; @@ -1502,7 +1502,7 @@ int InitPlatform(void) if (IsWindows10Version1703OrGreaterWin32()) { TRACELOG(LOG_INFO, "DpiAware: >=Win10Creators"); - if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) + if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) TRACELOG(LOG_ERROR, "%s failed, error %u", "SetProcessDpiAwarenessContext", GetLastError()); } else @@ -1620,7 +1620,7 @@ int InitPlatform(void) } CORE.Window.ready = true; - + // TODO: Should this function be called before or after drawing context is created? --> After swInit() called! //UpdateWindowSize(UPDATE_WINDOW_FIRST, platform.hwnd, platform.appScreenWidth, platform.appScreenHeight, platform.desiredFlags); UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); @@ -1660,7 +1660,7 @@ int InitPlatform(void) //---------------------------------------------------------------------------- TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully"); - + return 0; } @@ -1691,7 +1691,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara FlagsOp flagsOp = { 0 }; FlagsOp *deferredFlags = &flagsOp; - + // Message processing //------------------------------------------------------------------------------------ switch (msg) @@ -1707,13 +1707,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara // Clean up for window destruction if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer { - if (platform.hdcmem) + if (platform.hdcmem) { DeleteDC(platform.hdcmem); platform.hdcmem = NULL; } - if (platform.hbitmap) + if (platform.hbitmap) { DeleteObject(platform.hbitmap); // Clears platform.pixels data platform.hbitmap = NULL; @@ -1751,9 +1751,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) { // TODO: Enforce min/max size - } + } else TRACELOG(LOG_WARNING, "WIN32: WINDOW: Trying to resize a non-resizable window"); - + result = TRUE; } break; case WM_STYLECHANGING: @@ -1770,11 +1770,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara SIZE clientSize = { rect.right, rect.bottom }; SIZE oldSize = CalcWindowSize(dpi, clientSize, ss->styleOld); SIZE newSize = CalcWindowSize(dpi, clientSize, ss->styleNew); - + if (oldSize.cx != newSize.cx || oldSize.cy != newSize.cy) { TRACELOG(LOG_INFO, "WIN32: WINDOW: Resize from style change [%dx%d] to [%dx%d]", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy); - + if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) { // looks like windows will automatically "unminimize" a window @@ -1795,7 +1795,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara bool isIconic = IsIconic(hwnd); bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE)); if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "WIN32: IsIconic state different from WS_MINIMIZED state"); - + if (isIconic) mized = MIZED_MIN; else { @@ -1871,7 +1871,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara }; inoutSize->cx = desired.cx; inoutSize->cy = desired.cy; - + result = TRUE; } break; case WM_DPICHANGED: @@ -1929,7 +1929,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara case WM_RBUTTONUP : HandleMouseButton(MOUSE_BUTTON_RIGHT, 0); break; case WM_MBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_MIDDLE, 1); break; case WM_MBUTTONUP : HandleMouseButton(MOUSE_BUTTON_MIDDLE, 0); break; - case WM_XBUTTONDOWN: + case WM_XBUTTONDOWN: { switch (HIWORD(wparam)) { @@ -1975,11 +1975,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara static void HandleKey(WPARAM wparam, LPARAM lparam, char state) { KeyboardKey key = GetKeyFromWparam(wparam); - + // TODO: Use scancode? //BYTE scancode = lparam >> 16; //TRACELOG(LOG_INFO, "KEY key=%d vk=%lu scan=%u = %u", key, wparam, scancode, state); - + if (key != KEY_NULL) { CORE.Input.Keyboard.currentKeyState[key] = state; @@ -2003,9 +2003,9 @@ static void HandleRawInput(LPARAM lparam) UINT inputSize = sizeof(input); UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER)); - + if (size == (UINT)-1) TRACELOG(LOG_ERROR, "WIN32: Failed to get raw input data [ERROR: %lu]", GetLastError()); - + if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "WIN32: Unexpected WM_INPUT type %lu", input.header.dwType); if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!"); @@ -2043,7 +2043,7 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) unsigned int screenHeight = highdpi? (unsigned int)(((float)clientSize.cy)/dpiScale) : clientSize.cy; CORE.Window.screen.width = screenWidth; CORE.Window.screen.height = screenHeight; - + if (AdoptWindowResize(CORE.Window.flags)) { TRACELOG(LOG_DEBUG, "WIN32: WINDOW: Updating app size to [%ix%i] from window resize", screenWidth, screenHeight); @@ -2062,7 +2062,7 @@ static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) { DWORD current = STYLE_MASK_WRITABLE & MakeWindowStyle(CORE.Window.flags); DWORD desired = STYLE_MASK_WRITABLE & MakeWindowStyle(desiredFlags); - + if (current != desired) { SetLastError(0); diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 58e820af1..a08a76bce 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -864,7 +864,7 @@ void SwapScreenBuffer(void) // Create framebuffer with the correct format uint32_t fb = 0; result = drmModeAddFB(platform.fd, width, height, depth, bpp, creq.pitch, creq.handle, &fb); - if (result != 0) + if (result != 0) { TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d (%s)", result, strerror(errno)); struct drm_mode_destroy_dumb dreq = { 0 }; @@ -974,12 +974,12 @@ void SwapScreenBuffer(void) // Set CRTC with better error handling result = drmModeSetCrtc(platform.fd, crtcId, fb, 0, 0, &platform.connector->connector_id, 1, mode); - if (result != 0) + if (result != 0) { TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d (%s)", result, strerror(errno)); TRACELOG(LOG_ERROR, "DISPLAY: CRTC ID: %u, FB ID: %u, Connector ID: %u", crtcId, fb, platform.connector->connector_id); TRACELOG(LOG_ERROR, "DISPLAY: Mode: %dx%d@%d", mode->hdisplay, mode->vdisplay, mode->vrefresh); - + drmModeRmFB(platform.fd, fb); struct drm_mode_destroy_dumb dreq = {0}; dreq.handle = creq.handle; @@ -1229,7 +1229,7 @@ int InitPlatform(void) } TRACELOG(LOG_TRACE, "DISPLAY: Connector %i modes detected: %i", i, con->count_modes); - TRACELOG(LOG_TRACE, "DISPLAY: Connector %i status: %s", i, + TRACELOG(LOG_TRACE, "DISPLAY: Connector %i status: %s", i, (con->connection == DRM_MODE_CONNECTED) ? "CONNECTED" : (con->connection == DRM_MODE_DISCONNECTED) ? "DISCONNECTED" : (con->connection == DRM_MODE_UNKNOWNCONNECTION) ? "UNKNOWN" : "OTHER"); @@ -1357,10 +1357,10 @@ int InitPlatform(void) platform.modeIndex = 0; CORE.Window.display.width = platform.connector->modes[0].hdisplay; CORE.Window.display.height = platform.connector->modes[0].vdisplay; - - TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u) for software rendering", + + TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u) for software rendering", platform.connector->modes[0].name, - platform.connector->modes[0].hdisplay, + platform.connector->modes[0].hdisplay, platform.connector->modes[0].vdisplay, (platform.connector->modes[0].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', platform.connector->modes[0].vrefresh); diff --git a/src/rcore.c b/src/rcore.c index 5dea97c22..350490ad1 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2524,7 +2524,7 @@ int MakeDirectory(const char *dirPath) // Create final directory if (!DirectoryExists(pathcpy)) MKDIR(pathcpy); RL_FREE(pathcpy); - + // In case something failed and requested directory // was not successfully created, return -1 if (!DirectoryExists(dirPath)) return -1; @@ -3148,7 +3148,7 @@ unsigned int *ComputeSHA256(unsigned char *data, int dataSize) unsigned char *block = buffer + (blockN*64); unsigned int w[64]; for (int i = 0; i < 16; i++) - { + { w[i] = ((unsigned int)block[i*4 + 0] << 24) | ((unsigned int)block[i*4 + 1] << 16) | diff --git a/src/rtext.c b/src/rtext.c index 9951dc66d..b4cd560ba 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1701,9 +1701,9 @@ char *GetTextBetween(const char *text, const char *begin, const char *end) char *TextReplace(const char *text, const char *search, const char *replacement) { char *result = NULL; - + if (!text || !search) return NULL; // Sanity check - + char *insertPoint = NULL; // Next insert point char *temp = NULL; // Temp pointer int searchLen = 0; // Search string length of (the string to remove) @@ -1753,7 +1753,7 @@ char *TextReplaceBetween(const char *text, const char *begin, const char *end, c char *result = NULL; if (!text || !begin || !end) return NULL; // Sanity check - + int beginIndex = TextFindIndex(text, begin); if (beginIndex > -1) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 20fc1d5e5..d668655c8 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -239,7 +239,7 @@ int main(int argc, char *argv[]) // build : Build example for Desktop and Web platforms // validate : Validate examples collection, generates report // update : Validate and update examples collection, generates report - + if (strcmp(argv[1], "create") == 0) { // Check for valid upcoming argument @@ -394,8 +394,8 @@ int main(int argc, char *argv[]) { // Support building not only individual examples but categories and "ALL" if ((strcmp(argv[2], "ALL") == 0) || TextInList(argv[2], exCategories, REXM_MAX_EXAMPLE_CATEGORIES)) - { - // Category/ALL rebuilt requested + { + // Category/ALL rebuilt requested strcpy(exRebuildRequested, argv[2]); } else @@ -2432,7 +2432,7 @@ static void UpdateWebMetadata(const char *exHtmlPath, const char *exFilePath) static bool TextInList(const char *text, const char **list, int listCount) { bool result = false; - + for (int i = 0; i < listCount; i++) { if (TextIsEqual(text, list[i])) { result = true; break; } From 77b9214575ec60abe6824fd7c8abf40169c8e966 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2025 20:07:32 +0200 Subject: [PATCH 13/25] Update rcore_desktop_sdl.c --- src/platforms/rcore_desktop_sdl.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 6655db460..a5f626ecc 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -104,7 +104,6 @@ typedef struct { SDL_GameController *gamepad[MAX_GAMEPADS]; SDL_JoystickID gamepadId[MAX_GAMEPADS]; // Joystick instance ids, they do not start from 0 SDL_Cursor *cursor; - bool cursorRelative; } PlatformData; //---------------------------------------------------------------------------------- @@ -1209,13 +1208,13 @@ void EnableCursor(void) SDL_SetRelativeMouseMode(SDL_FALSE); #if defined(USING_VERSION_SDL3) - // SDL_ShowCursor() has been split into three functions: SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() + // NOTE: SDL_ShowCursor() has been split into three functions: + // SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() SDL_ShowCursor(); #else SDL_ShowCursor(SDL_ENABLE); #endif - platform.cursorRelative = false; CORE.Input.Mouse.cursorLocked = false; } @@ -1224,7 +1223,6 @@ void DisableCursor(void) { SDL_SetRelativeMouseMode(SDL_TRUE); - platform.cursorRelative = true; CORE.Input.Mouse.cursorLocked = true; } @@ -1332,7 +1330,7 @@ void PollInputEvents(void) CORE.Input.Mouse.currentWheelMove.y = 0; // Register previous mouse position - if (platform.cursorRelative) CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; + if (CORE.Input.Mouse.cursorLocked) CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; else CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; // Reset last gamepad button/axis registered state @@ -1634,7 +1632,7 @@ void PollInputEvents(void) } break; case SDL_MOUSEMOTION: { - if (platform.cursorRelative) + if (CORE.Input.Mouse.cursorLocked) { CORE.Input.Mouse.currentPosition.x = (float)event.motion.xrel; CORE.Input.Mouse.currentPosition.y = (float)event.motion.yrel; From 99ed81461526b37583cac323a51a5d019b49b16c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 21 Oct 2025 20:07:42 +0200 Subject: [PATCH 14/25] Update rcore_desktop_win32.c --- src/platforms/rcore_desktop_win32.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index de9a66911..9b66f8c4b 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -1136,17 +1136,17 @@ Image GetClipboardImage(void) // Show mouse cursor void ShowCursor(void) { - CORE.Input.Mouse.cursorHidden = false; SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); + CORE.Input.Mouse.cursorHidden = false; } // Hides mouse cursor void HideCursor(void) { - // NOTE: we use SetCursor instead of ShowCursor because it makes it easy - // to only hide the cursor while it's inside the client area - CORE.Input.Mouse.cursorHidden = true; + // NOTE: We use SetCursor() instead of ShowCursor() because + // it makes it easy to only hide the cursor while it's inside the client area SetCursor(NULL); + CORE.Input.Mouse.cursorHidden = true; } // Enables cursor (unlock cursor) @@ -1346,7 +1346,7 @@ void PollInputEvents(void) //---------------------------------------------------------------------------------- // Initialize modern OpenGL context -// NOTE: We need to create a dummy context first to query requried extensions +// NOTE: We need to create a dummy context first to query required extensions HGLRC InitOpenGL(HWND hwnd, HDC hdc) { // First, create a dummy context to get WGL extensions @@ -1621,8 +1621,7 @@ int InitPlatform(void) CORE.Window.ready = true; - // TODO: Should this function be called before or after drawing context is created? --> After swInit() called! - //UpdateWindowSize(UPDATE_WINDOW_FIRST, platform.hwnd, platform.appScreenWidth, platform.appScreenHeight, platform.desiredFlags); + // Update flags (in case of deferred state change required) UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); CORE.Window.render.width = CORE.Window.screen.width; @@ -1887,6 +1886,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_SETCURSOR: { + // Called when mouse moves, enters/leaves window... if (LOWORD(lparam) == HTCLIENT) { SetCursor(CORE.Input.Mouse.cursorHidden? NULL : LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); @@ -1972,6 +1972,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara return result; } +// Handle keyboard input event static void HandleKey(WPARAM wparam, LPARAM lparam, char state) { KeyboardKey key = GetKeyFromWparam(wparam); @@ -1991,12 +1992,15 @@ static void HandleKey(WPARAM wparam, LPARAM lparam, char state) // TODO: Add key to the queue as well? } +// Handle mouse button input event static void HandleMouseButton(int button, char state) { + // Register current mouse button state CORE.Input.Mouse.currentButtonState[button] = state; CORE.Input.Touch.currentTouchState[button] = state; } +// Handle raw input event static void HandleRawInput(LPARAM lparam) { RAWINPUT input = { 0 }; @@ -2020,6 +2024,7 @@ static void HandleRawInput(LPARAM lparam) //if (CORE.Input.Mouse.currentPosition.y != 0) abort(); } +// Handle window resizing event static void HandleWindowResize(HWND hwnd, int *width, int *height) { if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return; @@ -2051,10 +2056,8 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) *height = screenHeight; } - CORE.Window.screenScale = MatrixScale( - (float)CORE.Window.render.width/CORE.Window.screen.width, - (float)CORE.Window.render.height/CORE.Window.screen.height, - 1.0f); + CORE.Window.screenScale = MatrixScale( (float)CORE.Window.render.width/CORE.Window.screen.width, + (float)CORE.Window.render.height/CORE.Window.screen.height, 1.0f); } // Update window style From 1b5a14e5167755d54e8b4e9851b9167302641d2f Mon Sep 17 00:00:00 2001 From: sleeptightAnsiC <91839286+sleeptightAnsiC@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:07:04 +0200 Subject: [PATCH 15/25] [rcore_desktop_sdl] fix: handle monitor ID correctly on SDL3 (#5290) SDL3 uses ID when dealing with monitors, unlike SDL2 which uses Index for the same thing. This problem was already fixed in multiple places by use of preprocessor branches, so I did the very same thing. Please, notice that this is a pretty bad solution to this problem, and I only did it to keep it consistent with the rest of the code. The more about why it's not correct is mentioned here: https://github.com/raysan5/raylib/issues/5256#issuecomment-3429156919 Hopefully, someone will refactor it someday :) Fixes: https://github.com/raysan5/raylib/issues/5256 --- src/platforms/rcore_desktop_sdl.c | 37 ++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index a5f626ecc..e135cd848 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -837,7 +837,11 @@ void SetWindowPosition(int x, int y) void SetWindowMonitor(int monitor) { const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { // NOTE: // 1. SDL started supporting moving exclusive fullscreen windows between displays on SDL3, @@ -961,7 +965,11 @@ int GetCurrentMonitor(void) Vector2 GetMonitorPosition(int monitor) { const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { SDL_Rect displayBounds; @@ -985,7 +993,11 @@ int GetMonitorWidth(int monitor) int width = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(monitor, &mode); @@ -1002,7 +1014,11 @@ int GetMonitorHeight(int monitor) int height = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(monitor, &mode); @@ -1019,7 +1035,11 @@ int GetMonitorPhysicalWidth(int monitor) int width = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { float ddpi = 0.0f; SDL_GetDisplayDPI(monitor, &ddpi, NULL, NULL); @@ -1039,7 +1059,11 @@ int GetMonitorPhysicalHeight(int monitor) int height = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { float ddpi = 0.0f; SDL_GetDisplayDPI(monitor, &ddpi, NULL, NULL); @@ -1059,7 +1083,11 @@ int GetMonitorRefreshRate(int monitor) int refresh = 0; const int monitorCount = SDL_GetNumVideoDisplays(); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else if ((monitor >= 0) && (monitor < monitorCount)) +#endif { SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(monitor, &mode); @@ -1075,7 +1103,14 @@ const char *GetMonitorName(int monitor) { const int monitorCount = SDL_GetNumVideoDisplays(); - if ((monitor >= 0) && (monitor < monitorCount)) return SDL_GetDisplayName(monitor); +#if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure + if ((monitor > 0) && (monitor <= monitorCount)) +#else + if ((monitor >= 0) && (monitor < monitorCount)) +#endif + { + return SDL_GetDisplayName(monitor); + } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); return ""; From 428421f21a565b301e24e624fd2ffb4e1727258a Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 22 Oct 2025 11:03:19 +0200 Subject: [PATCH 16/25] Update examples_list.txt --- examples/examples_list.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 2a1b9c0b7..71cda6c1a 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -51,6 +51,7 @@ core;core_directory_files;★☆☆☆;5.5;5.6;2025;2025;"Hugo ARNAL";@hugoarnal core;core_highdpi_testbed;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_screen_recording;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_clipboard_text;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary +core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 shapes;shapes_basic_shapes;★☆☆☆;1.0;4.2;2014;2025;"Ramon Santamaria";@raysan5 shapes;shapes_bouncing_ball;★☆☆☆;2.5;2.5;2013;2025;"Ramon Santamaria";@raysan5 shapes;shapes_bullet_hell;★☆☆☆;5.6;5.6;2025;2025;"Zero";@zerohorsepower @@ -160,6 +161,7 @@ shaders;shaders_texture_rendering;★★☆☆;2.0;3.7;2019;2025;"Michał Ciesie shaders;shaders_texture_outline;★★★☆;4.0;4.0;2021;2025;"Serenity Skiff";@GoldenThumbs shaders;shaders_texture_waves;★★☆☆;2.5;3.7;2019;2025;"Anata";@anatagawa shaders;shaders_julia_set;★★★☆;2.5;4.0;2019;2025;"Josh Colclough";@joshcol9232 +shaders;shaders_mandelbrot_set;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant shaders;shaders_eratosthenes_sieve;★★★☆;2.5;4.0;2019;2025;"ProfJski";@ProfJski shaders;shaders_fog_rendering;★★★☆;2.5;3.7;2019;2025;"Chris Camacho";@chriscamacho shaders;shaders_simple_mask;★★☆☆;2.5;3.7;2019;2025;"Chris Camacho";@chriscamacho @@ -192,5 +194,3 @@ others;easings_testbed;★★★☆;2.5;3.0;2019;2025;"Juan Miguel López";@flas others;raylib_opengl_interop;★★★★;3.8;4.0;2021;2025;"Stephan Soller";@arkanis others;embedded_files_loading;★★☆☆;3.0;3.5;2020;2025;"Kristian Holmgren";@defutura others;web_basic_window;★☆☆☆;5.6-dev;5.6-dev;2014;2025;"Ramon Santamaria";@raysan5 -core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 -shaders;shaders_mandelbrot_set;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant From 780a7df760d45b8bd386be425ab5c2da0b1a48f6 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Thu, 23 Oct 2025 04:25:23 -0700 Subject: [PATCH 17/25] Fix warnings in more examples. (#5292) --- examples/core/core_text_file_loading.c | 16 ++++++++-------- examples/shapes/shapes_clock_of_clocks.c | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/core/core_text_file_loading.c b/examples/core/core_text_file_loading.c index 033fd4dc6..ed48f7d36 100644 --- a/examples/core/core_text_file_loading.c +++ b/examples/core/core_text_file_loading.c @@ -88,16 +88,16 @@ int main(void) for (int i = 0; i < lineCount; i++) { - Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], fontSize, 2); - textHeight += size.y + 10; + Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2); + textHeight += (int)size.y + 10; } // A simple scrollbar on the side to show how far we have red into the file Rectangle scrollBar = { - .x = screenWidth - 5, + .x = (float)screenWidth - 5, .y = 0, .width = 5, - .height = screenHeight*100/(textHeight - screenHeight) // Scrollbar height is just a percentage + .height = screenHeight*100.0f/(textHeight - screenHeight) // Scrollbar height is just a percentage }; SetTargetFPS(60); @@ -115,10 +115,10 @@ int main(void) // Ensuring that the camera does not scroll past all text if (cam.target.y > textHeight - screenHeight + textTop) - cam.target.y = textHeight - screenHeight + textTop; + cam.target.y = (float)textHeight - screenHeight + textTop; // Computing the position of the scrollBar depending on the percentage of text covered - scrollBar.y = Lerp(textTop, screenHeight - scrollBar.height, (cam.target.y - textTop)/(textHeight - screenHeight)); + scrollBar.y = Lerp((float)textTop, (float)screenHeight - scrollBar.height, (float)(cam.target.y - textTop)/(textHeight - screenHeight)); //---------------------------------------------------------------------------------- // Draw @@ -132,13 +132,13 @@ int main(void) for (int i = 0, t = textTop; i < lineCount; i++) { // Each time we go through and calculate the height of the text to move the cursor appropriately - Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], fontSize, 2); + Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2); DrawText(lines[i], 10, t, fontSize, RED); // Inserting extra space for real newlines, // wrapped lines are rendered closer together - t += size.y + 10; + t += (int)size.y + 10; } EndMode2D(); diff --git a/examples/shapes/shapes_clock_of_clocks.c b/examples/shapes/shapes_clock_of_clocks.c index 493e53b5e..3fc24aa92 100644 --- a/examples/shapes/shapes_clock_of_clocks.c +++ b/examples/shapes/shapes_clock_of_clocks.c @@ -14,6 +14,9 @@ * Copyright (c) 2025 JP Mortiboys (@themushroompirates) * ********************************************************************************************/ +#if defined(WIN32) +#define _CRT_SECURE_NO_WARNINGS +#endif #include "raylib.h" From 04930c5132f900be39b21a6b89685f0f75f2d0e5 Mon Sep 17 00:00:00 2001 From: Diogo Hartuiq Debarba <35925088+diogohartuiqdebarba@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:27:28 -0300 Subject: [PATCH 18/25] Fix typo in RPI cross-compiler variable name (#5295) --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 0d4ca9ac7..4369d8084 100644 --- a/src/Makefile +++ b/src/Makefile @@ -283,7 +283,7 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_DRM) - ifeq ($(USE_RPI_CROSS_COMPILER),TRUE) + ifeq ($(USE_RPI_CROSSCOMPILER),TRUE) # Define RPI cross-compiler #CC = armv6j-hardfloat-linux-gnueabi-gcc CC = $(RPI_TOOLCHAIN)/bin/$(RPI_TOOLCHAIN_NAME)-gcc From 93c5dc3b5f67b5c221cd93f379f9b31ba15b8c94 Mon Sep 17 00:00:00 2001 From: alexgb0 Date: Thu, 23 Oct 2025 17:28:00 +0200 Subject: [PATCH 19/25] [rlsw] c++ support (#5291) * rlsw c++ support * rslw formatting fixes --- src/external/rlsw.h | 141 ++++++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 64 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 904bf1210..aa4803265 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -156,6 +156,12 @@ #define SW_CLIP_EPSILON 1e-4f #endif +#ifdef __cplusplus + #define CURLY_INIT(name) name +#else + #define CURLY_INIT(name) (name) +#endif + //---------------------------------------------------------------------------------- // OpenGL Compatibility Types //---------------------------------------------------------------------------------- @@ -1699,7 +1705,7 @@ static inline void sw_framebuffer_blit_to_##name( int xSrc, int ySrc, int wSrc, int hSrc, \ DST_PTR_T *dst) \ { \ - const uint8_t *srcBase = RLSW.framebuffer.color; \ + const uint8_t *srcBase = (uint8_t*)RLSW.framebuffer.color; \ int fbWidth = RLSW.framebuffer.width; \ \ uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ @@ -1722,7 +1728,7 @@ static inline void sw_framebuffer_blit_to_##name( int xSrc, int ySrc, int wSrc, int hSrc, \ DST_PTR_T *dst) \ { \ - const uint8_t *srcBase = RLSW.framebuffer.color; \ + const uint8_t *srcBase = (uint8_t *)RLSW.framebuffer.color; \ int fbWidth = RLSW.framebuffer.width; \ \ uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ @@ -2583,6 +2589,15 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, /* Scanline rasterization */ \ for (int x = xStart; x < xEnd; x++) \ { \ + /* Pixel color computation */ \ + float wRcp = 1.0f/w; \ + float srcColor[4] = { \ + color[0]*wRcp, \ + color[1]*wRcp, \ + color[2]*wRcp, \ + color[3]*wRcp \ + }; \ + \ /* Test and write depth */ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -2593,15 +2608,6 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, \ sw_framebuffer_write_depth(dptr, z); \ \ - /* Pixel color computation */ \ - float wRcp = 1.0f/w; \ - float srcColor[4] = { \ - color[0]*wRcp, \ - color[1]*wRcp, \ - color[2]*wRcp, \ - color[3]*wRcp \ - }; \ - \ if (ENABLE_TEXTURE) \ { \ float texColor[4]; \ @@ -3063,6 +3069,14 @@ static inline void FUNC_NAME(void) /* Scanline rasterization */ \ for (int x = xMin; x < xMax; x++) \ { \ + /* Pixel color computation */ \ + float srcColor[4] = { \ + color[0], \ + color[1], \ + color[2], \ + color[3] \ + }; \ + \ /* Test and write depth */ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3073,14 +3087,6 @@ static inline void FUNC_NAME(void) \ sw_framebuffer_write_depth(dptr, z); \ \ - /* Pixel color computation */ \ - float srcColor[4] = { \ - color[0], \ - color[1], \ - color[2], \ - color[3] \ - }; \ - \ if (ENABLE_TEXTURE) \ { \ float texColor[4]; \ @@ -3361,6 +3367,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ { \ int offset = (y1 + i)*fbWidth + (x1 + (j >> 16)); \ void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ + void *cptr; \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3370,7 +3377,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - void *cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ + cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ @@ -3397,6 +3404,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ { \ int offset = (y1 + (j >> 16))*fbWidth + (x1 + i); \ void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ + void *cptr; \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3406,7 +3414,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - void *cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ + cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ @@ -3687,9 +3695,14 @@ static inline void sw_poly_line_render(void) const sw_vertex_t *vertices = RLSW.vertexBuffer; int cm1 = RLSW.vertexCounter - 1; - for (int i = 0; i < cm1; i++) sw_line_render((sw_vertex_t[2]){ vertices[i], vertices[i + 1] }); + for (int i = 0; i < cm1; i++) + { + sw_vertex_t verts[2] = { vertices[i], vertices[i + 1] }; + sw_line_render(verts); + } - sw_line_render((sw_vertex_t[2]){ vertices[cm1], vertices[0] }); + sw_vertex_t verts[2] = { vertices[cm1], vertices[0] }; + sw_line_render(verts); } static inline void sw_poly_fill_render(void) @@ -3817,10 +3830,10 @@ bool swInit(int w, int h) swViewport(0, 0, w, h); swScissor(0, 0, w, h); - RLSW.loadedTextures = SW_MALLOC(SW_MAX_TEXTURES*sizeof(sw_texture_t)); + RLSW.loadedTextures = (sw_texture_t *)SW_MALLOC(SW_MAX_TEXTURES*sizeof(sw_texture_t)); if (RLSW.loadedTextures == NULL) { swClose(); return false; } - RLSW.freeTextureIds = SW_MALLOC(SW_MAX_TEXTURES*sizeof(uint32_t)); + RLSW.freeTextureIds = (uint32_t *)SW_MALLOC(SW_MAX_TEXTURES*sizeof(uint32_t)); if (RLSW.loadedTextures == NULL) { swClose(); return false; } RLSW.clearColor[0] = 0.0f; @@ -3898,7 +3911,7 @@ void swClose(void) SW_FREE(RLSW.loadedTextures); SW_FREE(RLSW.freeTextureIds); - RLSW = (sw_context_t) { 0 }; + RLSW = CURLY_INIT(sw_context_t) { 0 }; } bool swResizeFramebuffer(int w, int h) @@ -3908,7 +3921,7 @@ bool swResizeFramebuffer(int w, int h) void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, void *pixels) { - sw_pixelformat_t pFormat = sw_get_pixel_format(format, type); + sw_pixelformat_t pFormat = (sw_pixelformat_t)sw_get_pixel_format(format, type); if (w <= 0) { @@ -3930,26 +3943,26 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, switch (pFormat) { - case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_copy_to_R5G6B5(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_copy_to_R8G8B8(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_copy_to_R5G5B5A1(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_copy_to_R4G4B4A4(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_copy_to_R32(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_copy_to_R32G32B32(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_copy_to_R32G32B32A32(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_copy_to_R16(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_copy_to_R16G16B16(x, y, w, h, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_copy_to_R16G16B16A16(x, y, w, h, pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_copy_to_R5G6B5(x, y, w, h, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_copy_to_R8G8B8(x, y, w, h, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_copy_to_R5G5B5A1(x, y, w, h, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_copy_to_R4G4B4A4(x, y, w, h, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_copy_to_R32(x, y, w, h, (float *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_copy_to_R32G32B32(x, y, w, h, (float *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_copy_to_R32G32B32A32(x, y, w, h, (float *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_copy_to_R16(x, y, w, h, (sw_half_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_copy_to_R16G16B16(x, y, w, h, (sw_half_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_copy_to_R16G16B16A16(x, y, w, h, (sw_half_t *)pixels); break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, SWformat format, SWtype type, void *pixels) { - sw_pixelformat_t pFormat = sw_get_pixel_format(format, type); + sw_pixelformat_t pFormat = (sw_pixelformat_t)sw_get_pixel_format(format, type); if (wSrc <= 0) { @@ -3971,19 +3984,19 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr switch (pFormat) { - case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_blit_to_R5G6B5(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_blit_to_R8G8B8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_blit_to_R5G5B5A1(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_blit_to_R4G4B4A4(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_blit_to_R8G8B8A8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_blit_to_R32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_blit_to_R32G32B32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_blit_to_R32G32B32A32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_blit_to_R16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_blit_to_R16G16B16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_blit_to_R16G16B16A16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_blit_to_R5G6B5(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_blit_to_R8G8B8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_blit_to_R5G5B5A1(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_blit_to_R4G4B4A4(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_blit_to_R8G8B8A8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_blit_to_R32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_blit_to_R32G32B32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (float *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_blit_to_R32G32B32A32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (float*)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_blit_to_R16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_blit_to_R16G16B16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_blit_to_R16G16B16A16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } @@ -4553,7 +4566,7 @@ void swBegin(SWdraw mode) void swEnd(void) { - RLSW.drawMode = 0; + RLSW.drawMode = (SWdraw)0; } void swVertex2i(int x, int y) @@ -4738,9 +4751,9 @@ void swBindArray(SWarray type, void *buffer) { switch (type) { - case SW_VERTEX_ARRAY: RLSW.array.positions = buffer; break; - case SW_TEXTURE_COORD_ARRAY: RLSW.array.texcoords = buffer; break; - case SW_COLOR_ARRAY: RLSW.array.colors = buffer; break; + case SW_VERTEX_ARRAY: RLSW.array.positions = (float *)buffer; break; + case SW_TEXTURE_COORD_ARRAY: RLSW.array.texcoords = (float *)buffer; break; + case SW_COLOR_ARRAY: RLSW.array.colors = (uint8_t *)buffer; break; default: break; } } @@ -4833,7 +4846,7 @@ void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy if (copy) { - int bytes = sw_get_pixel_bytes(pixelFormat); + int bytes = sw_get_pixel_bytes((sw_pixelformat_t)pixelFormat); int size = bytes*width*height; texture->pixels.ptr = SW_MALLOC(size); @@ -4854,7 +4867,7 @@ void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy texture->height = height; texture->wMinus1 = width - 1; texture->hMinus1 = height - 1; - texture->format = pixelFormat; + texture->format = (sw_pixelformat_t)pixelFormat; texture->tx = 1.0f/width; texture->ty = 1.0f/height; texture->copy = copy; @@ -4882,7 +4895,7 @@ void swTexParameteri(int param, int value) return; } - texture->minFilter = value; + texture->minFilter = (SWfilter)value; } break; case SW_TEXTURE_MAG_FILTER: { @@ -4892,7 +4905,7 @@ void swTexParameteri(int param, int value) return; } - texture->magFilter = value; + texture->magFilter = (SWfilter)value; } break; case SW_TEXTURE_WRAP_S: { @@ -4902,7 +4915,7 @@ void swTexParameteri(int param, int value) return; } - texture->sWrap = value; + texture->sWrap = (SWwrap)value; } break; case SW_TEXTURE_WRAP_T: { @@ -4912,7 +4925,7 @@ void swTexParameteri(int param, int value) return; } - texture->tWrap = value; + texture->tWrap = (SWwrap)value; } break; default: RLSW.errCode = SW_INVALID_ENUM; break; } From 12dc0d66758970212eaa323c2e398d8645a229e4 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Thu, 23 Oct 2025 08:45:33 -0700 Subject: [PATCH 20/25] Revert undesired change to rcore and restore correct win32 ifdef and max path (#5297) Fixes #5293 also add comment for win32 platform. --- src/rcore.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 350490ad1..e4628e77c 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -26,7 +26,8 @@ * - Linux DRM subsystem (KMS mode) * > PLATFORM_ANDROID: * - Android (ARM, ARM64) -* +* > PLATFORM_DESKTOP_WIN32 (Native Win32): +* - Windows (Win32, Win64) * CONFIGURATION: * #define SUPPORT_DEFAULT_FONT (default) * Default font is loaded on window initialization to be available for the user to render simple text. @@ -161,7 +162,10 @@ #endif // Platform specific defines to handle GetApplicationDirectory() -#if (defined(_WIN32) && !defined(PLATFORM_DESKTOP_RGFW)) || (defined(_MSC_VER) && defined(PLATFORM_DESKTOP_RGFW)) +#if defined(_WIN32) + #if !defined(MAX_PATH) + #define MAX_PATH 260 + #endif struct HINSTANCE__; #if defined(__cplusplus) From 39242dba2303cd321e594908c814e08f976d31b4 Mon Sep 17 00:00:00 2001 From: Le Juez Victor <90587919+Bigfoot71@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:29:01 +0200 Subject: [PATCH 21/25] [rlsw] Smarter texture management (#5296) * simplified framebuffer management * convert texture to rgba32 * remove float copy/blit * adding SIMD framebuffer read/write This adds SIMD framebuffer read/write, and also texel fetch. This supports SSE2/SSE41 and SISD fallback (also includes ARM NEON support, conceptually identical but still needs testing). * consistency * tweaks * review of `sw_texture_sample_linear` * better quad sorting unrelated to the PR, but at least it's done * ignore some pipeline state in certains context * convention tweaks --------- Co-authored-by: Ray --- src/external/rlsw.h | 1723 ++++++++++++++++++------------------------- 1 file changed, 724 insertions(+), 999 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index aa4803265..626cb2cc5 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -115,16 +115,12 @@ #define SW_GL_FRAMEBUFFER_COPY_BGRA true #endif -#ifndef SW_GL_BINDING_COPY_TEXTURE - #define SW_GL_BINDING_COPY_TEXTURE true -#endif - #ifndef SW_COLOR_BUFFER_BITS - #define SW_COLOR_BUFFER_BITS 24 + #define SW_COLOR_BUFFER_BITS 32 //< 32 (rgba), 16 (rgb packed) or 8 (rgb packed) #endif #ifndef SW_DEPTH_BUFFER_BITS - #define SW_DEPTH_BUFFER_BITS 16 + #define SW_DEPTH_BUFFER_BITS 16 //< 24, 16 or 8 #endif #ifndef SW_MAX_PROJECTION_STACK_SIZE @@ -382,7 +378,7 @@ typedef double GLclampd; #define glDrawArrays(m, o, c) swDrawArrays((m), (o), (c)) #define glGenTextures(c, v) swGenTextures((c), (v)) #define glDeleteTextures(c, v) swDeleteTextures((c), (v)) -#define glTexImage2D(tr, l, if, w, h, b, f, t, p) swTexImage2D((w), (h), (f), (t), SW_GL_BINDING_COPY_TEXTURE, (p)) +#define glTexImage2D(tr, l, if, w, h, b, f, t, p) swTexImage2D((w), (h), (f), (t), (p)) #define glTexParameteri(tr, pname, param) swTexParameteri((pname), (param)) #define glBindTexture(tr, id) swBindTexture((id)) @@ -597,7 +593,7 @@ SWAPI void swDrawArrays(SWdraw mode, int offset, int count); SWAPI void swGenTextures(int count, uint32_t *textures); SWAPI void swDeleteTextures(int count, uint32_t *textures); -SWAPI void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy, const void *data); +SWAPI void swTexImage2D(int width, int height, SWformat format, SWtype type, const void *data); SWAPI void swTexParameteri(int param, int value); SWAPI void swBindTexture(uint32_t id); @@ -615,6 +611,65 @@ SWAPI void swBindTexture(uint32_t id); #include #include // Required for: floorf(), fabsf() +#if defined(__FMA__) && defined(__AVX2__) +# define SW_HAS_FMA_AVX2 +# include +#endif + +#if defined(__FMA__) && defined(__AVX__) +# define SW_HAS_FMA_AVX +# include +#endif + +#if defined(__AVX2__) +# define SW_HAS_AVX2 +# include +#endif + +#if defined(__AVX__) +# define SW_HAS_AVX +# include +#endif + +#if defined(__SSE4_2__) +# define SW_HAS_SSE42 +# include +#endif + +#if defined(__SSE4_1__) +# define SW_HAS_SSE41 +# include +#endif + +#if defined(__SSSE3__) +# define SW_HAS_SSSE3 +# include +#endif + +#if defined(__SSE3__) +# define SW_HAS_SSE3 +# include +#endif + +#if defined(__SSE2__) +# define SW_HAS_SSE2 +# include +#endif + +#if defined(__SSE__) +# define SW_HAS_SSE +# include +#endif + +#if defined(__ARM_NEON) || defined(__aarch64__) +# if defined(__ARM_FEATURE_FMA) +# define SW_HAS_NEON_FMA +# else +# define SW_HAS_NEON +# endif +# include +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -625,7 +680,69 @@ SWAPI void swBindTexture(uint32_t id); #define SW_COLOR_PIXEL_SIZE (SW_COLOR_BUFFER_BITS/8) #define SW_DEPTH_PIXEL_SIZE (SW_DEPTH_BUFFER_BITS/8) -#define SW_STATE_CHECK(flags) ((RLSW.stateFlags & (flags)) == (flags)) +#if (SW_COLOR_BUFFER_BITS == 8) + #define COLOR_TYPE uint8_t + #define COLOR_IS_PACKED 1 + #define PACK_COLOR(r,g,b) ((((uint8_t)((r)*7+0.5f))&0x07)<<5 | (((uint8_t)((g)*7+0.5f))&0x07)<<2 | ((uint8_t)((b)*3+0.5f))&0x03) + #define UNPACK_R(p) (((p)>>5)&0x07) + #define UNPACK_G(p) (((p)>>2)&0x07) + #define UNPACK_B(p) ((p)&0x03) + #define SCALE_R(v) ((v)*255+3)/7 + #define SCALE_G(v) ((v)*255+3)/7 + #define SCALE_B(v) ((v)*255+1)/3 + #define TO_FLOAT_R(v) ((v)*(1.0f/7.0f)) + #define TO_FLOAT_G(v) ((v)*(1.0f/7.0f)) + #define TO_FLOAT_B(v) ((v)*(1.0f/3.0f)) +#elif (SW_COLOR_BUFFER_BITS == 16) + #define COLOR_TYPE uint16_t + #define COLOR_IS_PACKED 1 + #define PACK_COLOR(r,g,b) ((((uint16_t)((r)*31+0.5f))&0x1F)<<11 | (((uint16_t)((g)*63+0.5f))&0x3F)<<5 | ((uint16_t)((b)*31+0.5f))&0x1F) + #define UNPACK_R(p) (((p)>>11)&0x1F) + #define UNPACK_G(p) (((p)>>5)&0x3F) + #define UNPACK_B(p) ((p)&0x1F) + #define SCALE_R(v) ((v)*255+15)/31 + #define SCALE_G(v) ((v)*255+31)/63 + #define SCALE_B(v) ((v)*255+15)/31 + #define TO_FLOAT_R(v) ((v)*(1.0f/31.0f)) + #define TO_FLOAT_G(v) ((v)*(1.0f/63.0f)) + #define TO_FLOAT_B(v) ((v)*(1.0f/31.0f)) +#else // 32 bits + #define COLOR_TYPE uint8_t + #define COLOR_IS_PACKED 0 +#endif + +#if (SW_DEPTH_BUFFER_BITS == 8) + #define DEPTH_TYPE uint8_t + #define DEPTH_IS_PACKED 1 + #define DEPTH_MAX UINT8_MAX + #define DEPTH_SCALE (1.0f/UINT8_MAX) + #define PACK_DEPTH(d) ((DEPTH_TYPE)((d)*DEPTH_MAX)) + #define UNPACK_DEPTH(p) (p) +#elif (SW_DEPTH_BUFFER_BITS == 16) + #define DEPTH_TYPE uint16_t + #define DEPTH_IS_PACKED 1 + #define DEPTH_MAX UINT16_MAX + #define DEPTH_SCALE (1.0f/UINT16_MAX) + #define PACK_DEPTH(d) ((DEPTH_TYPE)((d)*DEPTH_MAX)) + #define UNPACK_DEPTH(p) (p) +#else // 24 bits + #define DEPTH_TYPE uint8_t + #define DEPTH_IS_PACKED 0 + #define DEPTH_MAX 0xFFFFFF + #define DEPTH_SCALE (1.0f/0xFFFFFF) + #define PACK_DEPTH_0(d) (((uint32_t)((d)*DEPTH_MAX)>>16)&0xFF) + #define PACK_DEPTH_1(d) (((uint32_t)((d)*DEPTH_MAX)>>8)&0xFF) + #define PACK_DEPTH_2(d) ((uint32_t)((d)*DEPTH_MAX)&0xFF) + #define UNPACK_DEPTH(p) (((p)[0]<<16)|((p)[1]<<8)|(p)[2]) +#endif + +#define GET_COLOR_PTR(ptr, offset) ((void*)((uint8_t*)(ptr) + (offset)*SW_COLOR_PIXEL_SIZE)) +#define GET_DEPTH_PTR(ptr, offset) ((void*)((uint8_t*)(ptr) + (offset)*SW_DEPTH_PIXEL_SIZE)) +#define INC_COLOR_PTR(ptr) ((ptr) = (void*)((uint8_t*)(ptr) + SW_COLOR_PIXEL_SIZE)) +#define INC_DEPTH_PTR(ptr) ((ptr) = (void*)((uint8_t*)(ptr) + SW_DEPTH_PIXEL_SIZE)) + +#define SW_STATE_CHECK(flags) (SW_STATE_CHECK_EX(RLSW.stateFlags, (flags))) +#define SW_STATE_CHECK_EX(state, flags) (((state) & (flags)) == (flags)) #define SW_STATE_SCISSOR_TEST (1 << 0) #define SW_STATE_TEXTURE_2D (1 << 1) @@ -674,16 +791,11 @@ typedef struct { } sw_vertex_t; typedef struct { - // Dirty hack for copied data - // TODO: Rework copied image handling - union { - const void *cptr; // NOTE: Is used for all data reads - void *ptr; // WARNING: Should only be used to allocate and free data - } pixels; + + uint8_t* pixels; // Texture pixels (RGBA32) int width, height; // Dimensions of the texture int wMinus1, hMinus1; // Dimensions minus one - sw_pixelformat_t format; // Pixel format (internal representation) SWfilter minFilter; // Minification filter SWfilter magFilter; // Magnification filter @@ -694,8 +806,6 @@ typedef struct { float tx; // Texel width float ty; // Texel height - bool copy; // Flag indicating whether memory has been allocated - } sw_texture_t; typedef struct { @@ -943,8 +1053,89 @@ static inline void sw_add_vertex_grad_PTCH( out->homogeneous[3] += gradients->homogeneous[3]; } -// Half floating point management functions -static inline uint32_t sw_f16_to_f32_ui(uint16_t h) +static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) +{ +#if defined(SW_HAS_NEON) + float32x4_t values = vld1q_f32(src); + float32x4_t scaled = vmulq_n_f32(values, 255.0f); + scaled = vminq_f32(vmaxq_f32(scaled, vdupq_n_f32(0.0f)), vdupq_n_f32(255.0f)); + uint32x4_t clamped = vcvtq_u32_f32(scaled); + + uint16x4_t narrow16 = vmovn_u32(clamped); + uint8x8_t narrow8 = vmovn_u16(vcombine_u16(narrow16, narrow16)); + + vst1_lane_u32((uint32_t*)dst, vreinterpret_u32_u8(narrow8), 0); + +#elif defined(SW_HAS_SSE41) + __m128 values = _mm_loadu_ps(src); + __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); + scaled = _mm_max_ps(_mm_min_ps(scaled, _mm_set1_ps(255.0f)), _mm_setzero_ps()); + __m128i clamped = _mm_cvtps_epi32(scaled); + + clamped = _mm_packus_epi32(clamped, clamped); + clamped = _mm_packus_epi16(clamped, clamped); + *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); + +#elif defined(SW_HAS_SSE2) + __m128 values = _mm_loadu_ps(src); + __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); + scaled = _mm_max_ps(_mm_min_ps(scaled, _mm_set1_ps(255.0f)), _mm_setzero_ps()); + __m128i clamped = _mm_cvtps_epi32(scaled); + + clamped = _mm_packs_epi32(clamped, clamped); + clamped = _mm_packus_epi16(clamped, clamped); + *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); + +#else + for (int i = 0; i < 4; i++) + { + float val = src[i]*255.0f; + val = (val > 255.0f)? 255.0f : val; + val = (val < 0.0f)? 0.0f : val; + dst[i] = (uint8_t)(val + 0.5f); + } +#endif +} + +static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) +{ +#if defined(SW_HAS_NEON) + uint32x4_t bytes = vdupq_n_u32(0); + bytes = vld1q_lane_u32((const uint32_t*)src, bytes, 0); + + uint8x8_t bytes8 = vreinterpret_u8_u32(vget_low_u32(bytes)); + uint16x8_t bytes16 = vmovl_u8(bytes8); + uint32x4_t ints = vmovl_u16(vget_low_u16(bytes16)); + + float32x4_t floats = vcvtq_f32_u32(ints); + floats = vmulq_n_f32(floats, 1.0f/255.0f); + vst1q_f32(dst, floats); + +#elif defined(SW_HAS_SSE41) + __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); + __m128i ints = _mm_cvtepu8_epi32(bytes); + __m128 floats = _mm_cvtepi32_ps(ints); + floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); + _mm_storeu_ps(dst, floats); + +#elif defined(SW_HAS_SSE2) + __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); + bytes = _mm_unpacklo_epi8(bytes, _mm_setzero_si128()); + __m128i ints = _mm_unpacklo_epi16(bytes, _mm_setzero_si128()); + __m128 floats = _mm_cvtepi32_ps(ints); + floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); + _mm_storeu_ps(dst, floats); + +#else + dst[0] = (float)src[0]/255.0f; + dst[1] = (float)src[1]/255.0f; + dst[2] = (float)src[2]/255.0f; + dst[3] = (float)src[3]/255.0f; +#endif +} + +// Half conversion functions +static inline uint32_t sw_half_to_float_ui(uint16_t h) { uint32_t s = (uint32_t)(h & 0x8000) << 16; int32_t em = h & 0x7fff; @@ -962,14 +1153,14 @@ static inline uint32_t sw_f16_to_f32_ui(uint16_t h) return s | r; } -static inline float sw_f16_to_f32(sw_half_t y) +static inline float sw_half_to_float(sw_half_t y) { - union { float f; uint32_t i; } v = { .i = sw_f16_to_f32_ui(y) }; + union { float f; uint32_t i; } v = { .i = sw_half_to_float_ui(y) }; return v.f; } -static inline uint16_t sw_f16_from_f32_ui(uint32_t ui) +static inline uint16_t sw_half_from_float_ui(uint32_t ui) { int32_t s = (ui >> 16) & 0x8000; int32_t em = ui & 0x7fffffff; @@ -989,14 +1180,15 @@ static inline uint16_t sw_f16_from_f32_ui(uint32_t ui) return (uint16_t)(s | h); } -static inline sw_half_t sw_f16_from_f32(float i) +static inline sw_half_t sw_half_from_float(float i) { union { float f; uint32_t i; } v; v.f = i; - return sw_f16_from_f32_ui(v.i); + return sw_half_from_float_ui(v.i); } // Framebuffer management functions + static inline bool sw_framebuffer_load(int w, int h) { int size = w*h; @@ -1005,7 +1197,11 @@ static inline bool sw_framebuffer_load(int w, int h) if (RLSW.framebuffer.color == NULL) return false; RLSW.framebuffer.depth = SW_MALLOC(SW_DEPTH_PIXEL_SIZE*size); - if (RLSW.framebuffer.depth == NULL) return false; + if (RLSW.framebuffer.depth == NULL) + { + SW_FREE(RLSW.framebuffer.color); + return false; + } RLSW.framebuffer.width = w; RLSW.framebuffer.height = h; @@ -1025,11 +1221,15 @@ static inline bool sw_framebuffer_resize(int w, int h) return true; } - void *newColor = SW_REALLOC(RLSW.framebuffer.color, newSize); + void *newColor = SW_REALLOC(RLSW.framebuffer.color, SW_COLOR_PIXEL_SIZE*newSize); if (newColor == NULL) return false; - void *newDepth = SW_REALLOC(RLSW.framebuffer.depth, newSize); - if (newDepth == NULL) return false; + void *newDepth = SW_REALLOC(RLSW.framebuffer.depth, SW_DEPTH_PIXEL_SIZE*newSize); + if (newDepth == NULL) + { + SW_FREE(newColor); + return false; + } RLSW.framebuffer.color = newColor; RLSW.framebuffer.depth = newDepth; @@ -1041,314 +1241,221 @@ static inline bool sw_framebuffer_resize(int w, int h) return true; } -static inline void *sw_framebuffer_get_color_addr(const void *ptr, uint32_t offset) -{ - return (uint8_t *)ptr + offset*SW_COLOR_PIXEL_SIZE; -} - -static inline void sw_framebuffer_inc_color_addr(void **ptr) -{ - *ptr = (void *)(((uint8_t *)*ptr) + SW_COLOR_PIXEL_SIZE); -} - -static inline void sw_framebuffer_inc_const_color_addr(const void **ptr) -{ - *ptr = (const void *)(((const uint8_t *)*ptr) + SW_COLOR_PIXEL_SIZE); -} - -static inline void *sw_framebuffer_get_depth_addr(const void *ptr, uint32_t offset) -{ - return (uint8_t *)ptr + offset*SW_DEPTH_PIXEL_SIZE; -} - -static inline void sw_framebuffer_inc_depth_addr(void* *ptr) -{ - *ptr = (void*)(((uint8_t *)*ptr) + SW_DEPTH_PIXEL_SIZE); -} - -#if (SW_COLOR_BUFFER_BITS == 8) // RGB - (3:3:2) static inline void sw_framebuffer_read_color(float dst[4], const void *src) { - uint8_t pixel = ((uint8_t *)src)[0]; - - dst[0] = ((pixel >> 5) & 0x07)*(1.0f/7.0f); - dst[1] = ((pixel >> 2) & 0x07)*(1.0f/7.0f); - dst[2] = (pixel & 0x03)*(1.0f/3.0f); +#if COLOR_IS_PACKED + COLOR_TYPE pixel = ((COLOR_TYPE*)src)[0]; + dst[0] = TO_FLOAT_R(UNPACK_R(pixel)); + dst[1] = TO_FLOAT_G(UNPACK_G(pixel)); + dst[2] = TO_FLOAT_B(UNPACK_B(pixel)); dst[3] = 1.0f; +#else + sw_float_from_unorm8_simd(dst, src); +#endif } static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) { - uint8_t pixel = ((const uint8_t *)src)[0]; - - uint8_t r = (pixel >> 5) & 0x07; - uint8_t g = (pixel >> 2) & 0x07; - uint8_t b = pixel & 0x03; - - dst[0] = (r*255 + 3)/7; - dst[1] = (g*255 + 3)/7; - dst[2] = (b*255 + 1)/3; +#if COLOR_IS_PACKED + COLOR_TYPE pixel = ((COLOR_TYPE*)src)[0]; + dst[0] = SCALE_R(UNPACK_R(pixel)); + dst[1] = SCALE_G(UNPACK_G(pixel)); + dst[2] = SCALE_B(UNPACK_B(pixel)); dst[3] = 255; +#else + const COLOR_TYPE *p = (const COLOR_TYPE*)src; + dst[0] = p[0]; + dst[1] = p[1]; + dst[2] = p[2]; + dst[3] = p[3]; +#endif } -static inline void sw_framebuffer_write_color(void *dst, const float color[3]) +static inline void sw_framebuffer_write_color(void *dst, const float src[4]) { - uint8_t r = ((uint8_t)(color[0]*UINT8_MAX) >> 5) & 0x07; - uint8_t g = ((uint8_t)(color[1]*UINT8_MAX) >> 5) & 0x07; - uint8_t b = ((uint8_t)(color[2]*UINT8_MAX) >> 6) & 0x03; - - ((uint8_t *)dst)[0] = (r << 5) | (g << 2) | b; +#if COLOR_IS_PACKED + ((COLOR_TYPE*)dst)[0] = PACK_COLOR(src[0], src[1], src[2]); +#else + sw_float_to_unorm8_simd(dst, src); +#endif } -static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) +static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[4]) { - uint8_t r8 = (uint8_t)(color[0]*7.0f + 0.5f); - uint8_t g8 = (uint8_t)(color[1]*7.0f + 0.5f); - uint8_t b8 = (uint8_t)(color[2]*3.0f + 0.5f); - - uint8_t packedColor = ((r8 & 0x07) << 5) | ((g8 & 0x07) << 2) | (b8 & 0x03); - - uint8_t *p = (uint8_t *)ptr; - +#if COLOR_IS_PACKED + COLOR_TYPE packed = PACK_COLOR(color[0], color[1], color[2]); + COLOR_TYPE *p = (COLOR_TYPE*)ptr; +#else + COLOR_TYPE r = sw_clampi(color[0]*255, 0, 255); + COLOR_TYPE g = sw_clampi(color[1]*255, 0, 255); + COLOR_TYPE b = sw_clampi(color[2]*255, 0, 255); + COLOR_TYPE a = sw_clampi(color[3]*255, 0, 255); + COLOR_TYPE *p = (COLOR_TYPE*)ptr; +#endif + if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; + int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { - uint8_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = packedColor; - } - } - else - { - for (int i = 0; i < size; i++) *p++ = packedColor; - } -} - -#elif (SW_COLOR_BUFFER_BITS == 16) // RGB - (5:6:5) - -static inline void sw_framebuffer_read_color(float dst[4], const void *src) -{ - uint16_t pixel = ((uint16_t *)src)[0]; - - dst[0] = ((pixel >> 11) & 0x1F)*(1.0f/31.0f); - dst[1] = ((pixel >> 5) & 0x3F)*(1.0f/63.0f); - dst[2] = (pixel & 0x1F)*(1.0f/31.0f); - dst[3] = 1.0f; -} - -static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) -{ - uint16_t pixel = ((const uint16_t *)src)[0]; - - uint8_t r = (pixel >> 11) & 0x1F; - uint8_t g = (pixel >> 5) & 0x3F; - uint8_t b = pixel & 0x1F; - - dst[0] = (r*255 + 15)/31; - dst[1] = (g*255 + 31)/63; - dst[2] = (b*255 + 15)/31; - dst[3] = 255; -} - -static inline void sw_framebuffer_write_color(void *dst, const float color[3]) -{ - uint8_t r = (uint8_t)(color[0]*31.0f + 0.5f) & 0x1F; - uint8_t g = (uint8_t)(color[1]*63.0f + 0.5f) & 0x3F; - uint8_t b = (uint8_t)(color[2]*31.0f + 0.5f) & 0x1F; - - ((uint16_t *)dst)[0] = (r << 11) | (g << 5) | b; -} - -static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) -{ - uint16_t r16 = (uint16_t)(color[0]*31.0f + 0.5f); - uint16_t g_16 = (uint16_t)(color[1]*63.0f + 0.5f); - uint16_t b_16 = (uint16_t)(color[2]*31.0f + 0.5f); - - uint16_t packedColor = ((r16 & 0x1F) << 11) | ((g_16 & 0x3F) << 5) | (b_16 & 0x1F); - - uint16_t *p = (uint16_t *)ptr; - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - uint16_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = packedColor; - } - } - else - { - for (int i = 0; i < size; i++) *p++ = packedColor; - } -} - -#elif (SW_COLOR_BUFFER_BITS == 24) // RGB - (8:8:8) - -static inline void sw_framebuffer_read_color(float dst[4], const void *src) -{ - dst[0] = ((uint8_t *)src)[0]*(1.0f/255.0f); - dst[1] = ((uint8_t *)src)[1]*(1.0f/255.0f); - dst[2] = ((uint8_t *)src)[2]*(1.0f/255.0f); - dst[3] = 1.0f; -} - -static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) -{ - dst[0] = ((uint8_t *)src)[0]; - dst[1] = ((uint8_t *)src)[1]; - dst[2] = ((uint8_t *)src)[2]; - dst[3] = 255; -} - -static inline void sw_framebuffer_write_color(void *dst, const float color[3]) -{ - ((uint8_t *)dst)[0] = (uint8_t)(color[0]*UINT8_MAX); - ((uint8_t *)dst)[1] = (uint8_t)(color[1]*UINT8_MAX); - ((uint8_t *)dst)[2] = (uint8_t)(color[2]*UINT8_MAX); -} - -static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) -{ - uint8_t r = (uint8_t)(color[0]*255.0f); - uint8_t g = (uint8_t)(color[1]*255.0f); - uint8_t b = (uint8_t)(color[2]*255.0f); - - uint8_t *p = (uint8_t *)ptr; - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - uint8_t *curPtr = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); - for (int xCount = 0; xCount < wScissor; xCount++) +#if COLOR_IS_PACKED + COLOR_TYPE *row = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; + for (int x = 0; x < w; x++) *row++ = packed; +#else + COLOR_TYPE *row = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); + for (int x = 0; x < w; x++) { - *curPtr++ = r; - *curPtr++ = g; - *curPtr++ = b; + *row++ = r; + *row++ = g; + *row++ = b; + *row++ = a; } +#endif } } else { +#if COLOR_IS_PACKED + for (int i = 0; i < size; i++) *p++ = packed; +#else for (int i = 0; i < size; i++) { *p++ = r; *p++ = g; *p++ = b; + *p++ = a; } +#endif } } -#endif // SW_COLOR_BUFFER_BITS - -#if (SW_DEPTH_BUFFER_BITS == 8) - static inline float sw_framebuffer_read_depth(const void *src) { - return (float)((uint8_t *)src)[0]*(1.0f/UINT8_MAX); +#if DEPTH_IS_PACKED + return ((DEPTH_TYPE*)src)[0]*DEPTH_SCALE; +#else + const DEPTH_TYPE *p = (const DEPTH_TYPE*)src; + uint32_t d = UNPACK_DEPTH(p); + return d*DEPTH_SCALE; +#endif } static inline void sw_framebuffer_write_depth(void *dst, float depth) { - ((uint8_t *)dst)[0] = (uint8_t)(depth*UINT8_MAX); +#if DEPTH_IS_PACKED + ((DEPTH_TYPE*)dst)[0] = PACK_DEPTH(depth); +#else + DEPTH_TYPE *p = (DEPTH_TYPE*)dst; + p[0] = PACK_DEPTH_0(depth); + p[1] = PACK_DEPTH_1(depth); + p[2] = PACK_DEPTH_2(depth); +#endif } static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) { - uint8_t d8 = (uint8_t)(value*UINT8_MAX); - uint8_t *p = (uint8_t *)ptr; - +#if DEPTH_IS_PACKED + DEPTH_TYPE d = PACK_DEPTH(value); + DEPTH_TYPE *p = (DEPTH_TYPE*)ptr; +#else + DEPTH_TYPE d0 = PACK_DEPTH_0(value); + DEPTH_TYPE d1 = PACK_DEPTH_1(value); + DEPTH_TYPE d2 = PACK_DEPTH_2(value); + DEPTH_TYPE *p = (DEPTH_TYPE*)ptr; +#endif + if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; + int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { - uint8_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = d8; - } - } - else - { - for (int i = 0; i < size; i++) *p++ = d8; - } -} - -#elif (SW_DEPTH_BUFFER_BITS == 16) - -static inline float sw_framebuffer_read_depth(const void *src) -{ - return (float)((uint16_t *)src)[0]*(1.0f/UINT16_MAX); -} - -static inline void sw_framebuffer_write_depth(void *dst, float depth) -{ - ((uint16_t *)dst)[0] = (uint16_t)(depth*UINT16_MAX); -} - -static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) -{ - uint16_t d16 = (uint16_t)(value*UINT16_MAX); - uint16_t *p = (uint16_t *)ptr; - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - uint16_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = d16; - } - } - else - { - for (int i = 0; i < size; i++) *p++ = d16; - } -} - -#elif (SW_DEPTH_BUFFER_BITS == 24) - -static inline float sw_framebuffer_read_depth(const void *src) -{ - uint32_t depth24 = (((uint8_t *)src)[0] << 16) | - (((uint8_t *)src)[1] << 8) | - ((uint8_t *)src)[2]; - - return depth24/(float)0xFFFFFF; -} - -static inline void sw_framebuffer_write_depth(void *dst, float depth) -{ - uint32_t depth24 = (uint32_t)(depth*0xFFFFFF); - - ((uint8_t *)dst)[0] = (depth24 >> 16) & 0xFF; - ((uint8_t *)dst)[1] = (depth24 >> 8) & 0xFF; - ((uint8_t *)dst)[2] = depth24 & 0xFF; -} - -static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) -{ - uint32_t d32 = (uint32_t)(value*UINT32_MAX); - uint8_t d_byte0 = (uint8_t)((d32 >> 16) & 0xFF); - uint8_t d_byte1 = (uint8_t)((d32 >> 8) & 0xFF); - uint8_t d_byte2 = (uint8_t)(d32 & 0xFF); - - uint8_t *p = (uint8_t *)ptr; - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - uint8_t *curPtr = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); - for (int xCount = 0; xCount < wScissor; xCount++) +#if DEPTH_IS_PACKED + DEPTH_TYPE *row = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; + for (int x = 0; x < w; x++) *row++ = d; +#else + DEPTH_TYPE *row = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); + for (int x = 0; x < w; x++) { - *curPtr++ = d_byte0; - *curPtr++ = d_byte1; - *curPtr++ = d_byte2; + *row++ = d0; + *row++ = d1; + *row++ = d2; + } +#endif + } + } + else + { +#if DEPTH_IS_PACKED + for (int i = 0; i < size; i++) *p++ = d; +#else + for (int i = 0; i < size; i++) + { + *p++ = d0; + *p++ = d1; + *p++ = d2; + } +#endif + } +} + +static inline void sw_framebuffer_fill(void *colorPtr, void *depthPtr, int size, float color[4], float depth) +{ +#if COLOR_IS_PACKED + COLOR_TYPE packedColor = PACK_COLOR(color[0], color[1], color[2]); + COLOR_TYPE *pColor = (COLOR_TYPE*)colorPtr; +#else + COLOR_TYPE r = sw_clampi(color[0]*255, 0, 255); + COLOR_TYPE g = sw_clampi(color[1]*255, 0, 255); + COLOR_TYPE b = sw_clampi(color[2]*255, 0, 255); + COLOR_TYPE a = sw_clampi(color[3]*255, 0, 255); + COLOR_TYPE *pColor = (COLOR_TYPE*)colorPtr; +#endif + +#if DEPTH_IS_PACKED + DEPTH_TYPE d = PACK_DEPTH(depth); + DEPTH_TYPE *pDepth = (DEPTH_TYPE*)depthPtr; +#else + DEPTH_TYPE d0 = PACK_DEPTH_0(depth); + DEPTH_TYPE d1 = PACK_DEPTH_1(depth); + DEPTH_TYPE d2 = PACK_DEPTH_2(depth); + DEPTH_TYPE *pDepth = (DEPTH_TYPE*)depthPtr; +#endif + + if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) + { + int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; + for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) + { +#if COLOR_IS_PACKED + COLOR_TYPE *rowColor = pColor + y*RLSW.framebuffer.width + RLSW.scMin[0]; +#else + COLOR_TYPE *rowColor = pColor + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); +#endif + +#if DEPTH_IS_PACKED + DEPTH_TYPE *rowDepth = pDepth + y*RLSW.framebuffer.width + RLSW.scMin[0]; +#else + DEPTH_TYPE *rowDepth = pDepth + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); +#endif + + for (int x = 0; x < w; x++) + { +#if COLOR_IS_PACKED + *rowColor++ = packedColor; +#else + *rowColor++ = r; + *rowColor++ = g; + *rowColor++ = b; + *rowColor++ = a; +#endif + +#if DEPTH_IS_PACKED + *rowDepth++ = d; +#else + *rowDepth++ = d0; + *rowDepth++ = d1; + *rowDepth++ = d2; +#endif } } } @@ -1356,140 +1463,27 @@ static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) { for (int i = 0; i < size; i++) { - *p++ = d_byte0; - *p++ = d_byte1; - *p++ = d_byte2; +#if COLOR_IS_PACKED + *pColor++ = packedColor; +#else + *pColor++ = r; + *pColor++ = g; + *pColor++ = b; + *pColor++ = a; +#endif + +#if DEPTH_IS_PACKED + *pDepth++ = d; +#else + *pDepth++ = d0; + *pDepth++ = d1; + *pDepth++ = d2; +#endif } } } -#endif // SW_DEPTH_BUFFER_BITS - -static inline void sw_framebuffer_fill(void *colorPtr, void *depthPtr, int size, float color[4], float depth_value) -{ -#if (SW_COLOR_BUFFER_BITS == 8) - // Calculate and pack 3:3:2 color - // Scale color components to the max value for each bit depth and round - uint8_t r8 = (uint8_t)(color[0]*7.0f + 0.5f); - uint8_t g8 = (uint8_t)(color[1]*7.0f + 0.5f); - uint8_t b8 = (uint8_t)(color[2]*3.0f + 0.5f); - // Pack the components into a single byte - uint8_t packedColor = ((r8 & 0x07) << 5) | ((g8 & 0x07) << 2) | (b8 & 0x03); - uint8_t *cptr = (uint8_t *)colorPtr; -#elif (SW_COLOR_BUFFER_BITS == 16) - // Calculate and pack 5:6:5 color - // Scale color components to the max value for each bit depth and round - uint16_t r16 = (uint16_t)(color[0]*31.0f + 0.5f); - uint16_t g16 = (uint16_t)(color[1]*63.0f + 0.5f); - uint16_t b16 = (uint16_t)(color[2]*31.0f + 0.5f); - // Pack the components into a 16-bit value - uint16_t packedColor = ((r16 & 0x1F) << 11) | ((g16 & 0x3F) << 5) | (b16 & 0x1F); - uint16_t *cptr = (uint16_t *)colorPtr; -#elif (SW_COLOR_BUFFER_BITS == 24) - // Calculate 8:8:8 color components - uint8_t r24 = (uint8_t)(color[0]*255.0f); - uint8_t g24 = (uint8_t)(color[1]*255.0f); - uint8_t b24 = (uint8_t)(color[2]*255.0f); - uint8_t *cptr = (uint8_t *)colorPtr; -#endif - -#if (SW_DEPTH_BUFFER_BITS == 8) - // Calculate 8-bit depth - uint8_t d8 = (uint8_t)(depth_value*UINT8_MAX); - uint8_t *dptr = (uint8_t *)depthPtr; -#elif (SW_DEPTH_BUFFER_BITS == 16) - // Calculate 16-bit depth - uint16_t d16 = (uint16_t)(depth_value*UINT16_MAX); - uint16_t *dptr = (uint16_t *)depthPtr; -#elif (SW_DEPTH_BUFFER_BITS == 24) - // Calculate 24-bit depth and pre-calculate bytes - uint32_t d32 = (uint32_t)(depth_value*UINT32_MAX); - uint8_t dByte0 = (uint8_t)((d32 >> 16) & 0xFF); - uint8_t dByte1 = (uint8_t)((d32 >> 8) & 0xFF); - uint8_t dByte2 = (uint8_t)(d32 & 0xFF); - uint8_t *dptr = (uint8_t *)depthPtr; -#endif - - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) - { - int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; - for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) - { - int rowStartIdx = y*RLSW.framebuffer.width + RLSW.scMin[0]; - - // Calculate starting pointers for the current row within the scissor rectangle - #if (SW_COLOR_BUFFER_BITS == 8) - uint8_t *curCPtr = cptr + rowStartIdx; - #elif (SW_COLOR_BUFFER_BITS == 16) - uint16_t *curCPtr = cptr + rowStartIdx; - #elif (SW_COLOR_BUFFER_BITS == 24) - uint8_t *curCPtr = cptr + 3*rowStartIdx; - #endif - - #if (SW_DEPTH_BUFFER_BITS == 8) - uint8_t *curDPtr = dptr + rowStartIdx; - #elif (SW_DEPTH_BUFFER_BITS == 16) - uint16_t *curDPtr = dptr + rowStartIdx; - #elif (SW_DEPTH_BUFFER_BITS == 24) - uint8_t *curDPtr = dptr + 3*rowStartIdx; - #endif - - // Fill the current row within the scissor rectangle - for (int xCount = 0; xCount < wScissor; xCount++) - { - // Write color - #if (SW_COLOR_BUFFER_BITS == 8) - *curCPtr++ = packedColor; - #elif (SW_COLOR_BUFFER_BITS == 16) - *curCPtr++ = packedColor; - #elif (SW_COLOR_BUFFER_BITS == 24) - *curCPtr++ = r24; - *curCPtr++ = g24; - *curCPtr++ = b24; - #endif - - // Write depth - #if (SW_DEPTH_BUFFER_BITS == 8) - *curDPtr++ = d8; - #elif (SW_DEPTH_BUFFER_BITS == 16) - *curDPtr++ = d16; - #elif (SW_DEPTH_BUFFER_BITS == 24) - *curDPtr++ = dByte0; - *curDPtr++ = dByte1; - *curDPtr++ = dByte2; - #endif - } - } - return; - } - - for (int i = 0; i < size; i++) - { - // Write color - #if (SW_COLOR_BUFFER_BITS == 8) - *cptr++ = packedColor; - #elif (SW_COLOR_BUFFER_BITS == 16) - *cptr++ = packedColor; - #elif (SW_COLOR_BUFFER_BITS == 24) - *cptr++ = r24; - *cptr++ = g24; - *cptr++ = b24; - #endif - - // Write depth - #if (SW_DEPTH_BUFFER_BITS == 8) - *dptr++ = d8; - #elif (SW_DEPTH_BUFFER_BITS == 16) - *dptr++ = d16; - #elif (SW_DEPTH_BUFFER_BITS == 24) - *dptr++ = dByte0; - *dptr++ = dByte1; - *dptr++ = dByte2; - #endif - } -} - -#define DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(name, DST_PTR_T) \ +#define DEFINE_FRAMEBUFFER_COPY_BEGIN(name, DST_PTR_T) \ static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST_PTR_T *dst) \ { \ const void *src = RLSW.framebuffer.color; \ @@ -1499,23 +1493,13 @@ static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST uint8_t color[4]; \ sw_framebuffer_read_color8(color, src); \ -#define DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(name, DST_PTR_T) \ -static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST_PTR_T *dst) \ -{ \ - const void *src = RLSW.framebuffer.color; \ - \ - for (int iy = y; iy < h; iy++) { \ - for (int ix = x; ix < w; ix++) { \ - float color[4]; \ - sw_framebuffer_read_color(color, src); \ - #define DEFINE_FRAMEBUFFER_COPY_END() \ - sw_framebuffer_inc_const_color_addr(&src); \ + INC_COLOR_PTR(src); \ } \ } \ } -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYSCALE, uint8_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(GRAYSCALE, uint8_t) { // NTSC grayscale conversion: Y = 0.299R + 0.587G + 0.114B uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); @@ -1523,7 +1507,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYSCALE, uint8_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYALPHA, uint8_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(GRAYALPHA, uint8_t) { // Convert RGB to grayscale using NTSC formula uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); @@ -1535,7 +1519,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYALPHA, uint8_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G6B5, uint16_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R5G6B5, uint16_t) { // Convert 8-bit RGB to 5:6:5 format uint8_t r5 = (color[0]*31 + 127)/255; @@ -1552,7 +1536,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G6B5, uint16_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8, uint8_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R8G8B8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; @@ -1568,7 +1552,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8, uint8_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G5B5A1, uint16_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R5G5B5A1, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g5 = (color[1]*31 + 127)/255; @@ -1585,7 +1569,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G5B5A1, uint16_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R4G4B4A4, uint16_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R4G4B4A4, uint16_t) { uint8_t r4 = (color[0]*15 + 127)/255; uint8_t g4 = (color[1]*15 + 127)/255; @@ -1602,7 +1586,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R4G4B4A4, uint16_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8A8, uint8_t) +DEFINE_FRAMEBUFFER_COPY_BEGIN(R8G8B8A8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; @@ -1619,87 +1603,7 @@ DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8A8, uint8_t) } DEFINE_FRAMEBUFFER_COPY_END() -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32, float) -{ - dst[0] = color[0]; - dst++; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32G32B32, float) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = color[2]; - dst[1] = color[1]; - dst[2] = color[0]; -#else // RGBA - dst[0] = color[0]; - dst[1] = color[1]; - dst[2] = color[2]; -#endif - - dst += 3; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32G32B32A32, float) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = color[2]; - dst[1] = color[1]; - dst[2] = color[0]; -#else // RGBA - dst[0] = color[0]; - dst[1] = color[1]; - dst[2] = color[2]; -#endif - dst[3] = color[3]; - - dst += 4; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16, sw_half_t) -{ - dst[0] = sw_f16_from_f32(color[0]); - dst++; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16G16B16, sw_half_t) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = sw_f16_from_f32(color[2]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[0]); -#else // RGBA - dst[0] = sw_f16_from_f32(color[0]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[2]); -#endif - - dst += 3; -} -DEFINE_FRAMEBUFFER_COPY_END() - -DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16G16B16A16, sw_half_t) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = sw_f16_from_f32(color[2]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[0]); -#else // RGBA - dst[0] = sw_f16_from_f32(color[0]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[2]); -#endif - dst[3] = sw_f16_from_f32(color[3]); - - dst += 4; -} -DEFINE_FRAMEBUFFER_COPY_END() - -#define DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(name, DST_PTR_T) \ +#define DEFINE_FRAMEBUFFER_BLIT_BEGIN(name, DST_PTR_T) \ static inline void sw_framebuffer_blit_to_##name( \ int xDst, int yDst, int wDst, int hDst, \ int xSrc, int ySrc, int wSrc, int hSrc, \ @@ -1708,56 +1612,33 @@ static inline void sw_framebuffer_blit_to_##name( const uint8_t *srcBase = (uint8_t*)RLSW.framebuffer.color; \ int fbWidth = RLSW.framebuffer.width; \ \ - uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ - uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ + uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ + uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ \ for (int dy = 0; dy < hDst; dy++) { \ - uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ + uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ int sy = yFix >> 16; \ \ for (int dx = 0; dx < wDst; dx++) { \ - uint32_t xFix = dx*xScale; \ + uint32_t xFix = dx*xScale; \ int sx = xFix >> 16; \ - const void *srcPtr = sw_framebuffer_get_color_addr(srcBase, sy*fbWidth + sx); \ + const void *srcPtr = GET_COLOR_PTR(srcBase, sy*fbWidth + sx); \ uint8_t color[4]; \ sw_framebuffer_read_color8(color, srcPtr); \ -#define DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(name, DST_PTR_T) \ -static inline void sw_framebuffer_blit_to_##name( \ - int xDst, int yDst, int wDst, int hDst, \ - int xSrc, int ySrc, int wSrc, int hSrc, \ - DST_PTR_T *dst) \ -{ \ - const uint8_t *srcBase = (uint8_t *)RLSW.framebuffer.color; \ - int fbWidth = RLSW.framebuffer.width; \ - \ - uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ - uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ - \ - for (int dy = 0; dy < hDst; dy++) { \ - uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ - int sy = yFix >> 16; \ - \ - for (int dx = 0; dx < wDst; dx++) { \ - uint32_t xFix = dx*xScale; \ - int sx = xFix >> 16; \ - const void *srcPtr = sw_framebuffer_get_color_addr(srcBase, sy*fbWidth + sx); \ - float color[4]; \ - sw_framebuffer_read_color(color, srcPtr); \ - #define DEFINE_FRAMEBUFFER_BLIT_END() \ } \ } \ } -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(GRAYSCALE, uint8_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(GRAYSCALE, uint8_t) { uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); *dst++ = gray; } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(GRAYALPHA, uint8_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(GRAYALPHA, uint8_t) { uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); @@ -1768,7 +1649,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(GRAYALPHA, uint8_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G6B5, uint16_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R5G6B5, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g6 = (color[1]*63 + 127)/255; @@ -1784,7 +1665,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G6B5, uint16_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8, uint8_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R8G8B8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; @@ -1800,7 +1681,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8, uint8_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G5B5A1, uint16_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R5G5B5A1, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g5 = (color[1]*31 + 127)/255; @@ -1817,7 +1698,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G5B5A1, uint16_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R4G4B4A4, uint16_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R4G4B4A4, uint16_t) { uint8_t r4 = (color[0]*15 + 127)/255; uint8_t g4 = (color[1]*15 + 127)/255; @@ -1834,7 +1715,7 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R4G4B4A4, uint16_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8A8, uint8_t) +DEFINE_FRAMEBUFFER_BLIT_BEGIN(R8G8B8A8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; @@ -1851,86 +1732,6 @@ DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8A8, uint8_t) } DEFINE_FRAMEBUFFER_BLIT_END() -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32, uint8_t) -{ - dst[0] = color[0]; - dst++; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32G32B32, float) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = color[2]; - dst[1] = color[1]; - dst[2] = color[0]; -#else // RGBA - dst[0] = color[0]; - dst[1] = color[1]; - dst[2] = color[2]; -#endif - - dst += 3; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32G32B32A32, float) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = color[2]; - dst[1] = color[1]; - dst[2] = color[0]; -#else // RGBA - dst[0] = color[0]; - dst[1] = color[1]; - dst[2] = color[2]; -#endif - dst[3] = color[3]; - - dst += 4; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16, sw_half_t) -{ - dst[0] = sw_f16_from_f32(color[0]); - dst++; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16G16B16, sw_half_t) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = sw_f16_from_f32(color[2]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[0]); -#else // RGBA - dst[0] = sw_f16_from_f32(color[0]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[2]); -#endif - - dst += 3; -} -DEFINE_FRAMEBUFFER_BLIT_END() - -DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16G16B16A16, sw_half_t) -{ -#if SW_GL_FRAMEBUFFER_COPY_BGRA - dst[0] = sw_f16_from_f32(color[2]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[0]); -#else // RGBA - dst[0] = sw_f16_from_f32(color[0]); - dst[1] = sw_f16_from_f32(color[1]); - dst[2] = sw_f16_from_f32(color[2]); -#endif - dst[3] = sw_f16_from_f32(color[3]); - - dst += 4; -} -DEFINE_FRAMEBUFFER_BLIT_END() - // Pixel format management functions static inline int sw_get_pixel_format(SWformat format, SWtype type) @@ -1985,210 +1786,178 @@ static inline int sw_get_pixel_format(SWformat format, SWtype type) return SW_PIXELFORMAT_UNKNOWN; } -int sw_get_pixel_bytes(sw_pixelformat_t format) +static inline void sw_get_pixel(uint8_t *color, const void *pixels, uint32_t offset, sw_pixelformat_t format) { - int bpp = 0; - switch (format) { - case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 1; break; + case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: + { + uint8_t gray = ((const uint8_t*)pixels)[offset]; + color[0] = gray; + color[1] = gray; + color[2] = gray; + color[3] = 255; + break; + } case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + { + const uint8_t *src = &((const uint8_t*)pixels)[offset*2]; + color[0] = src[0]; + color[1] = src[0]; + color[2] = src[0]; + color[3] = src[1]; + break; + } case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: + { + uint16_t pixel = ((const uint16_t*)pixels)[offset]; + color[0] = ((pixel >> 11) & 0x1F)*255/31; // R (5 bits) + color[1] = ((pixel >> 5) & 0x3F)*255/63; // G (6 bits) + color[2] = (pixel & 0x1F)*255/31; // B (5 bits) + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: + { + const uint8_t *src = &((const uint8_t*)pixels)[offset*3]; + color[0] = src[0]; + color[1] = src[1]; + color[2] = src[2]; + color[3] = 255; + break; + } case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: - case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 2; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 4; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 3; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 4; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 4*3; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 4*4; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 2; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 2*3; break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 2*4; break; - default: break; - } - - return bpp; -} - -static inline void sw_get_pixel_grayscale(float *color, const void *pixels, uint32_t offset) -{ - float gray = (float)((uint8_t *)pixels)[offset]*(1.0f/255); - - color[0] = gray; - color[1] = gray; - color[2] = gray; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_red_16(float *color, const void *pixels, uint32_t offset) -{ - float value = sw_f16_to_f32(((sw_half_t *)pixels)[offset]); - - color[0] = value; - color[1] = value; - color[2] = value; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_red_32(float *color, const void *pixels, uint32_t offset) -{ - float value = ((float *)pixels)[offset]; - - color[0] = value; - color[1] = value; - color[2] = value; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_grayscale_alpha(float *color, const void *pixels, uint32_t offset) -{ - const uint8_t *pixelData = (const uint8_t *)pixels + 2*offset; - - color[0] = color[1] = color[2] = (float)pixelData[0]*(1.0f/255); - color[3] = (float)pixelData[1]*(1.0f/255); -} - -static inline void sw_get_pixel_rgb_565(float *color, const void *pixels, uint32_t offset) -{ - uint16_t pixel = ((uint16_t *)pixels)[offset]; - - color[0] = (float)((pixel & 0xF800) >> 11)/31; - color[1] = (float)((pixel & 0x7E0) >> 5)/63; - color[2] = (float)(pixel & 0x1F)/31; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_rgb_888(float *color, const void *pixels, uint32_t offset) -{ - const uint8_t *pixel = (const uint8_t *)pixels + 3*offset; - - color[0] = (float)pixel[0]*(1.0f/255); - color[1] = (float)pixel[1]*(1.0f/255); - color[2] = (float)pixel[2]*(1.0f/255); - color[3] = 1.0f; -} - -static inline void sw_get_pixel_rgb_161616(float *color, const void *pixels, uint32_t offset) -{ - const sw_half_t *pixel = (sw_half_t *)pixels + 3*offset; - - color[0] = sw_f16_to_f32(pixel[0]); - color[1] = sw_f16_to_f32(pixel[1]); - color[2] = sw_f16_to_f32(pixel[2]); - color[3] = 1.0f; -} - -static inline void sw_get_pixel_rgb_323232(float *color, const void *pixels, uint32_t offset) -{ - const float *pixel = (float *)pixels + 3*offset; - - color[0] = pixel[0]; - color[1] = pixel[1]; - color[2] = pixel[2]; - color[3] = 1.0f; -} - -static inline void sw_get_pixel_rgba_5551(float *color, const void *pixels, uint32_t offset) -{ - uint16_t pixel = ((uint16_t *)pixels)[offset]; - - color[0] = (float)((pixel & 0xF800) >> 11)/31; - color[1] = (float)((pixel & 0x7C0) >> 6)/31; - color[2] = (float)((pixel & 0x3E) >> 1)/31; - color[3] = (float)(pixel & 0x1); -} - -static inline void sw_get_pixel_rgba_4444(float *color, const void *pixels, uint32_t offset) -{ - uint16_t pixel = ((uint16_t *)pixels)[offset]; - - color[0] = (float)((pixel & 0xF000) >> 12)/15; - color[1] = (float)((pixel & 0xF00) >> 8)/15; - color[2] = (float)((pixel & 0xF0) >> 4)/15; - color[3] = (float)(pixel & 0xF)/15; -} - -static inline void sw_get_pixel_rgba_8888(float *color, const void *pixels, uint32_t offset) -{ - const uint8_t *pixel = (uint8_t *)pixels + 4*offset; - - color[0] = (float)pixel[0]*(1.0f/255); - color[1] = (float)pixel[1]*(1.0f/255); - color[2] = (float)pixel[2]*(1.0f/255); - color[3] = (float)pixel[3]*(1.0f/255); -} - -static inline void sw_get_pixel_rgba_16161616(float *color, const void *pixels, uint32_t offset) -{ - const sw_half_t *pixel = (sw_half_t *)pixels + 4*offset; - - color[0] = sw_f16_to_f32(pixel[0]); - color[1] = sw_f16_to_f32(pixel[1]); - color[2] = sw_f16_to_f32(pixel[2]); - color[3] = sw_f16_to_f32(pixel[3]); -} - -static inline void sw_get_pixel_rgba_32323232(float *color, const void *pixels, uint32_t offset) -{ - const float *pixel = (float *)pixels + 4*offset; - - color[0] = pixel[0]; - color[1] = pixel[1]; - color[2] = pixel[2]; - color[3] = pixel[3]; -} - -static inline void sw_get_pixel(float *color, const void *pixels, uint32_t offset, sw_pixelformat_t format) -{ - switch (format) - { - case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_get_pixel_grayscale(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_get_pixel_grayscale_alpha(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_get_pixel_rgb_565(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_get_pixel_rgb_888(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_get_pixel_rgba_5551(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_get_pixel_rgba_4444(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_get_pixel_rgba_8888(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_get_pixel_red_32(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_get_pixel_rgb_323232(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_get_pixel_rgba_32323232(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_get_pixel_red_16(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_get_pixel_rgb_161616(color, pixels, offset); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_get_pixel_rgba_16161616(color, pixels, offset); break; - case SW_PIXELFORMAT_UNKNOWN: break; - default: break; + { + uint16_t pixel = ((const uint16_t*)pixels)[offset]; + color[0] = ((pixel >> 11) & 0x1F)*255/31; // R (5 bits) + color[1] = ((pixel >> 6) & 0x1F)*255/31; // G (5 bits) + color[2] = ((pixel >> 1) & 0x1F)*255/31; // B (5 bits) + color[3] = (pixel & 0x01)*255; // A (1 bit) + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: + { + uint16_t pixel = ((const uint16_t*)pixels)[offset]; + color[0] = ((pixel >> 12) & 0x0F)*255/15; // R (4 bits) + color[1] = ((pixel >> 8) & 0x0F)*255/15; // G (4 bits) + color[2] = ((pixel >> 4) & 0x0F)*255/15; // B (4 bits) + color[3] = (pixel & 0x0F)*255/15; // A (4 bits) + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: + { + const uint8_t *src = &((const uint8_t*)pixels)[offset*4]; + color[0] = src[0]; + color[1] = src[1]; + color[2] = src[2]; + color[3] = src[3]; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R32: + { + float val = ((const float*)pixels)[offset]; + uint8_t gray = (uint8_t)(val*255.0f); + color[0] = gray; + color[1] = gray; + color[2] = gray; + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: + { + const float *src = &((const float*)pixels)[offset*3]; + color[0] = (uint8_t)(src[0]*255.0f); + color[1] = (uint8_t)(src[1]*255.0f); + color[2] = (uint8_t)(src[2]*255.0f); + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + { + const float *src = &((const float*)pixels)[offset*4]; + color[0] = (uint8_t)(src[0]*255.0f); + color[1] = (uint8_t)(src[1]*255.0f); + color[2] = (uint8_t)(src[2]*255.0f); + color[3] = (uint8_t)(src[3]*255.0f); + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R16: + { + uint16_t val = ((const uint16_t*)pixels)[offset]; + uint8_t gray = sw_half_to_float(val)/255.0f; + color[0] = gray; + color[1] = gray; + color[2] = gray; + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: + { + const uint16_t *src = &((const uint16_t*)pixels)[offset*3]; + color[0] = sw_half_to_float(src[0])/255.0f; + color[1] = sw_half_to_float(src[1])/255.0f; + color[2] = sw_half_to_float(src[2])/255.0f; + color[3] = 255; + break; + } + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + { + const uint16_t *src = &((const uint16_t*)pixels)[offset*4]; + color[0] = sw_half_to_float(src[0])/255.0f; + color[1] = sw_half_to_float(src[1])/255.0f; + color[2] = sw_half_to_float(src[2])/255.0f; + color[3] = sw_half_to_float(src[3])/255.0f; + break; + } + case SW_PIXELFORMAT_UNKNOWN: + default: + { + color[0] = 0; + color[1] = 0; + color[2] = 0; + color[3] = 0; + break; + } } } // Texture sampling functionality +static inline void sw_texture_fetch(float* color, const sw_texture_t* tex, int x, int y) +{ + sw_float_from_unorm8_simd(color, &tex->pixels[4*(y*tex->width + x)]); +} + static inline void sw_texture_sample_nearest(float *color, const sw_texture_t *tex, float u, float v) { u = (tex->sWrap == SW_REPEAT)? sw_fract(u) : sw_saturate(u); v = (tex->tWrap == SW_REPEAT)? sw_fract(v) : sw_saturate(v); - int x = u*tex->width, y = v*tex->height; + int x = u*tex->width; + int y = v*tex->height; - sw_get_pixel(color, tex->pixels.cptr, y*tex->width + x, tex->format); + sw_texture_fetch(color, tex, x, y); } static inline void sw_texture_sample_linear(float *color, const sw_texture_t *tex, float u, float v) { - // TODO: REVIEW: With a bit more cleverness we could clearly reduce the + // TODO: With a bit more cleverness we could clearly reduce the // number of operations here, but for now it works fine. - float xf = u*tex->width - 0.5f; - float yf = v*tex->height - 0.5f; + float xf = (u*tex->width) - 0.5f; + float yf = (v*tex->height) - 0.5f; - int x0 = (int)floorf(xf); - int y0 = (int)floorf(yf); + float fx = sw_fract(xf); + float fy = sw_fract(yf); - float fx = xf - x0; - float fy = yf - y0; + int x0 = (int)xf; + int y0 = (int)yf; int x1 = x0 + 1; int y1 = y0 + 1; + // NOTE: If the textures are POT we could avoid the division for SW_REPEAT + if (tex->sWrap == SW_CLAMP) { x0 = (x0 > tex->wMinus1)? tex->wMinus1 : x0; @@ -2212,10 +1981,10 @@ static inline void sw_texture_sample_linear(float *color, const sw_texture_t *te } float c00[4], c10[4], c01[4], c11[4]; - sw_get_pixel(c00, tex->pixels.cptr, y0*tex->width + x0, tex->format); - sw_get_pixel(c10, tex->pixels.cptr, y0*tex->width + x1, tex->format); - sw_get_pixel(c01, tex->pixels.cptr, y1*tex->width + x0, tex->format); - sw_get_pixel(c11, tex->pixels.cptr, y1*tex->width + x1, tex->format); + sw_texture_fetch(c00, tex, x0, y0); + sw_texture_fetch(c10, tex, x1, y0); + sw_texture_fetch(c01, tex, x0, y1); + sw_texture_fetch(c11, tex, x1, y1); for (int i = 0; i < 4; i++) { @@ -2512,23 +2281,23 @@ static inline void sw_triangle_clip_and_project(void) // Calculation of the reciprocal of W for normalization // as well as perspective-correct attributes - const float invW = 1.0f/v->homogeneous[3]; - v->homogeneous[3] = invW; + const float wRcp = 1.0f/v->homogeneous[3]; + v->homogeneous[3] = wRcp; // Division of XYZ coordinates by weight - v->homogeneous[0] *= invW; - v->homogeneous[1] *= invW; - v->homogeneous[2] *= invW; + v->homogeneous[0] *= wRcp; + v->homogeneous[1] *= wRcp; + v->homogeneous[2] *= wRcp; // Division of texture coordinates (perspective-correct) - v->texcoord[0] *= invW; - v->texcoord[1] *= invW; + v->texcoord[0] *= wRcp; + v->texcoord[1] *= wRcp; // Division of colors (perspective-correct) - v->color[0] *= invW; - v->color[1] *= invW; - v->color[2] *= invW; - v->color[3] *= invW; + v->color[0] *= wRcp; + v->color[1] *= wRcp; + v->color[2] *= wRcp; + v->color[3] *= wRcp; // Transformation to screen space sw_project_ndc_to_screen(v->screen, v->homogeneous); @@ -2583,8 +2352,8 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, } \ \ /* Pre-calculate the starting pointers for the framebuffer row */ \ - void *cptr = sw_framebuffer_get_color_addr(RLSW.framebuffer.color, y*RLSW.framebuffer.width + xStart); \ - void *dptr = sw_framebuffer_get_depth_addr(RLSW.framebuffer.depth, y*RLSW.framebuffer.width + xStart); \ + void *cptr = GET_COLOR_PTR(RLSW.framebuffer.color, y*RLSW.framebuffer.width + xStart); \ + void *dptr = GET_DEPTH_PTR(RLSW.framebuffer.depth, y*RLSW.framebuffer.width + xStart); \ \ /* Scanline rasterization */ \ for (int x = xStart; x < xEnd; x++) \ @@ -2624,12 +2393,7 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, srcColor); \ - dstColor[0] = sw_saturate(dstColor[0]); \ - dstColor[1] = sw_saturate(dstColor[1]); \ - dstColor[2] = sw_saturate(dstColor[2]); \ - \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else \ @@ -2651,8 +2415,8 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, v += dvDx; \ } \ \ - sw_framebuffer_inc_color_addr(&cptr); \ - sw_framebuffer_inc_depth_addr(&dptr); \ + INC_COLOR_PTR(cptr); \ + INC_DEPTH_PTR(dptr); \ } \ } @@ -2777,13 +2541,17 @@ static inline void sw_triangle_render(void) } \ } - if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) - else if (SW_STATE_CHECK(SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) + uint32_t state = RLSW.stateFlags; + if (RLSW.currentTexture == 0) state &= ~SW_STATE_TEXTURE_2D; + if ((RLSW.srcFactor == SW_ONE) && (RLSW.dstFactor == SW_ZERO)) state &= ~SW_STATE_BLEND; + + if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) + else if (SW_STATE_CHECK_EX(state, SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) else TRIANGLE_RASTER(sw_triangle_raster) #undef TRIANGLE_RASTER @@ -2859,23 +2627,23 @@ static inline void sw_quad_clip_and_project(void) // Calculation of the reciprocal of W for normalization // as well as perspective-correct attributes - const float invW = 1.0f/v->homogeneous[3]; - v->homogeneous[3] = invW; + const float wRcp = 1.0f/v->homogeneous[3]; + v->homogeneous[3] = wRcp; // Division of XYZ coordinates by weight - v->homogeneous[0] *= invW; - v->homogeneous[1] *= invW; - v->homogeneous[2] *= invW; + v->homogeneous[0] *= wRcp; + v->homogeneous[1] *= wRcp; + v->homogeneous[2] *= wRcp; // Division of texture coordinates (perspective-correct) - v->texcoord[0] *= invW; - v->texcoord[1] *= invW; + v->texcoord[0] *= wRcp; + v->texcoord[1] *= wRcp; // Division of colors (perspective-correct) - v->color[0] *= invW; - v->color[1] *= invW; - v->color[2] *= invW; - v->color[3] *= invW; + v->color[0] *= wRcp; + v->color[1] *= wRcp; + v->color[2] *= wRcp; + v->color[3] *= wRcp; // Transformation to screen space sw_project_ndc_to_screen(v->screen, v->homogeneous); @@ -2908,76 +2676,40 @@ static inline bool sw_quad_is_axis_aligned(void) static inline void sw_quad_sort_cw(const sw_vertex_t* *output) { - // Sort 4 quad vertices into clockwise order with fixed layout: - // - // v0 -- v1 - // | | - // v3 -- v2 - // - // The goal is: - // - v0: top-left (minimum Y, then minimum X) - // - v1: top-right (minimum Y row, maximum X) - // - v2: bottom-right (maximum Y, maximum X) - // - v3: bottom-left (maximum Y, minimum X) const sw_vertex_t *input = RLSW.vertexBuffer; - // Separate vertices into top and bottom based on Y-coordinate - const sw_vertex_t *top[2] = {NULL, NULL}; - const sw_vertex_t *bottom[2] = {NULL, NULL}; - int topCount = 0, bottomCount = 0; + // Calculate the centroid of the quad + float cx = (input[0].screen[0] + input[1].screen[0] + + input[2].screen[0] + input[3].screen[0])*0.25f; + float cy = (input[0].screen[1] + input[1].screen[1] + + input[2].screen[1] + input[3].screen[1])*0.25f; - // Find minimum and maximum Y - float minY = input[0].screen[1]; - float maxY = input[0].screen[1]; + // Calculate the angle of each vertex relative to the center + // and assign them directly to their correct position + const sw_vertex_t *corners[4] = { 0 }; - for (int i = 1; i < 4; i++) - { - if (input[i].screen[1] < minY) minY = input[i].screen[1]; - if (input[i].screen[1] > maxY) maxY = input[i].screen[1]; - } - - // Separate vertices based on Y-coordinate for (int i = 0; i < 4; i++) { - if ((input[i].screen[1] == minY) && (topCount < 2)) top[topCount++] = &input[i]; - else if ((input[i].screen[1] == maxY) && (bottomCount < 2)) bottom[bottomCount++] = &input[i]; + float dx = input[i].screen[0] - cx; + float dy = input[i].screen[1] - cy; + + // Determine the quadrant (clockwise from top-left) + // top-left: dx < 0, dy < 0 + // top-right: dx >= 0, dy < 0 + // bottom-right: dx >= 0, dy >= 0 + // bottom-left: dx < 0, dy >= 0 + + int idx; + if (dy < 0) idx = (dx < 0)? 0 : 1; // Top row + else idx = (dx < 0)? 3 : 2; // Bottom row + + corners[idx] = &input[i]; } - // If we don't have enough top/bottom vertices (e.g., Y values are all different), - // classify vertices as top or bottom based on whether they're closer to minY or maxY - for (int i = 0; i < 4; i++) - { - if ((topCount < 2) && (&input[i] != top[0]) && (&input[i] != bottom[0]) && (&input[i] != bottom[1])) - { - if (fabsf(input[i].screen[1] - minY) <= fabsf(input[i].screen[1] - maxY)) top[topCount++] = &input[i]; - } - if ((bottomCount < 2) && (&input[i] != top[0]) && (&input[i] != top[1]) && (&input[i] != bottom[0])) - { - if (fabsf(input[i].screen[1] - maxY) < fabsf(input[i].screen[1] - minY)) bottom[bottomCount++] = &input[i]; - } - } - - // Sort top vertices by X (left to right) - if ((topCount == 2) && (top[0]->screen[0] > top[1]->screen[0])) - { - const sw_vertex_t *temp = top[0]; - top[0] = top[1]; - top[1] = temp; - } - - // Sort bottom vertices by X (left to right) - if ((bottomCount == 2) && (bottom[0]->screen[0] > bottom[1]->screen[0])) - { - const sw_vertex_t *temp = bottom[0]; - bottom[0] = bottom[1]; - bottom[1] = temp; - } - - // Assign vertices in clockwise order as per the required layout - output[0] = top[0]; // v0: top-left - output[1] = top[topCount-1]; // v1: top-right - output[2] = bottom[bottomCount-1]; // v2: bottom-right - output[3] = bottom[0]; // v3: bottom-left + output[0] = corners[0]; // top-left + output[1] = corners[1]; // top-right + output[2] = corners[2]; // bottom-right + output[3] = corners[3]; // bottom-left } // TODO: REVIEW: Could a perfectly aligned quad, where one of the four points has a different depth, @@ -3052,8 +2784,8 @@ static inline void FUNC_NAME(void) \ for (int y = yMin; y < yMax; y++) \ { \ - void *cptr = sw_framebuffer_get_color_addr(cDstBase, y*wDst + xMin); \ - void *dptr = sw_framebuffer_get_depth_addr(dDstBase, y*wDst + xMin); \ + void *cptr = GET_COLOR_PTR(cDstBase, y*wDst + xMin); \ + void *dptr = GET_DEPTH_PTR(dDstBase, y*wDst + xMin); \ \ float z = zScanline; \ float u = uScanline; \ @@ -3101,13 +2833,7 @@ static inline void FUNC_NAME(void) { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, srcColor); \ - dstColor[0] = sw_saturate(dstColor[0]); \ - dstColor[1] = sw_saturate(dstColor[1]); \ - dstColor[2] = sw_saturate(dstColor[2]); \ - dstColor[3] = sw_saturate(dstColor[3]); \ - \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else sw_framebuffer_write_color(cptr, srcColor); \ @@ -3125,8 +2851,8 @@ static inline void FUNC_NAME(void) v += tcDx[1]; \ } \ \ - sw_framebuffer_inc_color_addr(&cptr); \ - sw_framebuffer_inc_depth_addr(&dptr); \ + INC_COLOR_PTR(cptr); \ + INC_DEPTH_PTR(dptr); \ } \ \ zScanline += zDy; \ @@ -3163,15 +2889,19 @@ static inline void sw_quad_render(void) if (RLSW.vertexCounter < 3) return; + uint32_t state = RLSW.stateFlags; + if (RLSW.currentTexture == 0) state &= ~SW_STATE_TEXTURE_2D; + if ((RLSW.srcFactor == SW_ONE) && (RLSW.dstFactor == SW_ZERO)) state &= ~SW_STATE_BLEND; + if ((RLSW.vertexCounter == 4) && sw_quad_is_axis_aligned()) { - if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND(); - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_DEPTH_BLEND(); - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_BLEND(); - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_TEX_DEPTH(); - else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_quad_raster_axis_aligned_BLEND(); - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_DEPTH(); - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) sw_quad_raster_axis_aligned_TEX(); + if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_DEPTH_BLEND(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_BLEND(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_TEX_DEPTH(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_BLEND)) sw_quad_raster_axis_aligned_BLEND(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_DEPTH(); + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D)) sw_quad_raster_axis_aligned_TEX(); else sw_quad_raster_axis_aligned(); return; } @@ -3189,13 +2919,13 @@ static inline void sw_quad_render(void) } \ } - if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) - else if (SW_STATE_CHECK(SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) - else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) - else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) + if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) + else if (SW_STATE_CHECK_EX(state, SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) + else if (SW_STATE_CHECK_EX(state, SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) + else if (SW_STATE_CHECK_EX(state, SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) else TRIANGLE_RASTER(sw_triangle_raster) #undef TRIANGLE_RASTER @@ -3310,7 +3040,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ int dy = y2 - y1; \ \ /* Handling of lines that are more horizontal or vertical */ \ - if ((dx == 0) && (dy == 0)) \ + if ((dx == 0) && (dy == 0)) \ { \ /* TODO: A point should be rendered here */ \ return; \ @@ -3366,8 +3096,8 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ for (int i = 0; i != longLen; i += sgnInc) \ { \ int offset = (y1 + i)*fbWidth + (x1 + (j >> 16)); \ - void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ - void *cptr; \ + void *dptr = GET_DEPTH_PTR(dBuffer, offset); \ + void *cptr = NULL; \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3377,13 +3107,12 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ + cptr = GET_COLOR_PTR(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ @@ -3403,8 +3132,8 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ for (int i = 0; i != longLen; i += sgnInc) \ { \ int offset = (y1 + (j >> 16))*fbWidth + (x1 + i); \ - void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ - void *cptr; \ + void *dptr = GET_DEPTH_PTR(dBuffer, offset); \ + void *cptr = NULL; \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3414,17 +3143,16 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ + cptr = GET_COLOR_PTR(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ - else sw_framebuffer_write_color(cptr, color); \ + else sw_framebuffer_write_color(cptr, color); \ \ discardB: \ j += decInc; \ @@ -3570,7 +3298,7 @@ static inline void FUNC_NAME(int x, int y, float z, const float color[4]) \ \ int offset = y*RLSW.framebuffer.width + x; \ \ - void *dptr = sw_framebuffer_get_depth_addr(RLSW.framebuffer.depth, offset); \ + void *dptr = GET_DEPTH_PTR(RLSW.framebuffer.depth, offset); \ \ if (ENABLE_DEPTH_TEST) \ { \ @@ -3580,13 +3308,12 @@ static inline void FUNC_NAME(int x, int y, float z, const float color[4]) \ \ sw_framebuffer_write_depth(dptr, z); \ \ - void *cptr = sw_framebuffer_get_color_addr(RLSW.framebuffer.color, offset); \ + void *cptr = GET_COLOR_PTR(RLSW.framebuffer.color, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ - \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ @@ -3724,7 +3451,7 @@ static inline bool sw_is_texture_valid(uint32_t id) if (id == 0) valid = false; else if (id >= SW_MAX_TEXTURES) valid = false; - else if (RLSW.loadedTextures[id].pixels.cptr == 0) valid = false; + else if (RLSW.loadedTextures[id].pixels == NULL) valid = false; return true; } @@ -3872,26 +3599,24 @@ bool swInit(int w, int h) RLSW.polyMode = SW_FILL; RLSW.cullFace = SW_BACK; - static const float defTex[3*2*2] = { - 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, + static uint32_t defaultTex[3*2*2] = { + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xFFFFFFFF }; - RLSW.loadedTextures[0].pixels.cptr = defTex; + RLSW.loadedTextures[0].pixels = (uint8_t*)defaultTex; RLSW.loadedTextures[0].width = 2; RLSW.loadedTextures[0].height = 2; RLSW.loadedTextures[0].wMinus1 = 1; RLSW.loadedTextures[0].hMinus1 = 1; - RLSW.loadedTextures[0].format = SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32; RLSW.loadedTextures[0].minFilter = SW_NEAREST; RLSW.loadedTextures[0].magFilter = SW_NEAREST; RLSW.loadedTextures[0].sWrap = SW_REPEAT; RLSW.loadedTextures[0].tWrap = SW_REPEAT; RLSW.loadedTextures[0].tx = 0.5f; RLSW.loadedTextures[0].ty = 0.5f; - RLSW.loadedTextures[0].copy = false; RLSW.loadedTextureCount = 1; @@ -3900,10 +3625,13 @@ bool swInit(int w, int h) void swClose(void) { + // NOTE: Starts at texture 1, texture 0 does not have to be freed for (int i = 1; i < RLSW.loadedTextureCount; i++) { - sw_texture_t *texture = &RLSW.loadedTextures[i]; - if (sw_is_texture_valid(i) && texture->copy) SW_FREE(texture->pixels.ptr); + if (sw_is_texture_valid(i)) + { + SW_FREE(RLSW.loadedTextures[i].pixels); + } } SW_FREE(RLSW.framebuffer.color); @@ -3950,13 +3678,16 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_copy_to_R5G5B5A1(x, y, w, h, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_copy_to_R4G4B4A4(x, y, w, h, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, (uint8_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_copy_to_R32(x, y, w, h, (float *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_copy_to_R32G32B32(x, y, w, h, (float *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_copy_to_R32G32B32A32(x, y, w, h, (float *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_copy_to_R16(x, y, w, h, (sw_half_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_copy_to_R16G16B16(x, y, w, h, (sw_half_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_copy_to_R16G16B16A16(x, y, w, h, (sw_half_t *)pixels); break; - default: RLSW.errCode = SW_INVALID_ENUM; break; + // Below: not implemented + case SW_PIXELFORMAT_UNCOMPRESSED_R32: + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + case SW_PIXELFORMAT_UNCOMPRESSED_R16: + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + default: + RLSW.errCode = SW_INVALID_ENUM; + break; } } @@ -3991,13 +3722,16 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_blit_to_R5G5B5A1(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_blit_to_R4G4B4A4(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_blit_to_R8G8B8A8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_blit_to_R32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_blit_to_R32G32B32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (float *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_blit_to_R32G32B32A32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (float*)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_blit_to_R16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_blit_to_R16G16B16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_blit_to_R16G16B16A16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (sw_half_t *)pixels); break; - default: RLSW.errCode = SW_INVALID_ENUM; break; + // Below: not implemented + case SW_PIXELFORMAT_UNCOMPRESSED_R32: + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: + case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: + case SW_PIXELFORMAT_UNCOMPRESSED_R16: + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: + case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: + default: + RLSW.errCode = SW_INVALID_ENUM; + break; } } @@ -4814,17 +4548,14 @@ void swDeleteTextures(int count, uint32_t *textures) continue; } - if (RLSW.loadedTextures[textures[i]].copy) - { - SW_FREE(RLSW.loadedTextures[textures[i]].pixels.ptr); - } + SW_FREE(RLSW.loadedTextures[textures[i]].pixels); - RLSW.loadedTextures[textures[i]].pixels.cptr = NULL; + RLSW.loadedTextures[textures[i]].pixels = NULL; RLSW.freeTextureIds[RLSW.freeTextureIdCount++] = textures[i]; } } -void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy, const void *data) +void swTexImage2D(int width, int height, SWformat format, SWtype type, const void *data) { uint32_t id = RLSW.currentTexture; @@ -4844,33 +4575,27 @@ void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy sw_texture_t *texture = &RLSW.loadedTextures[id]; - if (copy) + int size = width*height; + texture->pixels = SW_MALLOC(4*size); + + if (texture->pixels == NULL) { - int bytes = sw_get_pixel_bytes((sw_pixelformat_t)pixelFormat); - int size = bytes*width*height; - texture->pixels.ptr = SW_MALLOC(size); - - if (texture->pixels.ptr == NULL) - { - RLSW.errCode = SW_STACK_OVERFLOW; // WARING: Out of memory... - return; - } - - for (int i = 0; i < size; i++) - { - ((uint8_t *)texture->pixels.ptr)[i] = ((uint8_t *)data)[i]; - } + RLSW.errCode = SW_STACK_OVERFLOW; // WARNING: Out of memory... + return; + } + + for (int i = 0; i < size; i++) + { + uint32_t *dst = &((uint32_t*)texture->pixels)[i]; + sw_get_pixel((uint8_t*)dst, data, i, pixelFormat); } - else texture->pixels.cptr = data; texture->width = width; texture->height = height; texture->wMinus1 = width - 1; texture->hMinus1 = height - 1; - texture->format = (sw_pixelformat_t)pixelFormat; texture->tx = 1.0f/width; texture->ty = 1.0f/height; - texture->copy = copy; } void swTexParameteri(int param, int value) @@ -4939,7 +4664,7 @@ void swBindTexture(uint32_t id) return; } - if (RLSW.loadedTextures[id].pixels.cptr == NULL) + if (RLSW.loadedTextures[id].pixels == NULL) { RLSW.errCode = SW_INVALID_OPERATION; return; From 4bb8c89084930862b70042845166739cf058e883 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sat, 25 Oct 2025 00:49:31 -0700 Subject: [PATCH 22/25] Don't undefine the versions of LoadImage that don't conflict with raylib so that the Win32 platform can use them. (#5299) --- src/platforms/rcore_desktop_win32.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 9b66f8c4b..5b67c72b0 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -49,8 +49,6 @@ #define CloseWindow CloseWindowWin32 #define Rectangle RectangleWin32 #define ShowCursor ShowCursorWin32 -#define LoadImageA LoadImageAWin32 -#define LoadImageW LoadImageWin32 #define DrawTextA DrawTextAWin32 #define DrawTextW DrawTextWin32 #define DrawTextExA DrawTextExAWin32 @@ -63,8 +61,6 @@ #undef Rectangle // raylib symbol collision #undef ShowCursor // raylib symbol collision #undef LoadImage // raylib symbol collision -#undef LoadImageA -#undef LoadImageW #undef DrawText // raylib symbol collision #undef DrawTextA #undef DrawTextW @@ -1529,7 +1525,7 @@ int InitPlatform(void) // Load user-provided icon if available // NOTE: raylib resource file defaults to GLFW_ICON id, so looking for same identifier windowClass.hIcon = LoadImageW(hInstance, L"GLFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); - if (!windowClass.hIcon) windowClass.hIcon = LoadImageW(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + if (!windowClass.hIcon) windowClass.hIcon = LoadImageW(NULL, (LPCWSTR)IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); // Register window class result = (int)RegisterClassExW(&windowClass); From dbcc5081964cb2413b32eecf2135d73e32420285 Mon Sep 17 00:00:00 2001 From: themushroompirates <59015901+themushroompirates@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:41:39 +0200 Subject: [PATCH 23/25] [examples] Added `models_decals` (#5298) * Added decal models example * Code formatting to match Raylib conventions --- examples/models/models_decals.c | 573 +++ examples/models/models_decals.png | Bin 0 -> 62387 bytes .../models/resources/models/obj/character.mtl | 12 + .../models/resources/models/obj/character.obj | 3454 +++++++++++++++++ .../models/obj/character_diffuse.png | Bin 0 -> 41705 bytes examples/models/resources/raylib_logo.png | Bin 0 -> 3672 bytes 6 files changed, 4039 insertions(+) create mode 100644 examples/models/models_decals.c create mode 100644 examples/models/models_decals.png create mode 100644 examples/models/resources/models/obj/character.mtl create mode 100644 examples/models/resources/models/obj/character.obj create mode 100644 examples/models/resources/models/obj/character_diffuse.png create mode 100644 examples/models/resources/raylib_logo.png diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c new file mode 100644 index 000000000..81bbd2796 --- /dev/null +++ b/examples/models/models_decals.c @@ -0,0 +1,573 @@ +/******************************************************************************************* +* +* raylib [models] example - decals +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 5.6-dev +* +* Example contributed by JP Mortiboys (@themushroompirates) and reviewed by Ramon Santamaria (@raysan5) +* Based on previous work by @mrdoob +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2025 JP Mortiboys (@themushroompirates) and Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" + +#include + +#undef FLT_MAX +#define FLT_MAX 340282346638528859811704183484516925440.0f // Maximum value of a float, from bit pattern 01111111011111111111111111111111 + +#define MAX_DECALS 256 + +typedef struct MeshBuilder { + int vertexCount; + int vertexCapacity; + Vector3 *vertices; + Vector2 *uvs; +} MeshBuilder; + +// Utility functions +void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]); +void FreeMeshBuilder(MeshBuilder *mb); +Mesh BuildMesh(MeshBuilder *mb); + +Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); + +Mesh GenMeshDecal(Mesh inputMesh, Ray ray); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "raylib [models] example - decals"); + + // Define the camera to look into our 3d world + Camera camera = { 0 }; + camera.position = (Vector3){ 5.0f, 5.0f, 5.0f }; // Camera position + camera.target = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera looking at point + camera.up = (Vector3){ 0.0f, 1.6f, 0.0f }; // Camera up vector (rotation towards target) + camera.fovy = 45.0f; // Camera field-of-view Y + camera.projection = CAMERA_PERSPECTIVE; // Camera projection type + + // Load character model + Model model = LoadModel("resources/models/obj/character.obj"); + + // Apply character skin + Texture2D modelTexture = LoadTexture("resources/models/obj/character_diffuse.png"); // Load model texture + SetTextureFilter(modelTexture, TEXTURE_FILTER_BILINEAR); + model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = modelTexture; // Set model diffuse texture + + + BoundingBox modelBBox = GetMeshBoundingBox(model.meshes[0]); // Get mesh bounding box + + camera.target = Vector3Lerp(modelBBox.min, modelBBox.max, 0.5f); + camera.position = Vector3Scale(modelBBox.max, 1.0f); + camera.position.x *= 0.1f; + + float modelSize = fminf( + fminf(fabsf(modelBBox.max.x - modelBBox.min.x), + fabsf(modelBBox.max.y - modelBBox.min.y)), + fabsf(modelBBox.max.z - modelBBox.min.z) + ); + + camera.position = (Vector3){ 0, modelBBox.max.y * 1.2f, modelSize * 3.0f }; + + float decalSize = modelSize * .25f; + float decalOffset = 0.01f; + + Model placementCube = LoadModelFromMesh(GenMeshCube(decalSize, decalSize, decalSize)); + placementCube.materials[0].maps[0].color = LIME; + + Material decalMaterial = LoadMaterialDefault(); + decalMaterial.maps[0].color = YELLOW; + + Image decalImage = LoadImage("resources/raylib_logo.png"); + ImageResizeNN(&decalImage, decalImage.width / 4, decalImage.height / 4); + Texture decalTexture = LoadTextureFromImage(decalImage); + UnloadImage(decalImage); + + SetTextureFilter(decalTexture, TEXTURE_FILTER_BILINEAR); + decalMaterial.maps[MATERIAL_MAP_DIFFUSE].texture = decalTexture; + decalMaterial.maps[MATERIAL_MAP_DIFFUSE].color = RAYWHITE; + + // We're going to use these to build up our decal meshes + // They'll resize automatically as we go, we'll free them at the end + MeshBuilder mesh_builders[2] = { {0}, {0} }; + + bool showModel = true; + Model decalModels[MAX_DECALS] = {0}; + int decalCount = 0; + + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + + if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { + UpdateCamera(&camera, CAMERA_THIRD_PERSON); + } + + if (IsKeyPressed(KEY_SPACE)) { + showModel = !showModel; + } + + // Display information about closest hit + RayCollision collision = { 0 }; + collision.distance = FLT_MAX; + collision.hit = false; + + // Get mouse ray + Ray ray = GetScreenToWorldRay(GetMousePosition(), camera); + + // Check ray collision against bounding box first, before trying the full ray-mesh test + RayCollision boxHitInfo = GetRayCollisionBox(ray, modelBBox); + + if ((boxHitInfo.hit) && decalCount < MAX_DECALS) + { + // Check ray collision against model meshes + RayCollision meshHitInfo = { 0 }; + for (int m = 0; m < model.meshCount; m++) + { + // NOTE: We consider the model.transform for the collision check but + // it can be checked against any transform Matrix, used when checking against same + // model drawn multiple times with multiple transforms + meshHitInfo = GetRayCollisionMesh(ray, model.meshes[m], model.transform); + if (meshHitInfo.hit) + { + // Save the closest hit mesh + if ((!collision.hit) || (collision.distance > meshHitInfo.distance)) collision = meshHitInfo; + } + } + + if (meshHitInfo.hit) + { + collision = meshHitInfo; + } + } + + // -------------- + + if (collision.hit && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && decalCount < MAX_DECALS) + { + + // Create the transformation to project the decal + Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); + Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); + + // Spin the placement around a bit + splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD * ((float)GetRandomValue(-180, 180)))); + + Matrix splatInv = MatrixInvert(splat); + + // Reset the mesh builders + mesh_builders[0].vertexCount = 0; + mesh_builders[1].vertexCount = 0; + + // We'll be flip-flopping between the two mesh builders + // Reading from one and writing to the other, then swapping + int mb_index = 0; + + // First pass, just get any triangle inside the bounding box + // (for each mesh of the model) + for (int meshIndex = 0; meshIndex < model.meshCount; meshIndex++) { + Mesh mesh = model.meshes[meshIndex]; + for (int tri = 0; tri < mesh.triangleCount; tri++) + { + Vector3 vertices[3]; + + // The way we calculate the vertices of the mesh triangle + // depend on whether the mesh vertices are indexed or not + if (mesh.indices == 0) + { + for (int v = 0; v < 3; v++) { + vertices[v] = (Vector3) { + mesh.vertices[3*3*tri + 3*v + 0], + mesh.vertices[3*3*tri + 3*v + 1], + mesh.vertices[3*3*tri + 3*v + 2] + }; + } + } + else + { + for (int v = 0; v < 3; v++) + { + vertices[v] = (Vector3) { + mesh.vertices[ 3*mesh.indices[3*tri+0] + v], + mesh.vertices[ 3*mesh.indices[3*tri+1] + v], + mesh.vertices[ 3*mesh.indices[3*tri+2] + v] + }; + } + } + + // Transform all 3 vertices of the triangle + // and check if they are inside our decal box + int insideCount = 0; + for (int i = 0; i < 3; i++) + { + // To splat space + Vector3 v = Vector3Transform(vertices[i], splat); + + if (fabsf(v.x) 0) + { + AddTriangleToMeshBuilder(&mesh_builders[mb_index], vertices); + } + } + } + + // Clipping time! We need to clip against all 6 directions + Vector3 planes[6] = { + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 } + }; + for (int face = 0; face < 6; face++) + { + // Swap current model builder (so we read from the one we just wrote to) + mb_index = 1 - mb_index; + + MeshBuilder *inMesh = &mesh_builders[1-mb_index]; + MeshBuilder *outMesh = &mesh_builders[mb_index]; + + // Reset write builder + outMesh->vertexCount = 0; + + float s = 0.5f * decalSize; + + for (int i = 0; i < inMesh->vertexCount; i+=3) + { + Vector3 nV1, nV2, nV3, nV4; + + float d1 = Vector3DotProduct(inMesh->vertices[ i + 0 ], planes[face] ) - s; + float d2 = Vector3DotProduct(inMesh->vertices[ i + 1 ], planes[face] ) - s; + float d3 = Vector3DotProduct(inMesh->vertices[ i + 2 ], planes[face] ) - s; + + int v1Out = d1 > 0; + int v2Out = d2 > 0; + int v3Out = d3 > 0; + + // calculate, how many vertices of the face lie outside of the clipping plane + int total = v1Out + v2Out + v3Out; + + switch ( total ) { + case 0: { + // the entire face lies inside of the plane, no clipping needed + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){inMesh->vertices[i], inMesh->vertices[i+1], inMesh->vertices[i+2]}); + break; + } + + case 1: { + // one vertex lies outside of the plane, perform clipping + + if (v1Out) { + nV1 = inMesh->vertices[ i + 1 ]; + nV2 = inMesh->vertices[ i + 2 ]; + nV3 = ClipSegment( inMesh->vertices[ i ], nV1, planes[face], s ); + nV4 = ClipSegment( inMesh->vertices[ i ], nV2, planes[face], s ); + } + + if (v2Out) { + nV1 = inMesh->vertices[ i ]; + nV2 = inMesh->vertices[ i + 2 ]; + nV3 = ClipSegment( inMesh->vertices[ i + 1 ], nV1, planes[face], s ); + nV4 = ClipSegment( inMesh->vertices[ i + 1 ], nV2, planes[face], s ); + + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV3, nV2, nV1}); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV2, nV3, nV4}); + break; + } + + if (v3Out) { + nV1 = inMesh->vertices[ i ]; + nV2 = inMesh->vertices[ i + 1 ]; + nV3 = ClipSegment( inMesh->vertices[ i + 2 ], nV1, planes[face], s ); + nV4 = ClipSegment( inMesh->vertices[ i + 2 ], nV2, planes[face], s ); + } + + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2}); + + break; + } + + case 2: { + // two vertices lies outside of the plane, perform clipping + if (!v1Out) { + nV1 = inMesh->vertices[ i ]; + nV2 = ClipSegment( nV1, inMesh->vertices[ i + 1 ], planes[face], s ); + nV3 = ClipSegment( nV1, inMesh->vertices[ i + 2 ], planes[face], s ); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + if (!v2Out) { + nV1 = inMesh->vertices[ i + 1 ]; + nV2 = ClipSegment( nV1, inMesh->vertices[ i + 2 ], planes[face], s ); + nV3 = ClipSegment( nV1, inMesh->vertices[ i ], planes[face], s ); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + if (!v3Out) { + nV1 = inMesh->vertices[ i + 2 ]; + nV2 = ClipSegment( nV1, inMesh->vertices[ i ], planes[face], s ); + nV3 = ClipSegment( nV1, inMesh->vertices[ i + 1 ], planes[face], s ); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + break; + } + + case 3: { + // the entire face lies outside of the plane, so let's discard the corresponding vertices + break; + } + + } + } + } + + // Now we just need to re-transform the vertices + MeshBuilder *theMesh = &mesh_builders[mb_index]; + + // Allocate room for UVs + if (theMesh->vertexCount > 0) + { + theMesh->uvs = (Vector2*)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); + for (int i = 0; i < theMesh->vertexCount; i++) + { + + // Calculate the UVs based on the projected coords + // They are clipped to (-decalSize .. decalSize) and we want them (0..1) + theMesh->uvs[i].x = (theMesh->vertices[i].x / decalSize + .5f); + theMesh->uvs[i].y = (theMesh->vertices[i].y / decalSize + .5f); + + // From splat space to world space + theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], splatInv); + + // Tiny nudge in the normal direction so it renders properly over the mesh + theMesh->vertices[i] = Vector3Add(theMesh->vertices[i], Vector3Scale(collision.normal, decalOffset)); + } + + // Decal model data ready, create it and add it + int decalIndex = decalCount++; + decalModels[decalIndex] = LoadModelFromMesh(BuildMesh(theMesh)); + decalModels[decalIndex].materials[0] = decalMaterial; + } + + } + + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + // Draw the model at the origin and default scale + if (showModel) + { + DrawModel(model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); + } + + // Draw the decal models + for (int i = 0; i < decalCount; i++) + { + DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); + } + + // If we hit the mesh, draw the box for the decal + if (collision.hit) + { + + Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); + Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); + + placementCube.transform = MatrixInvert(splat); + DrawModel(placementCube, (Vector3){0}, 1.0f, Fade(WHITE, 0.5f)); + } + + DrawGrid(10, 10.0f); + + EndMode3D(); + + { + float yPos = 10; + float x0 = GetScreenWidth() - 300; + float x1 = x0 + 100; + float x2 = x1 + 100; + + DrawText("Vertices", x1, yPos, 10, LIME); + DrawText("Triangles", x2, yPos, 10, LIME); + yPos += 15; + + int vertexCount = 0; + int triangleCount = 0; + + for (int i = 0; i < model.meshCount; i++) + { + vertexCount += model.meshes[i].vertexCount; + triangleCount += model.meshes[i].triangleCount; + } + + DrawText("Main model", x0, yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + yPos += 15; + + for (int i = 0; i < decalCount; i++) + { + DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); + + vertexCount += decalModels[i].meshes[0].vertexCount; + triangleCount += decalModels[i].meshes[0].triangleCount; + yPos += 15; + } + + DrawText("TOTAL", x0, yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + yPos += 15; + } + + DrawText("Hold RMB to move camera", 10, 430, 10, GRAY); + + DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY); + + DrawFPS(10, 10); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadModel(model); + UnloadTexture(modelTexture); + + for (int i = 0; i < decalCount; i++) + { + UnloadModel(decalModels[i]); + } + + UnloadTexture(decalTexture); + + FreeMeshBuilder(&mesh_builders[0]); + FreeMeshBuilder(&mesh_builders[1]); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +// A really simple dynamic array manager +void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) { + // Reallocate and copy if we need to + if (mb->vertexCapacity <= mb->vertexCount + 3 ) + { + int newVertexCapacity = (1 + (mb->vertexCapacity / 256)) * 256; + Vector3 *newVertices = (Vector3*)MemAlloc(newVertexCapacity * sizeof(Vector3)); + if (mb->vertexCapacity > 0) + { + memcpy(newVertices, mb->vertices, mb->vertexCount * sizeof(Vector3)); + MemFree(mb->vertices); + } + mb->vertices = newVertices; + mb->vertexCapacity = newVertexCapacity; + } + + // Add 3 vertices + int index = mb->vertexCount; + mb->vertexCount += 3; + + for (int i = 0; i < 3; i++) + { + mb->vertices[index+i] = vertices[i]; + } +} + +void FreeMeshBuilder(MeshBuilder *mb) +{ + MemFree(mb->vertices); + + if (mb->uvs) + { + MemFree(mb->uvs); + } + + *mb = (MeshBuilder){ 0 }; +} + +// Construct a Raylib Mesh from our MeshBuilder data +Mesh BuildMesh(MeshBuilder *mb) +{ + Mesh outMesh = { 0 }; + + outMesh.vertexCount = mb->vertexCount; + outMesh.triangleCount = mb->vertexCount / 3; + + outMesh.vertices = MemAlloc(outMesh.vertexCount * 3 * sizeof(float)); + + if (mb->uvs) + { + outMesh.texcoords = MemAlloc(outMesh.vertexCount * 2 * sizeof(float)); + } + + for (int i = 0; i < mb->vertexCount; i++) + { + outMesh.vertices[3*i+0] = mb->vertices[i].x; + outMesh.vertices[3*i+1] = mb->vertices[i].y; + outMesh.vertices[3*i+2] = mb->vertices[i].z; + if (mb->uvs) + { + outMesh.texcoords[2*i+0] = mb->uvs[i].x; + outMesh.texcoords[2*i+1] = mb->uvs[i].y; + } + } + + UploadMesh(&outMesh, false); + + return outMesh; +} + +Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) +{ + float d0 = Vector3DotProduct(v0, p) - s; + float d1 = Vector3DotProduct(v1, p) - s; + + float s0 = d0 / ( d0 - d1 ); + + Vector3 position = Vector3Lerp(v0, v1, s0); + + return position; +} \ No newline at end of file diff --git a/examples/models/models_decals.png b/examples/models/models_decals.png new file mode 100644 index 0000000000000000000000000000000000000000..48e027fe2d1711237f457995d46ddcc0647a102e GIT binary patch literal 62387 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 Ad;kCd literal 0 HcmV?d00001 diff --git a/examples/models/resources/models/obj/character.mtl b/examples/models/resources/models/obj/character.mtl new file mode 100644 index 000000000..96b272b29 --- /dev/null +++ b/examples/models/resources/models/obj/character.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl skin +Ns 86.470579 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 0.000000 +illum 9 diff --git a/examples/models/resources/models/obj/character.obj b/examples/models/resources/models/obj/character.obj new file mode 100644 index 000000000..be7e47863 --- /dev/null +++ b/examples/models/resources/models/obj/character.obj @@ -0,0 +1,3454 @@ +# Blender v2.83.2 OBJ File: '' +# www.blender.org +mtllib character.mtl +o characterMedium +v 0.410751 3.453012 0.200817 +v 0.176931 3.516046 -0.492870 +v 0.176931 3.069885 -0.492870 +v 0.193767 2.961166 0.489574 +v 0.193767 3.452389 0.455386 +v 0.400606 2.998632 0.217911 +v 0.400606 3.035397 -0.212411 +v 0.410751 3.483905 -0.212411 +v 0.188780 3.698580 0.247489 +v 0.188780 3.731344 -0.224509 +v 0.188780 2.741884 -0.224509 +v 0.188780 2.664058 0.281677 +v 0.338542 2.788810 0.205190 +v 0.323730 3.496119 -0.410188 +v 0.323730 3.441588 0.352035 +v 0.338542 3.642598 0.223085 +v 0.338542 2.852551 -0.208748 +v 0.176295 2.876968 -0.427238 +v 0.323730 3.072495 -0.410188 +v 0.193131 2.735571 0.459728 +v 0.323730 2.996823 0.381949 +v 0.338542 3.655763 -0.208748 +v 0.176295 3.660861 -0.399904 +v 0.193131 3.617279 0.405342 +v 0.180529 3.275434 -0.517282 +v 0.197366 3.218354 0.478425 +v 0.409350 3.230555 0.194491 +v 0.403118 3.253443 -0.219099 +v 0.429171 3.009897 0.014208 +v 0.439316 3.472003 0.009934 +v 0.198303 3.743473 0.020506 +v 0.198303 2.669343 0.029053 +v 0.286423 2.928726 -0.357824 +v 0.286423 2.767836 0.367046 +v 0.286423 3.636701 -0.357824 +v 0.301893 3.594941 0.358830 +v 0.357587 2.802252 0.025342 +v 0.322536 3.266541 -0.423686 +v 0.327481 3.227505 0.360122 +v 0.357587 3.654801 0.017864 +v 0.434369 3.238045 0.005717 +v 0.111250 2.648599 -0.113100 +v 0.129723 2.623841 0.000772 +v 0.110155 2.617475 0.111510 +v 0.098894 2.551101 -0.119944 +v 0.140309 2.525439 -0.009396 +v 0.098894 2.517193 0.100599 +v 0.208678 2.515187 -0.122760 +v 0.245812 2.502890 -0.002447 +v 0.208678 2.483677 0.109699 +v 0.271570 2.206755 -0.145013 +v 0.319908 2.187368 0.032075 +v 0.271570 2.177688 0.191096 +v 0.303127 1.441376 -0.106186 +v 0.355115 1.410218 0.080975 +v 0.289850 1.402806 0.240428 +v 0.260948 1.763152 0.232137 +v 0.307393 1.770383 0.075582 +v 0.260948 1.788246 -0.089088 +v 0.247213 2.324639 0.153241 +v 0.247213 2.354879 -0.163591 +v 0.115069 1.290704 0.269082 +v 0.017798 1.168477 -0.035384 +v 0.183067 1.284675 -0.183271 +v 0.084912 3.522239 -0.501146 +v 0.084912 3.064342 -0.512888 +v 0.084912 2.943401 0.510254 +v 0.084912 3.458095 0.476066 +v 0.090590 3.723567 0.254122 +v 0.090590 3.746935 -0.231142 +v 0.090590 2.722769 -0.231142 +v 0.090590 2.643034 0.288310 +v 0.084607 2.864935 -0.440534 +v 0.084607 2.702693 0.473189 +v 0.084607 3.673055 -0.413200 +v 0.084607 3.629352 0.421907 +v 0.086637 3.278739 -0.526585 +v 0.086636 3.215177 0.507309 +v 0.095154 3.754534 0.020506 +v 0.060191 2.658972 -0.154656 +v 0.053444 2.613782 0.136010 +v 0.047516 2.554504 -0.139039 +v 0.047516 2.514067 0.124693 +v 0.100126 2.519134 -0.142488 +v 0.100126 2.479805 0.136555 +v 0.130264 2.209162 -0.175208 +v 0.130264 2.175989 0.226360 +v 0.145386 1.454041 -0.168399 +v 0.145386 1.399946 0.290329 +v 0.125174 1.790136 -0.123980 +v 0.125174 1.762047 0.266843 +v 0.118592 2.321896 0.187758 +v 0.118592 2.358026 -0.194066 +v 0.064118 0.867640 -0.065778 +v 0.139275 0.844466 0.140266 +v 0.257486 0.890374 -0.145882 +v 0.359097 0.900878 0.061199 +v 0.074195 0.752219 -0.105874 +v 0.147259 0.698792 0.087905 +v 0.263534 0.755735 -0.191206 +v 0.364126 0.730329 0.010206 +v 0.092608 0.518587 -0.156357 +v 0.161208 0.488124 0.011298 +v 0.257654 0.503497 -0.235568 +v 0.342662 0.497755 -0.059963 +v 0.100936 1.052740 0.183646 +v 0.362186 1.124208 0.082677 +v 0.035605 1.007707 -0.051248 +v 0.244578 1.066754 -0.153714 +v 0.055029 1.214705 -0.163442 +v 0.255589 1.328226 0.210676 +v 0.140172 0.882954 -0.138230 +v 0.262287 0.869133 0.135226 +v 0.152224 0.756748 -0.186384 +v 0.268705 0.704238 0.080827 +v 0.163237 0.508650 -0.221611 +v 0.266438 0.484781 0.003128 +v 0.113057 1.038591 -0.152600 +v 0.246990 1.089520 0.173845 +v 0.054756 1.198160 0.101170 +v 0.310361 1.325851 -0.067512 +v 0.060255 0.853278 0.049460 +v 0.343613 0.899484 -0.056313 +v 0.071380 0.718974 0.002418 +v 0.350589 0.741909 -0.110914 +v 0.091684 0.503566 -0.057160 +v 0.339310 0.499423 -0.163148 +v 0.344611 1.099522 -0.066307 +v 0.028575 1.027615 0.082498 +v 0.455190 2.450647 -0.082374 +v 0.462809 2.465601 0.006612 +v 0.448972 2.441031 0.081440 +v 0.434408 2.252116 -0.088512 +v 0.448014 2.213438 0.029647 +v 0.428671 2.233589 0.135751 +v 0.442147 2.332059 0.110493 +v 1.328482 2.369486 -0.025838 +v 0.448115 2.351334 -0.094647 +v 0.717408 2.422497 -0.064617 +v 0.727228 2.428632 0.006928 +v 0.711866 2.404597 0.073618 +v 0.698886 2.236229 -0.077850 +v 0.706620 2.199933 0.027458 +v 0.693774 2.219717 0.102536 +v 0.705784 2.307478 0.109237 +v 0.945792 2.289033 0.134562 +v 0.711103 2.324656 -0.097021 +v 1.329792 2.357378 0.034079 +v 1.323088 2.354750 0.089928 +v 1.305094 2.214479 -0.036920 +v 1.300746 2.180356 0.051271 +v 1.300117 2.200886 0.110979 +v 1.313884 2.273778 0.121339 +v 1.509906 2.320435 -0.046242 +v 1.319060 2.287920 -0.055202 +v 0.930288 2.200850 0.122871 +v 0.936312 2.211617 -0.068478 +v 0.952059 2.307598 -0.088140 +v 0.936310 2.172915 0.046302 +v 0.968273 2.418404 0.023926 +v 0.961626 2.413997 -0.054054 +v 0.955096 2.394653 0.096614 +v 0.330881 2.488142 0.003375 +v 0.305188 2.468017 0.099597 +v 0.315496 2.226114 -0.122984 +v 0.320825 2.354873 -0.139518 +v 0.303439 2.230228 0.158131 +v 0.309166 2.327404 0.136383 +v 0.348316 2.192029 0.031171 +v 0.315498 2.485255 -0.117814 +v 1.401711 2.330388 -0.053750 +v 1.402313 2.325266 0.037149 +v 1.368471 2.326110 0.116069 +v 1.380995 2.227921 -0.070645 +v 1.378716 2.209659 0.062973 +v 1.340428 2.225900 0.146512 +v 1.351661 2.274332 0.162481 +v 1.795120 2.206148 -0.017422 +v 1.399326 2.275373 -0.096810 +v 1.525167 2.312472 0.043161 +v 1.504512 2.310743 0.126497 +v 1.486518 2.218486 -0.062778 +v 1.505804 2.196043 0.068815 +v 1.481542 2.209545 0.157907 +v 1.495307 2.257487 0.173365 +v 1.626749 2.173789 0.059473 +v 1.500484 2.266789 -0.090058 +v 1.809535 2.198612 0.054630 +v 1.788894 2.200041 0.121791 +v 1.762707 2.137646 -0.030749 +v 1.779807 2.119442 0.075304 +v 1.756964 2.132012 0.147105 +v 1.774950 2.163872 0.159563 +v 1.797727 2.160531 0.057501 +v 1.780926 2.169733 -0.052734 +v 1.607462 2.193023 -0.059653 +v 1.616252 2.226447 0.154119 +v 1.602486 2.185360 0.140125 +v 1.646111 2.273569 0.036250 +v 1.630851 2.280393 -0.044683 +v 1.621429 2.234418 -0.084348 +v 1.625457 2.272087 0.111690 +v 1.379530 2.252804 0.224500 +v 1.371148 2.204101 0.211689 +v 1.461364 2.245535 0.213279 +v 1.452982 2.196833 0.200468 +v 1.424118 2.225873 0.279904 +v 1.419068 2.175998 0.270241 +v 1.481443 2.235304 0.220739 +v 1.476394 2.185430 0.211076 +v 1.509712 2.198027 0.341520 +v 1.506171 2.163050 0.334743 +v 1.549915 2.204642 0.300027 +v 1.546374 2.169665 0.293251 +v 0.262061 0.256721 -0.179629 +v 0.125851 0.263241 -0.073885 +v 0.264548 0.252916 -0.065338 +v 0.141817 0.268149 -0.192735 +v 0.270091 0.253667 -0.122910 +v 0.125675 0.264206 -0.137064 +v 0.206420 0.264077 -0.203655 +v 0.230977 0.000692 0.289805 +v 0.342493 0.000690 0.270761 +v 0.163985 0.000688 -0.230879 +v 0.270393 0.000686 -0.245194 +v 0.144177 0.000691 0.033839 +v 0.288000 0.000691 0.291562 +v 0.372899 0.000687 -0.002369 +v 0.227192 0.000686 -0.254522 +v 0.257567 0.000689 0.016768 +v 0.137243 0.000692 0.170051 +v 0.391563 0.000688 0.127957 +v 0.273728 0.000690 0.160912 +v 0.338169 0.000686 -0.146682 +v 0.108448 0.000690 -0.113832 +v 0.239700 0.000687 -0.142594 +v 0.167568 0.000692 0.233611 +v 0.383765 0.000689 0.197667 +v 0.281809 0.000691 0.232984 +v 0.309290 0.000686 -0.218277 +v 0.229983 0.000687 -0.223266 +v 0.118360 0.000689 -0.190507 +v 0.130135 0.000690 -0.034828 +v 0.349068 0.000687 -0.064281 +v 0.249487 0.000688 -0.055304 +v 0.387775 0.000687 0.061323 +v 0.265648 0.000689 0.088840 +v 0.129773 0.000692 0.103416 +v 0.232718 0.085533 0.288274 +v 0.363576 0.085530 0.268239 +v 0.154897 0.085528 -0.243375 +v 0.260403 0.080414 -0.243911 +v 0.128135 0.085532 0.035193 +v 0.299506 0.085532 0.289325 +v 0.383870 0.085527 -0.003576 +v 0.214830 0.085527 -0.261124 +v 0.132008 0.085533 0.181044 +v 0.405763 0.085528 0.135972 +v 0.339530 0.085526 -0.157915 +v 0.096516 0.085530 -0.123177 +v 0.167580 0.085533 0.249102 +v 0.395495 0.085529 0.210885 +v 0.304116 0.077122 -0.204676 +v 0.110617 0.085530 -0.206279 +v 0.114426 0.085531 -0.038332 +v 0.357741 0.085527 -0.069335 +v 0.401320 0.085528 0.064622 +v 0.123244 0.085533 0.109694 +v 0.235360 0.121957 0.282892 +v 0.342479 0.121955 0.271351 +v 0.155498 0.140203 -0.234632 +v 0.258674 0.141703 -0.233045 +v 0.147943 0.140206 0.045064 +v 0.367478 0.140202 0.015099 +v 0.209678 0.140554 -0.248081 +v 0.151504 0.140207 0.167879 +v 0.387046 0.140203 0.131704 +v 0.326827 0.140201 -0.142554 +v 0.113117 0.142231 -0.125673 +v 0.174977 0.140207 0.230696 +v 0.378520 0.140204 0.201276 +v 0.291506 0.139635 -0.192779 +v 0.127237 0.141691 -0.187001 +v 0.134396 0.140206 -0.008056 +v 0.354118 0.140202 -0.035038 +v 0.382905 0.140203 0.065200 +v 0.143705 0.140207 0.101330 +v 0.244012 0.174412 0.250246 +v 0.312400 0.174411 0.237719 +v 0.154929 0.197920 -0.224667 +v 0.244653 0.200889 -0.226557 +v 0.200247 0.182648 0.039626 +v 0.320975 0.193251 0.026737 +v 0.200815 0.200081 -0.230324 +v 0.176892 0.181218 0.144060 +v 0.349336 0.193278 0.128903 +v 0.299466 0.209667 -0.141685 +v 0.117610 0.203001 -0.122411 +v 0.197637 0.187029 0.207154 +v 0.340156 0.188834 0.188204 +v 0.275703 0.199069 -0.189682 +v 0.130048 0.198871 -0.180965 +v 0.183002 0.199344 -0.013043 +v 0.295575 0.215862 -0.011766 +v 0.341167 0.191744 0.072780 +v 0.191423 0.184576 0.092515 +v 0.338887 0.000686 -0.105818 +v 0.244594 0.000688 -0.098949 +v 0.116803 0.000690 -0.073995 +v 0.348659 0.085527 -0.113812 +v 0.102306 0.085531 -0.080271 +v 0.339594 0.140202 -0.089574 +v 0.118165 0.140205 -0.060612 +v 0.304014 0.215861 -0.085942 +v 0.129758 0.215864 -0.062783 +v 0.261646 0.242079 -0.188306 +v 0.230162 0.234212 -0.224732 +v 0.193922 0.242264 -0.221201 +v 0.165941 0.245963 -0.208302 +v 0.280499 0.229314 -0.077601 +v 0.279868 0.243470 -0.137553 +v 0.127675 0.236447 -0.179834 +v 0.120948 0.240070 -0.122668 +v 0.128658 0.229317 -0.058955 +v 0.189612 0.231437 -0.029962 +v 0.259835 0.229315 -0.044259 +v 0.190037 0.256893 -0.038329 +v -0.410268 3.453012 0.200817 +v -0.176448 3.516046 -0.492870 +v -0.176448 3.069885 -0.492870 +v -0.193285 2.961166 0.489574 +v -0.193285 3.452389 0.455386 +v -0.400123 2.998632 0.217911 +v -0.400123 3.035397 -0.212411 +v -0.410268 3.483905 -0.212411 +v -0.188297 3.698580 0.247489 +v -0.188298 3.731344 -0.224509 +v -0.188298 2.741884 -0.224509 +v -0.188298 2.664058 0.281677 +v -0.338060 2.788810 0.205190 +v -0.323248 3.496119 -0.410188 +v -0.323248 3.441588 0.352035 +v -0.338059 3.642598 0.223085 +v -0.338060 2.852551 -0.208748 +v -0.175812 2.876968 -0.427238 +v -0.323248 3.072495 -0.410188 +v -0.192649 2.735571 0.459728 +v -0.323248 2.996823 0.381949 +v -0.338060 3.655763 -0.208748 +v -0.175812 3.660861 -0.399904 +v -0.192648 3.617279 0.405342 +v -0.180047 3.275434 -0.517282 +v 0.000241 3.527937 -0.505363 +v 0.000241 3.059241 -0.517105 +v -0.196883 3.218354 0.478425 +v 0.000241 2.927055 0.514236 +v 0.000241 3.457695 0.495095 +v -0.408868 3.230555 0.194491 +v -0.402636 3.253443 -0.219099 +v -0.428688 3.009897 0.014208 +v -0.438833 3.472003 0.009934 +v 0.000241 3.735282 0.260225 +v 0.000241 3.756304 -0.237245 +v -0.197821 3.743473 0.020506 +v -0.197821 2.669343 0.029053 +v 0.000241 2.716923 -0.237245 +v 0.000241 2.623689 0.294413 +v -0.285941 2.928726 -0.357824 +v -0.285941 2.767836 0.367046 +v -0.285941 3.636701 -0.357824 +v -0.301410 3.594941 0.358830 +v -0.357105 2.802252 0.025342 +v 0.000241 2.853864 -0.452768 +v -0.322054 3.266541 -0.423686 +v 0.000241 2.695371 0.485576 +v -0.326999 3.227505 0.360122 +v -0.357105 3.654801 0.017864 +v 0.000241 3.684277 -0.419779 +v 0.000241 3.640461 0.434294 +v 0.000241 3.279187 -0.533908 +v 0.000241 3.212254 0.507793 +v -0.433886 3.238045 0.005717 +v 0.000241 3.765371 0.020506 +v 0.000241 2.608866 0.157665 +v 0.000241 2.662452 -0.152277 +v -0.110768 2.648599 -0.113100 +v -0.129241 2.623841 0.000772 +v -0.109673 2.617475 0.111510 +v 0.000241 2.511192 0.146864 +v 0.000241 2.557636 -0.161210 +v -0.098412 2.551101 -0.119944 +v -0.139827 2.525439 -0.009396 +v -0.098412 2.517193 0.100599 +v 0.000241 2.476242 0.161267 +v 0.000241 2.522766 -0.165241 +v -0.208196 2.515187 -0.122760 +v -0.245330 2.502890 -0.002447 +v -0.208196 2.483677 0.109699 +v 0.000241 2.174426 0.258809 +v 0.000241 2.211377 -0.202993 +v -0.271088 2.206755 -0.145013 +v -0.319426 2.187368 0.032075 +v -0.271088 2.177688 0.191096 +v 0.000241 1.398135 0.309651 +v 0.000241 1.463624 -0.206620 +v -0.302644 1.441376 -0.106186 +v -0.354291 1.410232 0.081480 +v -0.288561 1.402802 0.240545 +v 0.000241 1.761031 0.298778 +v -0.260465 1.763152 0.232137 +v -0.306911 1.770383 0.075582 +v -0.260465 1.788246 -0.089088 +v 0.000241 1.791875 -0.156086 +v -0.246731 2.324639 0.153241 +v -0.246731 2.354880 -0.163592 +v 0.000241 2.360921 -0.206229 +v 0.000241 2.319373 0.219519 +v -0.113364 1.290662 0.268232 +v -0.018429 1.168483 -0.035181 +v -0.184480 1.284700 -0.183080 +v -0.000316 1.168480 -0.035282 +v -0.084430 3.522239 -0.501146 +v -0.084430 3.064342 -0.512888 +v -0.084430 2.943401 0.510254 +v -0.084429 3.458095 0.476066 +v -0.090108 3.723567 0.254122 +v -0.090108 3.746935 -0.231142 +v -0.090108 2.722769 -0.231142 +v -0.090108 2.643034 0.288310 +v -0.084125 2.864935 -0.440534 +v -0.084125 2.702693 0.473189 +v -0.084125 3.673055 -0.413200 +v -0.084125 3.629352 0.421907 +v -0.086154 3.278739 -0.526585 +v -0.086154 3.215177 0.507309 +v -0.094671 3.754534 0.020506 +v -0.059709 2.658972 -0.154656 +v -0.052962 2.613782 0.136010 +v -0.047034 2.554504 -0.139039 +v -0.047034 2.514068 0.124693 +v -0.099643 2.519134 -0.142488 +v -0.099643 2.479805 0.136555 +v -0.129782 2.209162 -0.175208 +v -0.129782 2.175989 0.226360 +v -0.144904 1.454041 -0.168399 +v -0.144904 1.399946 0.290329 +v -0.124691 1.790136 -0.123980 +v -0.124691 1.762047 0.266843 +v -0.118110 2.321897 0.187758 +v -0.118110 2.358026 -0.194066 +v 0.000241 1.284807 0.281643 +v 0.000241 1.284807 -0.179892 +v -0.065606 0.867575 -0.068223 +v -0.137184 0.844409 0.139088 +v -0.260316 0.890440 -0.144957 +v -0.358304 0.900967 0.063854 +v -0.076447 0.752149 -0.108154 +v -0.146162 0.698757 0.086858 +v -0.267231 0.755783 -0.190189 +v -0.364320 0.730428 0.012933 +v -0.095873 0.518518 -0.158302 +v -0.161565 0.488099 0.010515 +v -0.262272 0.503526 -0.234639 +v -0.344217 0.497838 -0.057588 +v -0.097973 1.052652 0.181831 +v -0.360885 1.124295 0.085420 +v -0.036763 1.007622 -0.054166 +v -0.247441 1.066813 -0.152981 +v -0.057083 1.214724 -0.162813 +v -0.253453 1.328216 0.210857 +v -0.001001 1.201565 -0.157958 +v -0.142896 0.882948 -0.139342 +v -0.260245 0.869150 0.136185 +v -0.155858 0.756730 -0.187298 +v -0.267706 0.704276 0.081887 +v -0.167628 0.508623 -0.222321 +v -0.266919 0.484820 0.004171 +v -0.115942 1.038571 -0.154153 +v -0.244148 1.089522 0.174570 +v -0.053830 1.198178 0.101779 +v -0.310544 1.325896 -0.066387 +v 0.000415 1.184651 0.093554 +v -0.059751 0.853190 0.046927 +v -0.344866 0.899585 -0.053907 +v -0.071771 0.718897 0.000070 +v -0.352883 0.742005 -0.108402 +v -0.093234 0.503499 -0.059137 +v -0.342657 0.499502 -0.160814 +v -0.345917 1.099626 -0.063848 +v -0.027398 1.027503 0.079441 +v -0.454708 2.450647 -0.082374 +v -0.462326 2.465601 0.006612 +v -0.448490 2.441031 0.081440 +v -0.433925 2.252116 -0.088512 +v -0.447532 2.213438 0.029647 +v -0.428189 2.233589 0.135751 +v -0.441665 2.332059 0.110493 +v -1.328000 2.369487 -0.025839 +v -0.447632 2.351334 -0.094647 +v -0.716926 2.422497 -0.064618 +v -0.726745 2.428632 0.006928 +v -0.711384 2.404597 0.073617 +v -0.698403 2.236229 -0.077851 +v -0.706138 2.199933 0.027457 +v -0.693291 2.219717 0.102535 +v -0.705302 2.307478 0.109236 +v -0.945310 2.289035 0.134561 +v -0.710620 2.324656 -0.097021 +v -1.329309 2.357379 0.034077 +v -1.322606 2.354751 0.089927 +v -1.304611 2.214480 -0.036921 +v -1.300263 2.180357 0.051270 +v -1.299635 2.200886 0.110977 +v -1.313401 2.273779 0.121337 +v -1.509424 2.320436 -0.046244 +v -1.318578 2.287921 -0.055203 +v -0.929806 2.200852 0.122871 +v -0.935830 2.211618 -0.068479 +v -0.951576 2.307599 -0.088141 +v -0.935828 2.172916 0.046301 +v -0.967791 2.418405 0.023925 +v -0.961144 2.413998 -0.054055 +v -0.954614 2.394654 0.096613 +v -0.330399 2.488142 0.003375 +v -0.304706 2.468017 0.099597 +v -0.315013 2.226115 -0.122984 +v -0.320343 2.354873 -0.139518 +v -0.302957 2.230229 0.158131 +v -0.308683 2.327404 0.136383 +v -0.347834 2.192029 0.031171 +v -0.315016 2.485255 -0.117814 +v -1.401229 2.330389 -0.053752 +v -1.401832 2.325267 0.037148 +v -1.367989 2.326111 0.116068 +v -1.380512 2.227921 -0.070646 +v -1.378233 2.209659 0.062972 +v -1.339946 2.225900 0.146510 +v -1.351179 2.274333 0.162479 +v -1.794637 2.206148 -0.017425 +v -1.398844 2.275373 -0.096812 +v -1.524685 2.312473 0.043160 +v -1.504030 2.310744 0.126495 +v -1.486035 2.218486 -0.062780 +v -1.505322 2.196043 0.068813 +v -1.481060 2.209546 0.157905 +v -1.494826 2.257488 0.173364 +v -1.626266 2.173790 0.059471 +v -1.500002 2.266789 -0.090059 +v -1.809052 2.198613 0.054628 +v -1.788411 2.200042 0.121789 +v -1.762224 2.137647 -0.030751 +v -1.779324 2.119443 0.075302 +v -1.756481 2.132013 0.147103 +v -1.774468 2.163872 0.159561 +v -1.797244 2.160531 0.057499 +v -1.780442 2.169733 -0.052736 +v -1.606979 2.193023 -0.059655 +v -1.615770 2.226447 0.154117 +v -1.602004 2.185361 0.140123 +v -1.645628 2.273569 0.036248 +v -1.630368 2.280394 -0.044685 +v -1.620946 2.234419 -0.084350 +v -1.624974 2.272088 0.111689 +v -1.379048 2.252805 0.224499 +v -1.370666 2.204103 0.211687 +v -1.460882 2.245536 0.213278 +v -1.452500 2.196834 0.200466 +v -1.423636 2.225874 0.279902 +v -1.418586 2.176000 0.270239 +v -1.480961 2.235306 0.220738 +v -1.475912 2.185432 0.211075 +v -1.509230 2.198029 0.341518 +v -1.505689 2.163052 0.334741 +v -1.549433 2.204644 0.300026 +v -1.545892 2.169667 0.293249 +v -0.265503 0.256741 -0.179040 +v -0.128605 0.263213 -0.074647 +v -0.266996 0.252931 -0.064922 +v -0.145572 0.268133 -0.193156 +v -0.273029 0.253686 -0.122348 +v -0.128976 0.264181 -0.137721 +v -0.210161 0.264081 -0.203502 +v -0.234285 0.000684 0.289802 +v -0.345794 0.000685 0.270758 +v -0.167301 0.000685 -0.230884 +v -0.273702 0.000685 -0.245200 +v -0.147492 0.000684 0.033835 +v -0.291304 0.000684 0.291559 +v -0.376200 0.000685 -0.002375 +v -0.230503 0.000685 -0.254528 +v -0.260875 0.000685 0.016763 +v -0.140559 0.000683 0.170049 +v -0.394861 0.000685 0.127952 +v -0.277034 0.000684 0.160908 +v -0.341473 0.000685 -0.146689 +v -0.111766 0.000684 -0.113835 +v -0.243010 0.000685 -0.142600 +v -0.170881 0.000684 0.233609 +v -0.387064 0.000685 0.197662 +v -0.285114 0.000684 0.232981 +v -0.312595 0.000685 -0.218283 +v -0.233294 0.000685 -0.223272 +v -0.121678 0.000684 -0.190511 +v -0.133452 0.000684 -0.034831 +v -0.352371 0.000685 -0.064287 +v -0.252796 0.000685 -0.055309 +v -0.391075 0.000685 0.061317 +v -0.268955 0.000685 0.088836 +v -0.133089 0.000684 0.103414 +v -0.236024 0.085525 0.288271 +v -0.366872 0.085526 0.268235 +v -0.158210 0.085526 -0.243380 +v -0.263710 0.080414 -0.243917 +v -0.131449 0.085526 0.035190 +v -0.302807 0.085526 0.289322 +v -0.387167 0.085527 -0.003582 +v -0.218139 0.085526 -0.261130 +v -0.135320 0.085525 0.181042 +v -0.409058 0.085527 0.135967 +v -0.342831 0.085527 -0.157921 +v -0.099833 0.085525 -0.123180 +v -0.170889 0.085525 0.249100 +v -0.398790 0.085527 0.210881 +v -0.307420 0.077122 -0.204682 +v -0.113933 0.085526 -0.206283 +v -0.117741 0.085526 -0.038335 +v -0.361040 0.085527 -0.069341 +v -0.404616 0.085527 0.064617 +v -0.126558 0.085525 0.109692 +v -0.238664 0.121950 0.282890 +v -0.345776 0.121951 0.271347 +v -0.158809 0.140201 -0.234637 +v -0.261978 0.141703 -0.233051 +v -0.151253 0.140201 0.045061 +v -0.370775 0.140202 0.015093 +v -0.212986 0.140554 -0.248087 +v -0.154814 0.140200 0.167876 +v -0.390340 0.140202 0.131699 +v -0.330126 0.140202 -0.142561 +v -0.116431 0.142227 -0.125676 +v -0.178285 0.140200 0.230694 +v -0.381814 0.140202 0.201272 +v -0.294809 0.139635 -0.192786 +v -0.130550 0.141688 -0.187006 +v -0.137707 0.140201 -0.008060 +v -0.357415 0.140202 -0.035044 +v -0.386200 0.140202 0.065195 +v -0.147016 0.140200 0.101327 +v -0.247314 0.174407 0.250243 +v -0.315698 0.174407 0.237716 +v -0.158239 0.197919 -0.224671 +v -0.247956 0.200890 -0.226562 +v -0.203553 0.182644 0.039623 +v -0.324272 0.193250 0.026732 +v -0.204122 0.200081 -0.230329 +v -0.180198 0.181212 0.144057 +v -0.352631 0.193276 0.128898 +v -0.302766 0.209668 -0.141691 +v -0.120922 0.202998 -0.122415 +v -0.200942 0.187023 0.207151 +v -0.343452 0.188832 0.188200 +v -0.279004 0.199070 -0.189688 +v -0.133359 0.198869 -0.180969 +v -0.186309 0.199341 -0.013046 +v -0.298874 0.215861 -0.011771 +v -0.344463 0.191743 0.072775 +v -0.194729 0.184572 0.092512 +v -0.342190 0.000685 -0.105824 +v -0.247903 0.000685 -0.098954 +v -0.120121 0.000684 -0.073998 +v -0.351959 0.085527 -0.113818 +v -0.105622 0.085526 -0.080274 +v -0.342893 0.140202 -0.089580 +v -0.121478 0.140201 -0.060615 +v -0.307313 0.215861 -0.085948 +v -0.133068 0.215860 -0.062786 +v -0.264947 0.242080 -0.188311 +v -0.233465 0.234212 -0.224737 +v -0.197228 0.242264 -0.221206 +v -0.169248 0.245962 -0.208306 +v -0.283799 0.229314 -0.077606 +v -0.283168 0.243471 -0.137559 +v -0.130985 0.236445 -0.179838 +v -0.124258 0.240067 -0.122671 +v -0.131967 0.229313 -0.058958 +v -0.192917 0.231434 -0.029966 +v -0.263136 0.229314 -0.044264 +v -0.192377 0.256884 -0.038600 +v -1.684602 2.165335 -0.045203 +v -1.679243 2.158687 0.143613 +v -1.727340 2.236091 0.045438 +v -1.702795 2.146616 0.067387 +v -1.695118 2.195160 0.156839 +v -1.712502 2.243271 -0.031055 +v -1.700694 2.202076 -0.068543 +v -1.706693 2.236064 0.116739 +v 1.685084 2.165334 -0.045201 +v 1.679725 2.158686 0.143615 +v 1.727823 2.236091 0.045440 +v 1.703278 2.146616 0.067389 +v 1.695601 2.195159 0.156841 +v 1.712985 2.243270 -0.031053 +v 1.701177 2.202075 -0.068541 +v 1.707175 2.236064 0.116741 +v 1.566276 2.184916 0.064144 +v 1.555779 2.241967 0.163742 +v 1.570378 2.300414 -0.045463 +v 1.560956 2.250603 -0.087203 +v 1.564984 2.291415 0.119094 +v 1.546990 2.205754 -0.061216 +v 1.542014 2.197453 0.149016 +v 1.585639 2.293020 0.039706 +v -1.565794 2.184916 0.064142 +v -1.555298 2.241968 0.163740 +v -1.569896 2.300415 -0.045465 +v -1.560474 2.250604 -0.087204 +v -1.564502 2.291416 0.119092 +v -1.546507 2.205755 -0.061217 +v -1.541532 2.197453 0.149014 +v -1.585157 2.293021 0.039704 +v 0.817599 2.223923 -0.073164 +v 0.817657 2.186424 0.036880 +v 0.839517 2.418247 -0.059336 +v 0.833481 2.399625 0.085116 +v 0.812031 2.210284 0.112704 +v 0.831581 2.316127 -0.092581 +v 0.836919 2.423518 0.015427 +v -0.817117 2.223924 -0.073165 +v -0.817175 2.186424 0.036879 +v -0.839035 2.418248 -0.059336 +v -0.832999 2.399626 0.085115 +v -0.811549 2.210284 0.112703 +v -0.831098 2.316128 -0.092581 +v -0.836436 2.423519 0.015427 +v 0.000241 1.579583 0.304214 +v 0.334021 1.590260 0.078120 +v 0.000241 1.627749 -0.181353 +v 0.277708 1.583014 0.235970 +v 0.282037 1.614811 -0.097637 +v 0.135280 1.622088 -0.146189 +v 0.135280 1.580997 0.278586 +v -0.333345 1.590268 0.078406 +v -0.276778 1.583012 0.236050 +v -0.281555 1.614811 -0.097637 +v -0.134797 1.622088 -0.146189 +v -0.134797 1.580997 0.278586 +v 0.266259 1.970420 0.211617 +v 0.266259 1.997501 -0.117050 +v 0.000241 1.967728 0.278793 +v 0.313651 1.978875 0.053829 +v 0.000241 2.001626 -0.179539 +v 0.127719 1.969018 0.246602 +v 0.127719 1.999649 -0.149594 +v -0.265777 1.970420 0.211617 +v -0.265777 1.997501 -0.117050 +v -0.313168 1.978875 0.053829 +v -0.127236 1.969018 0.246602 +v -0.127236 1.999649 -0.149594 +v 1.122344 2.200868 0.116925 +v 1.142700 2.297759 -0.071671 +v 1.154038 2.387891 0.029002 +v 1.136979 2.281405 0.127950 +v 1.127844 2.213048 -0.052699 +v 1.133873 2.176636 0.048787 +v 1.152195 2.391741 -0.039946 +v 1.146233 2.374701 0.093271 +v -1.121862 2.200869 0.116924 +v -1.142218 2.297760 -0.071672 +v -1.153556 2.387892 0.029001 +v -1.136497 2.281407 0.127949 +v -1.127362 2.213049 -0.052700 +v -1.133391 2.176636 0.048786 +v -1.151713 2.391742 -0.039947 +v -1.145751 2.374702 0.093270 +v 0.581107 2.319769 0.109865 +v 0.586750 2.337995 -0.095834 +v 0.593441 2.436572 -0.073496 +v 0.600412 2.447117 0.006770 +v 0.587561 2.422814 0.077529 +v 0.577991 2.206686 0.028552 +v 0.568364 2.226653 0.119143 +v 0.573788 2.244173 -0.083181 +v -0.580625 2.319769 0.109865 +v -0.586267 2.337995 -0.095834 +v -0.592958 2.436572 -0.073496 +v -0.599929 2.447117 0.006770 +v -0.587078 2.422814 0.077529 +v -0.577509 2.206685 0.028552 +v -0.567881 2.226653 0.119143 +v -0.573305 2.244173 -0.083181 +v 0.069151 0.811140 -0.084140 +v 0.143267 0.769931 0.115422 +v 0.361608 0.814996 0.037936 +v 0.265496 0.785043 0.109763 +v 0.146190 0.822050 -0.160339 +v 0.347095 0.821727 -0.081373 +v 0.065815 0.785709 0.027417 +v -0.070991 0.811069 -0.086508 +v -0.141651 0.769890 0.114305 +v -0.361270 0.815091 0.040619 +v -0.263946 0.785076 0.110766 +v -0.149333 0.822031 -0.161359 +v -0.348828 0.821822 -0.078922 +v -0.065734 0.785628 0.024971 +vt 0.424701 0.840048 +vt 0.423209 0.793785 +vt 0.461737 0.792426 +vt 0.464884 0.838834 +vt 0.468357 0.881332 +vt 0.416097 0.891155 +vt 0.382303 0.875623 +vt 0.388845 0.838885 +vt 0.386734 0.795098 +vt 0.579037 0.844668 +vt 0.567369 0.879717 +vt 0.544300 0.865660 +vt 0.547922 0.837220 +vt 0.549707 0.794263 +vt 0.582610 0.796170 +vt 0.598269 0.849956 +vt 0.602276 0.796592 +vt 0.621565 0.796099 +vt 0.617431 0.855287 +vt 0.601115 0.901709 +vt 0.583674 0.890608 +vt 0.580062 0.754791 +vt 0.546804 0.757119 +vt 0.544177 0.724490 +vt 0.573455 0.711359 +vt 0.598626 0.752337 +vt 0.595019 0.704367 +vt 0.617289 0.699781 +vt 0.620532 0.750242 +vt 0.349273 0.747190 +vt 0.347330 0.708825 +vt 0.370455 0.709217 +vt 0.379997 0.749138 +vt 0.352581 0.794437 +vt 0.329588 0.744940 +vt 0.331063 0.794428 +vt 0.315002 0.794084 +vt 0.314985 0.741795 +vt 0.314901 0.703498 +vt 0.329344 0.703695 +vt 0.354491 0.840733 +vt 0.357743 0.880856 +vt 0.332172 0.842510 +vt 0.333959 0.884940 +vt 0.315019 0.885796 +vt 0.315031 0.842108 +vt 0.414559 0.747483 +vt 0.399518 0.701783 +vt 0.448949 0.694770 +vt 0.456853 0.745548 +vt 0.505141 0.748829 +vt 0.508784 0.706093 +vt 0.507142 0.792801 +vt 0.508074 0.837122 +vt 0.514661 0.872259 +vt 0.419137 0.937326 +vt 0.478349 0.922822 +vt 0.424097 0.965195 +vt 0.486594 0.948330 +vt 0.495723 0.973192 +vt 0.428443 0.990876 +vt 0.345577 0.936058 +vt 0.314782 0.941388 +vt 0.375705 0.922558 +vt 0.532102 0.902649 +vt 0.545515 0.922196 +vt 0.559814 0.940878 +vt 0.436532 0.655316 +vt 0.362648 0.645806 +vt 0.382595 0.577465 +vt 0.445393 0.577416 +vt 0.535122 0.656711 +vt 0.561397 0.634699 +vt 0.607811 0.622342 +vt 0.339216 0.643866 +vt 0.314578 0.640560 +vt 0.510735 0.577421 +vt 0.559529 0.577420 +vt 0.444090 0.534948 +vt 0.509270 0.534980 +vt 0.347826 0.577463 +vt 0.314959 0.577465 +vt 0.314357 0.535019 +vt 0.343869 0.535021 +vt 0.327559 0.284656 +vt 0.332242 0.271386 +vt 0.359600 0.274896 +vt 0.357633 0.299914 +vt 0.321315 0.250312 +vt 0.314290 0.247970 +vt 0.314171 0.231740 +vt 0.334433 0.243039 +vt 0.382847 0.535032 +vt 0.558656 0.534925 +vt 0.325529 0.253434 +vt 0.351430 0.248123 +vt 0.319754 0.286217 +vt 0.333829 0.303559 +vt 0.418691 0.370972 +vt 0.389352 0.351632 +vt 0.418603 0.335552 +vt 0.431990 0.354981 +vt 0.338145 0.328760 +vt 0.366602 0.325961 +vt 0.377808 0.356778 +vt 0.343676 0.358303 +vt 0.335864 0.210609 +vt 0.314003 0.207365 +vt 0.314103 0.176477 +vt 0.345144 0.178504 +vt 0.387039 0.219621 +vt 0.390341 0.194177 +vt 0.422363 0.207716 +vt 0.418946 0.229929 +vt 0.345500 0.054288 +vt 0.315138 0.054235 +vt 0.315528 0.018181 +vt 0.347504 0.018238 +vt 0.376599 0.479159 +vt 0.415450 0.479198 +vt 0.416158 0.512649 +vt 0.378862 0.512649 +vt 0.417869 0.054390 +vt 0.379180 0.054356 +vt 0.383732 0.018359 +vt 0.421058 0.018359 +vt 0.344804 0.479148 +vt 0.345824 0.512586 +vt 0.343730 0.404567 +vt 0.376072 0.404566 +vt 0.374336 0.441555 +vt 0.343784 0.441555 +vt 0.416751 0.128948 +vt 0.377222 0.128952 +vt 0.374628 0.091879 +vt 0.414680 0.091883 +vt 0.414734 0.404572 +vt 0.414742 0.441555 +vt 0.344319 0.128948 +vt 0.314426 0.128944 +vt 0.314749 0.091873 +vt 0.343495 0.091877 +vt 0.377580 0.274898 +vt 0.379110 0.250741 +vt 0.414096 0.254865 +vt 0.413016 0.274926 +vt 0.383845 0.322909 +vt 0.415778 0.316747 +vt 0.785443 0.156150 +vt 0.764725 0.156147 +vt 0.765086 0.122612 +vt 0.785862 0.122612 +vt 0.799682 0.217712 +vt 0.787967 0.231864 +vt 0.787944 0.209717 +vt 0.806383 0.217712 +vt 0.817931 0.209717 +vt 0.817908 0.231864 +vt 0.313408 0.304910 +vt 0.313716 0.329378 +vt 0.371733 0.216071 +vt 0.314416 0.404567 +vt 0.314870 0.441555 +vt 0.315301 0.479147 +vt 0.315731 0.512570 +vt 0.379817 0.180762 +vt 0.313963 0.358401 +vt 0.313509 0.286997 +vt 0.603163 0.577407 +vt 0.606192 0.534877 +vt 0.745241 0.200706 +vt 0.748033 0.232049 +vt 0.860635 0.200706 +vt 0.857842 0.232049 +vt 0.709972 0.232115 +vt 0.709972 0.200711 +vt 0.709972 0.171386 +vt 0.709972 0.163211 +vt 0.711885 0.163092 +vt 0.722930 0.172839 +vt 0.895904 0.232115 +vt 0.895904 0.200711 +vt 0.882945 0.172839 +vt 0.893990 0.163092 +vt 0.895904 0.163211 +vt 0.895904 0.171386 +vt 0.746436 0.108499 +vt 0.728058 0.108499 +vt 0.727994 0.095821 +vt 0.746362 0.095822 +vt 0.742320 0.156136 +vt 0.722900 0.156121 +vt 0.728122 0.122612 +vt 0.746509 0.122612 +vt 0.820432 0.156150 +vt 0.806711 0.156148 +vt 0.806948 0.122612 +vt 0.820014 0.122612 +vt 0.863555 0.156136 +vt 0.841092 0.156147 +vt 0.840811 0.122612 +vt 0.859366 0.122612 +vt 0.820810 0.095822 +vt 0.806988 0.095820 +vt 0.807180 0.054984 +vt 0.822926 0.054984 +vt 0.820412 0.108499 +vt 0.806968 0.108499 +vt 0.859513 0.095822 +vt 0.859440 0.108499 +vt 0.840859 0.095821 +vt 0.785464 0.108499 +vt 0.765055 0.108499 +vt 0.765025 0.095821 +vt 0.785065 0.095822 +vt 0.765027 0.054984 +vt 0.748996 0.054984 +vt 0.749674 0.016269 +vt 0.765051 0.016269 +vt 0.840857 0.054984 +vt 0.856880 0.054984 +vt 0.782949 0.054984 +vt 0.729510 0.054983 +vt 0.799277 0.156148 +vt 0.882975 0.156121 +vt 0.798935 0.095820 +vt 0.798699 0.054984 +vt 0.877882 0.095821 +vt 0.876366 0.054983 +vt 0.730059 0.016269 +vt 0.798960 0.108499 +vt 0.877818 0.108499 +vt 0.877753 0.122612 +vt 0.798985 0.122612 +vt 0.840838 0.016269 +vt 0.856202 0.016269 +vt 0.782896 0.016268 +vt 0.798648 0.016268 +vt 0.449700 0.220684 +vt 0.449796 0.204123 +vt 0.480859 0.204123 +vt 0.480685 0.220695 +vt 0.449654 0.257610 +vt 0.449682 0.238404 +vt 0.480652 0.238492 +vt 0.480599 0.257655 +vt 0.449661 0.302478 +vt 0.449646 0.286082 +vt 0.480577 0.286036 +vt 0.480601 0.302370 +vt 0.449670 0.274956 +vt 0.480593 0.274987 +vt 0.377299 0.292342 +vt 0.412883 0.292280 +vt 0.427685 0.190243 +vt 0.435707 0.203873 +vt 0.566812 0.286023 +vt 0.566812 0.275036 +vt 0.595577 0.275057 +vt 0.595566 0.286095 +vt 0.566827 0.354952 +vt 0.566822 0.326817 +vt 0.595512 0.326825 +vt 0.595500 0.354952 +vt 0.566782 0.238670 +vt 0.566782 0.220717 +vt 0.595706 0.220705 +vt 0.595699 0.238600 +vt 0.566815 0.302227 +vt 0.595550 0.302456 +vt 0.449673 0.326828 +vt 0.480612 0.326823 +vt 0.449765 0.354952 +vt 0.480773 0.354952 +vt 0.936048 0.768188 +vt 0.936053 0.746623 +vt 0.924192 0.740812 +vt 0.923121 0.760166 +vt 0.936018 0.819789 +vt 0.936059 0.797784 +vt 0.916592 0.805110 +vt 0.913699 0.821253 +vt 0.935973 0.782601 +vt 0.915423 0.782034 +vt 0.911752 0.319669 +vt 0.933272 0.316595 +vt 0.941824 0.333084 +vt 0.910614 0.334381 +vt 0.566800 0.257742 +vt 0.595621 0.257734 +vt 0.566790 0.204124 +vt 0.595682 0.204124 +vt 0.506526 0.274998 +vt 0.506548 0.257674 +vt 0.532519 0.257694 +vt 0.532485 0.275009 +vt 0.506620 0.220701 +vt 0.506734 0.204123 +vt 0.532625 0.204124 +vt 0.532558 0.220707 +vt 0.532559 0.238577 +vt 0.506530 0.302339 +vt 0.506519 0.286032 +vt 0.532490 0.286028 +vt 0.532482 0.302308 +vt 0.506529 0.326822 +vt 0.532468 0.326821 +vt 0.506639 0.354952 +vt 0.532541 0.354952 +vt 0.420373 0.176908 +vt 0.414725 0.374942 +vt 0.885913 0.762972 +vt 0.885325 0.783977 +vt 0.889616 0.328988 +vt 0.912643 0.364253 +vt 0.892080 0.360976 +vt 0.879460 0.743643 +vt 0.891324 0.803735 +vt 0.891345 0.819504 +vt 0.896713 0.317960 +vt 0.936077 0.838896 +vt 0.917882 0.833832 +vt 0.948365 0.733420 +vt 0.934622 0.732855 +vt 0.849376 0.815349 +vt 0.850748 0.805248 +vt 0.836471 0.804603 +vt 0.835136 0.812201 +vt 0.845468 0.793376 +vt 0.831510 0.796107 +vt 0.917673 0.409579 +vt 0.942118 0.402143 +vt 0.940634 0.420541 +vt 0.917720 0.427678 +vt 0.838529 0.771488 +vt 0.832166 0.765778 +vt 0.818658 0.776896 +vt 0.825597 0.780492 +vt 0.891695 0.833437 +vt 0.868327 0.735148 +vt 0.887944 0.727402 +vt 0.871512 0.724326 +vt 0.941792 0.356512 +vt 0.824181 0.797634 +vt 0.812448 0.795354 +vt 0.828406 0.817321 +vt 0.830746 0.786410 +vt 0.844654 0.780574 +vt 0.900278 0.405603 +vt 0.901378 0.423637 +vt 0.844856 0.823697 +vt 0.876501 0.831755 +vt 0.877480 0.819001 +vt 0.863616 0.818497 +vt 0.861307 0.830073 +vt 0.872376 0.787311 +vt 0.872238 0.768855 +vt 0.858562 0.774738 +vt 0.859427 0.790645 +vt 0.895628 0.374273 +vt 0.915135 0.377867 +vt 0.917627 0.391481 +vt 0.899177 0.387570 +vt 0.865460 0.753064 +vt 0.851461 0.762485 +vt 0.857000 0.744904 +vt 0.845674 0.754660 +vt 0.942698 0.370129 +vt 0.943603 0.383745 +vt 0.878174 0.804814 +vt 0.865024 0.805893 +vt 0.914557 0.725788 +vt 0.924213 0.719241 +vt 0.914994 0.703678 +vt 0.906472 0.711107 +vt 0.881044 0.356556 +vt 0.875706 0.338668 +vt 0.863509 0.352136 +vt 0.879002 0.362548 +vt 0.863138 0.381292 +vt 0.852273 0.373989 +vt 0.885267 0.719886 +vt 0.871271 0.716399 +vt 0.894062 0.694357 +vt 0.896674 0.686606 +vt 0.881207 0.690837 +vt 0.886620 0.697803 +vt 0.822979 0.016268 +vt 0.807221 0.016268 +vt 0.875816 0.016269 +vt 0.767140 0.911152 +vt 0.774189 0.910992 +vt 0.771025 0.901580 +vt 0.764041 0.893569 +vt 0.762619 0.936895 +vt 0.770492 0.925697 +vt 0.696701 0.941705 +vt 0.711877 0.936747 +vt 0.712850 0.911002 +vt 0.696555 0.911002 +vt 0.714237 0.884771 +vt 0.699642 0.883044 +vt 0.663963 0.911002 +vt 0.680259 0.911002 +vt 0.684668 0.883866 +vt 0.669298 0.887352 +vt 0.666683 0.936525 +vt 0.681635 0.941705 +vt 0.736641 0.940160 +vt 0.745745 0.941028 +vt 0.748883 0.911001 +vt 0.739015 0.911001 +vt 0.747331 0.888913 +vt 0.738188 0.889775 +vt 0.652548 0.923774 +vt 0.650728 0.911086 +vt 0.654009 0.898398 +vt 0.729146 0.911001 +vt 0.728658 0.888542 +vt 0.727561 0.938163 +vt 0.685829 0.695635 +vt 0.692503 0.712229 +vt 0.708990 0.708576 +vt 0.708432 0.689949 +vt 0.636358 0.838542 +vt 0.651216 0.838339 +vt 0.651688 0.827846 +vt 0.635893 0.827677 +vt 0.781184 0.780586 +vt 0.761434 0.786870 +vt 0.765163 0.797322 +vt 0.785169 0.791226 +vt 0.644587 0.767637 +vt 0.662889 0.772774 +vt 0.667608 0.758644 +vt 0.648575 0.752749 +vt 0.673889 0.744932 +vt 0.655645 0.733289 +vt 0.636687 0.810221 +vt 0.652656 0.811979 +vt 0.655557 0.797710 +vt 0.638034 0.794237 +vt 0.751425 0.706855 +vt 0.736307 0.719414 +vt 0.743897 0.740484 +vt 0.762033 0.731608 +vt 0.770097 0.809706 +vt 0.787648 0.803114 +vt 0.775207 0.821912 +vt 0.791133 0.816315 +vt 0.756045 0.773861 +vt 0.741140 0.776355 +vt 0.746963 0.789506 +vt 0.749903 0.756858 +vt 0.738081 0.763188 +vt 0.728583 0.733966 +vt 0.733420 0.750248 +vt 0.683428 0.721761 +vt 0.690265 0.735383 +vt 0.699037 0.717821 +vt 0.675902 0.773849 +vt 0.678758 0.763215 +vt 0.663860 0.828975 +vt 0.666038 0.813500 +vt 0.651369 0.848259 +vt 0.662989 0.848260 +vt 0.662721 0.839648 +vt 0.726155 0.711178 +vt 0.718851 0.717725 +vt 0.763232 0.827184 +vt 0.767474 0.837667 +vt 0.779325 0.835265 +vt 0.753212 0.799839 +vt 0.758294 0.814508 +vt 0.683588 0.751217 +vt 0.658905 0.785523 +vt 0.671987 0.788046 +vt 0.669398 0.798853 +vt 0.770171 0.848260 +vt 0.779396 0.848260 +vt 0.737140 0.805935 +vt 0.745139 0.819551 +vt 0.691125 0.756638 +vt 0.696225 0.747399 +vt 0.671483 0.841478 +vt 0.672536 0.832167 +vt 0.695283 0.790443 +vt 0.692294 0.778048 +vt 0.684106 0.807013 +vt 0.755272 0.840706 +vt 0.757876 0.848260 +vt 0.689617 0.767062 +vt 0.751837 0.830942 +vt 0.726011 0.777908 +vt 0.724135 0.790230 +vt 0.727747 0.767067 +vt 0.723221 0.747063 +vt 0.728335 0.757320 +vt 0.702554 0.734897 +vt 0.676496 0.818660 +vt 0.669902 0.848260 +vt 0.715282 0.734795 +vt 0.744649 0.834372 +vt 0.747224 0.843692 +vt 0.678439 0.843499 +vt 0.679462 0.835501 +vt 0.740023 0.822211 +vt 0.676559 0.848260 +vt 0.718747 0.798862 +vt 0.731506 0.809276 +vt 0.681201 0.821956 +vt 0.749698 0.848260 +vt 0.689595 0.809913 +vt 0.701454 0.798968 +vt 0.731965 0.694501 +vt 0.685748 0.848259 +vt 0.686221 0.838135 +vt 0.741199 0.848260 +vt 0.688336 0.824075 +vt 0.697257 0.814380 +vt 0.734432 0.824734 +vt 0.739082 0.837215 +vt 0.710682 0.811176 +vt 0.725362 0.813361 +vt 0.794502 0.834892 +vt 0.769187 0.749663 +vt 0.775611 0.765710 +vt 0.794901 0.848257 +vt 0.640929 0.781632 +vt 0.666521 0.709568 +vt 0.636356 0.848259 +vt 0.205395 0.840356 +vt 0.165057 0.839231 +vt 0.168249 0.792680 +vt 0.206812 0.794023 +vt 0.214392 0.891615 +vt 0.161589 0.882055 +vt 0.241265 0.839077 +vt 0.247916 0.875817 +vt 0.243291 0.795273 +vt 0.050187 0.844482 +vt 0.081566 0.837294 +vt 0.084989 0.865972 +vt 0.061577 0.879960 +vt 0.047164 0.795723 +vt 0.080136 0.794155 +vt 0.030751 0.849572 +vt 0.011363 0.854676 +vt 0.008122 0.795077 +vt 0.027453 0.795879 +vt 0.044980 0.890795 +vt 0.027199 0.901816 +vt 0.050209 0.754404 +vt 0.083344 0.757041 +vt 0.057188 0.711303 +vt 0.086190 0.724548 +vt 0.031763 0.751718 +vt 0.009998 0.749349 +vt 0.013882 0.699460 +vt 0.035870 0.704195 +vt 0.280653 0.747292 +vt 0.249910 0.749310 +vt 0.259406 0.709407 +vt 0.282503 0.708944 +vt 0.277433 0.794536 +vt 0.300327 0.744987 +vt 0.298944 0.794473 +vt 0.300463 0.703752 +vt 0.275598 0.840835 +vt 0.272406 0.880931 +vt 0.297897 0.842557 +vt 0.296112 0.884965 +vt 0.215427 0.747718 +vt 0.173187 0.745804 +vt 0.181086 0.695152 +vt 0.230402 0.702057 +vt 0.124974 0.748995 +vt 0.121499 0.706399 +vt 0.122778 0.792952 +vt 0.121636 0.837433 +vt 0.114850 0.872809 +vt 0.211767 0.938667 +vt 0.151460 0.924131 +vt 0.206881 0.967319 +vt 0.202444 0.993810 +vt 0.133578 0.975415 +vt 0.143009 0.950086 +vt 0.284332 0.935822 +vt 0.254612 0.922405 +vt 0.097049 0.903456 +vt 0.083305 0.923173 +vt 0.068619 0.942013 +vt 0.194143 0.656045 +vt 0.184361 0.577417 +vt 0.247489 0.577466 +vt 0.266383 0.646033 +vt 0.095483 0.657314 +vt 0.069552 0.635446 +vt 0.023577 0.623156 +vt 0.289968 0.643994 +vt 0.071392 0.577421 +vt 0.119669 0.577423 +vt 0.120835 0.534998 +vt 0.185223 0.534962 +vt 0.282127 0.577464 +vt 0.284898 0.535025 +vt 0.299459 0.284656 +vt 0.269410 0.300468 +vt 0.267320 0.274905 +vt 0.294776 0.271386 +vt 0.306484 0.250312 +vt 0.293315 0.242924 +vt 0.245931 0.535039 +vt 0.071962 0.534946 +vt 0.275823 0.248153 +vt 0.301489 0.253434 +vt 0.307265 0.286217 +vt 0.293325 0.304171 +vt 0.208695 0.372787 +vt 0.194848 0.354981 +vt 0.207736 0.335642 +vt 0.238048 0.352770 +vt 0.289297 0.329143 +vt 0.284195 0.358809 +vt 0.249789 0.357798 +vt 0.260662 0.326638 +vt 0.291825 0.210419 +vt 0.282910 0.178142 +vt 0.240092 0.219225 +vt 0.207156 0.229503 +vt 0.203883 0.206876 +vt 0.237038 0.193441 +vt 0.284744 0.054246 +vt 0.283530 0.018181 +vt 0.253997 0.479174 +vt 0.252661 0.512749 +vt 0.215378 0.512749 +vt 0.215164 0.479218 +vt 0.212078 0.054277 +vt 0.209691 0.018239 +vt 0.247220 0.018239 +vt 0.250975 0.054268 +vt 0.285805 0.479156 +vt 0.285667 0.512634 +vt 0.285068 0.404567 +vt 0.285942 0.441555 +vt 0.255333 0.441555 +vt 0.252561 0.404567 +vt 0.211458 0.128944 +vt 0.214464 0.091875 +vt 0.254730 0.091874 +vt 0.251290 0.128949 +vt 0.214951 0.441555 +vt 0.213861 0.404572 +vt 0.284433 0.128947 +vt 0.285957 0.091875 +vt 0.249199 0.274905 +vt 0.212908 0.274933 +vt 0.211924 0.254886 +vt 0.247827 0.250786 +vt 0.210380 0.317703 +vt 0.243254 0.323720 +vt 0.634500 0.156150 +vt 0.634082 0.122612 +vt 0.654909 0.122612 +vt 0.655078 0.156147 +vt 0.620451 0.217712 +vt 0.631999 0.209717 +vt 0.631977 0.231864 +vt 0.985614 0.217712 +vt 0.973899 0.231864 +vt 0.973876 0.209717 +vt 0.255597 0.215743 +vt 0.247850 0.179997 +vt 0.028247 0.577410 +vt 0.025045 0.534903 +vt 0.674703 0.200706 +vt 0.671910 0.232049 +vt 0.931173 0.200706 +vt 0.933965 0.232049 +vt 0.697013 0.172839 +vt 0.708058 0.163092 +vt 0.908862 0.172839 +vt 0.897817 0.163092 +vt 0.673508 0.108499 +vt 0.673582 0.095822 +vt 0.691950 0.095821 +vt 0.691886 0.108499 +vt 0.677624 0.156136 +vt 0.673434 0.122612 +vt 0.691822 0.122612 +vt 0.697044 0.156121 +vt 0.971375 0.156150 +vt 0.971794 0.122612 +vt 0.984917 0.122612 +vt 0.985209 0.156148 +vt 0.928252 0.156136 +vt 0.932441 0.122612 +vt 0.951042 0.122612 +vt 0.950589 0.156147 +vt 0.970997 0.095822 +vt 0.968881 0.054984 +vt 0.984631 0.054984 +vt 0.984867 0.095820 +vt 0.971395 0.108499 +vt 0.984892 0.108499 +vt 0.932368 0.108499 +vt 0.932294 0.095822 +vt 0.950965 0.095821 +vt 0.634480 0.108499 +vt 0.634879 0.095822 +vt 0.654938 0.095821 +vt 0.654923 0.108499 +vt 0.654936 0.054984 +vt 0.654925 0.016269 +vt 0.670270 0.016269 +vt 0.670948 0.054984 +vt 0.934928 0.054984 +vt 0.950969 0.054984 +vt 0.636994 0.054984 +vt 0.690377 0.054983 +vt 0.620779 0.156148 +vt 0.908832 0.156121 +vt 0.621056 0.095820 +vt 0.621248 0.054984 +vt 0.913926 0.095821 +vt 0.915498 0.054983 +vt 0.689838 0.016269 +vt 0.621036 0.108499 +vt 0.913990 0.108499 +vt 0.914054 0.122612 +vt 0.621016 0.122612 +vt 0.935606 0.016269 +vt 0.950999 0.016269 +vt 0.621289 0.016268 +vt 0.637047 0.016268 +vt 0.176090 0.220682 +vt 0.145191 0.220694 +vt 0.145087 0.204123 +vt 0.175901 0.204123 +vt 0.176179 0.257611 +vt 0.145241 0.257656 +vt 0.145210 0.238484 +vt 0.176125 0.238395 +vt 0.176168 0.302520 +vt 0.145242 0.302415 +vt 0.145255 0.286054 +vt 0.176196 0.286099 +vt 0.145244 0.274996 +vt 0.176147 0.274965 +vt 0.213159 0.292296 +vt 0.249624 0.292358 +vt 0.190858 0.203868 +vt 0.204472 0.190753 +vt 0.059422 0.286044 +vt 0.031066 0.286124 +vt 0.031049 0.275075 +vt 0.059417 0.275048 +vt 0.059492 0.354952 +vt 0.031128 0.354952 +vt 0.031124 0.326827 +vt 0.059475 0.326819 +vt 0.059279 0.238666 +vt 0.030929 0.238598 +vt 0.030921 0.220705 +vt 0.059276 0.220716 +vt 0.031084 0.302521 +vt 0.059440 0.302276 +vt 0.176146 0.326829 +vt 0.145236 0.326824 +vt 0.175964 0.354952 +vt 0.145141 0.354952 +vt 0.936052 0.597770 +vt 0.923354 0.589708 +vt 0.924484 0.570357 +vt 0.936057 0.576206 +vt 0.936019 0.649366 +vt 0.913751 0.650767 +vt 0.916692 0.634633 +vt 0.936061 0.627368 +vt 0.915592 0.611553 +vt 0.935975 0.612170 +vt 0.797433 0.319246 +vt 0.796272 0.333957 +vt 0.827484 0.332708 +vt 0.818957 0.316206 +vt 0.059362 0.257746 +vt 0.031009 0.257741 +vt 0.030934 0.204124 +vt 0.059305 0.204124 +vt 0.118260 0.275008 +vt 0.093911 0.275019 +vt 0.093876 0.257696 +vt 0.118252 0.257676 +vt 0.118222 0.220700 +vt 0.093831 0.220705 +vt 0.093754 0.204124 +vt 0.118173 0.204123 +vt 0.093831 0.238570 +vt 0.118261 0.302386 +vt 0.093919 0.302358 +vt 0.093909 0.286048 +vt 0.118265 0.286051 +vt 0.118262 0.326824 +vt 0.093936 0.326823 +vt 0.118215 0.354952 +vt 0.093852 0.354952 +vt 0.208452 0.175985 +vt 0.212772 0.376778 +vt 0.885489 0.613407 +vt 0.886139 0.592404 +vt 0.775283 0.328531 +vt 0.777696 0.360523 +vt 0.798255 0.363832 +vt 0.879743 0.573056 +vt 0.891403 0.648951 +vt 0.891428 0.633182 +vt 0.782396 0.317514 +vt 0.936077 0.668483 +vt 0.917897 0.663357 +vt 0.934937 0.562431 +vt 0.948678 0.563037 +vt 0.849446 0.644672 +vt 0.835215 0.641481 +vt 0.836574 0.633887 +vt 0.850848 0.634574 +vt 0.831637 0.625377 +vt 0.845604 0.622687 +vt 0.803215 0.409166 +vt 0.803234 0.427265 +vt 0.826158 0.420164 +vt 0.827672 0.401768 +vt 0.838730 0.600779 +vt 0.825771 0.609744 +vt 0.818842 0.606128 +vt 0.832383 0.595050 +vt 0.891712 0.662885 +vt 0.868635 0.564528 +vt 0.871852 0.553715 +vt 0.888276 0.556840 +vt 0.827416 0.356136 +vt 0.824304 0.626881 +vt 0.812578 0.624567 +vt 0.828470 0.646582 +vt 0.830902 0.615677 +vt 0.844827 0.609883 +vt 0.785826 0.405163 +vt 0.786898 0.423198 +vt 0.844902 0.653006 +vt 0.876523 0.661158 +vt 0.861333 0.659431 +vt 0.863677 0.647862 +vt 0.877540 0.648407 +vt 0.872530 0.616702 +vt 0.859570 0.619998 +vt 0.858753 0.604088 +vt 0.872446 0.598246 +vt 0.781225 0.373826 +vt 0.784753 0.387128 +vt 0.803196 0.391068 +vt 0.800726 0.377450 +vt 0.851688 0.591814 +vt 0.865716 0.582435 +vt 0.845924 0.583972 +vt 0.857280 0.574250 +vt 0.829185 0.383372 +vt 0.828300 0.369754 +vt 0.878275 0.634222 +vt 0.865123 0.635262 +vt 0.914893 0.555304 +vt 0.906852 0.540600 +vt 0.915395 0.533196 +vt 0.924568 0.548786 +vt 0.761358 0.338190 +vt 0.766667 0.356086 +vt 0.749140 0.351639 +vt 0.737870 0.373475 +vt 0.748724 0.380794 +vt 0.764617 0.362075 +vt 0.885620 0.549316 +vt 0.871635 0.545787 +vt 0.894491 0.523813 +vt 0.887040 0.527237 +vt 0.881647 0.520255 +vt 0.897126 0.516070 +vt 0.968828 0.016268 +vt 0.984580 0.016268 +vt 0.916038 0.016269 +vt 0.934523 0.917621 +vt 0.931424 0.935204 +vt 0.938408 0.927193 +vt 0.941572 0.917780 +vt 0.930002 0.891877 +vt 0.937875 0.903075 +vt 0.864084 0.887068 +vt 0.863938 0.917771 +vt 0.880234 0.917771 +vt 0.879260 0.892026 +vt 0.867026 0.945729 +vt 0.881621 0.944002 +vt 0.831347 0.917772 +vt 0.836681 0.941421 +vt 0.852052 0.944907 +vt 0.847642 0.917771 +vt 0.834066 0.892248 +vt 0.849018 0.887068 +vt 0.904024 0.888613 +vt 0.906398 0.917771 +vt 0.916266 0.917771 +vt 0.913129 0.887745 +vt 0.905572 0.938997 +vt 0.914714 0.939860 +vt 0.819931 0.904999 +vt 0.818111 0.917687 +vt 0.821392 0.930376 +vt 0.896042 0.940231 +vt 0.896530 0.917771 +vt 0.894944 0.890609 +vt 0.686042 0.524352 +vt 0.708647 0.518689 +vt 0.709177 0.537312 +vt 0.692687 0.540948 +vt 0.636372 0.667162 +vt 0.635921 0.656300 +vt 0.651714 0.656493 +vt 0.651227 0.666984 +vt 0.781285 0.609428 +vt 0.785249 0.620097 +vt 0.765233 0.626167 +vt 0.761520 0.615688 +vt 0.644711 0.596297 +vt 0.648718 0.581418 +vt 0.667739 0.587337 +vt 0.663002 0.601458 +vt 0.655822 0.561957 +vt 0.674036 0.573634 +vt 0.636733 0.638857 +vt 0.638119 0.622878 +vt 0.655630 0.626375 +vt 0.652694 0.640638 +vt 0.751631 0.535644 +vt 0.762210 0.560416 +vt 0.744045 0.569271 +vt 0.736483 0.548184 +vt 0.787716 0.631988 +vt 0.770153 0.638555 +vt 0.791176 0.645197 +vt 0.775239 0.650769 +vt 0.756154 0.602668 +vt 0.747050 0.618300 +vt 0.741245 0.605142 +vt 0.750033 0.585655 +vt 0.738203 0.591970 +vt 0.733559 0.579024 +vt 0.728733 0.562733 +vt 0.683603 0.550467 +vt 0.699215 0.546547 +vt 0.690416 0.564106 +vt 0.678881 0.591921 +vt 0.676012 0.602550 +vt 0.666074 0.642176 +vt 0.663882 0.657641 +vt 0.651365 0.677015 +vt 0.662728 0.668313 +vt 0.662980 0.677015 +vt 0.726341 0.539931 +vt 0.719028 0.546474 +vt 0.779342 0.664126 +vt 0.767486 0.666511 +vt 0.763261 0.656022 +vt 0.758344 0.643339 +vt 0.753283 0.628664 +vt 0.683726 0.579931 +vt 0.658998 0.614197 +vt 0.672082 0.616741 +vt 0.669471 0.627536 +vt 0.779389 0.677016 +vt 0.770165 0.677015 +vt 0.745182 0.648362 +vt 0.737201 0.634733 +vt 0.696399 0.576131 +vt 0.691285 0.585364 +vt 0.672552 0.660852 +vt 0.671485 0.670160 +vt 0.692397 0.606771 +vt 0.695369 0.619169 +vt 0.684166 0.635721 +vt 0.757870 0.677015 +vt 0.755280 0.669527 +vt 0.689760 0.595785 +vt 0.751861 0.659762 +vt 0.724212 0.618997 +vt 0.726110 0.606671 +vt 0.727889 0.595831 +vt 0.728493 0.586086 +vt 0.723396 0.575823 +vt 0.702738 0.563637 +vt 0.676524 0.647357 +vt 0.669891 0.677015 +vt 0.715467 0.563548 +vt 0.747228 0.672502 +vt 0.744669 0.663180 +vt 0.679473 0.664199 +vt 0.678437 0.672191 +vt 0.740064 0.651011 +vt 0.676547 0.677015 +vt 0.731566 0.638069 +vt 0.718813 0.627622 +vt 0.681227 0.650664 +vt 0.749692 0.677015 +vt 0.689654 0.638630 +vt 0.701529 0.627703 +vt 0.732176 0.523263 +vt 0.685735 0.677015 +vt 0.686228 0.666838 +vt 0.741198 0.677015 +vt 0.688358 0.652787 +vt 0.697308 0.643104 +vt 0.734468 0.653528 +vt 0.739084 0.666015 +vt 0.710741 0.639927 +vt 0.725417 0.642145 +vt 0.794526 0.663780 +vt 0.769330 0.578484 +vt 0.775733 0.594543 +vt 0.794904 0.677014 +vt 0.641034 0.610282 +vt 0.666728 0.538248 +vt 0.636356 0.677015 +vn 0.9457 0.1443 0.2913 +vn 0.9564 -0.0041 0.2922 +vn 0.9998 -0.0209 -0.0009 +vn 0.9824 0.1864 0.0134 +vn 0.7379 0.6737 0.0405 +vn 0.7078 0.6423 0.2941 +vn 0.6460 0.4611 0.6084 +vn 0.7695 0.1073 0.6296 +vn 0.7886 0.0331 0.6140 +vn 0.3103 0.2935 -0.9042 +vn 0.3025 0.7212 -0.6232 +vn 0.6054 0.6169 -0.5030 +vn 0.7379 0.2250 -0.6362 +vn 0.7562 -0.0103 -0.6543 +vn 0.3308 0.0001 -0.9437 +vn 0.1084 0.3109 -0.9442 +vn 0.0989 0.0136 -0.9950 +vn -0.0000 0.0197 -0.9998 +vn -0.0000 0.3126 -0.9499 +vn -0.0000 0.7558 -0.6548 +vn 0.1472 0.7442 -0.6515 +vn 0.3550 -0.2096 -0.9110 +vn 0.7477 -0.1850 -0.6378 +vn 0.6791 -0.4538 -0.5770 +vn 0.3752 -0.5647 -0.7350 +vn 0.1368 -0.2014 -0.9699 +vn 0.1695 -0.6011 -0.7810 +vn 0.0000 -0.6130 -0.7901 +vn -0.0000 -0.2004 -0.9797 +vn 0.4474 -0.0538 0.8927 +vn 0.4742 -0.5231 0.7082 +vn 0.7731 -0.4655 0.4308 +vn 0.8083 -0.0705 0.5845 +vn 0.4476 0.0625 0.8920 +vn 0.1322 -0.0587 0.9895 +vn 0.1444 0.0607 0.9877 +vn -0.0000 0.0525 0.9986 +vn 0.0000 -0.0698 0.9976 +vn 0.0000 -0.6245 0.7810 +vn 0.2144 -0.6358 0.7415 +vn 0.4047 0.1590 0.9005 +vn 0.3492 0.5807 0.7354 +vn 0.1784 0.2024 0.9629 +vn 0.2000 0.6209 0.7580 +vn 0.0000 0.6376 0.7704 +vn -0.0000 0.1969 0.9804 +vn 0.9553 -0.1362 0.2625 +vn 0.8333 -0.5293 0.1596 +vn 0.8217 -0.5659 -0.0678 +vn 0.9850 -0.1721 -0.0131 +vn 0.9458 -0.1792 -0.2708 +vn 0.7735 -0.5442 -0.3250 +vn 0.9657 -0.0170 -0.2589 +vn 0.9520 0.1879 -0.2416 +vn 0.7174 0.6660 -0.2046 +vn 0.2950 0.9058 0.3042 +vn 0.3086 0.9496 0.0553 +vn 0.1702 0.9347 0.3120 +vn 0.1370 0.9891 0.0545 +vn 0.0000 0.9989 0.0475 +vn 0.0000 0.9489 0.3155 +vn 0.2964 0.9341 -0.1988 +vn 0.1395 0.9681 -0.2083 +vn 0.0000 0.9774 -0.2113 +vn 0.5914 -0.8002 -0.0998 +vn 0.4555 -0.8719 0.1795 +vn 0.6394 -0.6842 0.3507 +vn 0.8795 -0.4694 -0.0788 +vn 0.4438 -0.7827 -0.4363 +vn 0.1549 -0.8154 -0.5578 +vn 0.0000 -0.8224 -0.5689 +vn 0.2065 -0.9492 0.2376 +vn -0.0000 -0.9727 0.2319 +vn 0.6646 -0.5276 -0.5291 +vn 0.2417 -0.5411 -0.8054 +vn 0.7131 0.6934 0.1032 +vn 0.5830 0.4427 -0.6813 +vn 0.3650 -0.6772 0.6388 +vn -0.0000 -0.7727 0.6348 +vn 0.0000 0.2392 0.9710 +vn 0.3422 0.2291 0.9113 +vn 0.2187 0.9685 0.1193 +vn 0.2776 0.7794 -0.5617 +vn 0.0000 0.4232 0.9060 +vn 0.2215 0.4915 0.8423 +vn 0.5299 0.4783 0.7003 +vn 0.3187 0.1528 -0.9355 +vn 0.2344 0.7167 0.6568 +vn 0.2099 0.4105 -0.8874 +vn 0.3673 -0.9301 0.0098 +vn 0.3974 -0.5924 -0.7008 +vn 0.1794 -0.6195 -0.7642 +vn 0.0690 -0.9969 -0.0385 +vn 0.1877 0.1073 -0.9763 +vn 0.2476 0.0854 -0.9651 +vn 0.4933 -0.2459 -0.8344 +vn 0.2022 -0.1020 -0.9740 +vn 0.2329 0.2954 0.9266 +vn 0.0000 0.3022 0.9532 +vn -0.0000 0.1792 0.9838 +vn 0.2403 0.1796 0.9539 +vn 0.2128 0.2478 0.9452 +vn 0.4914 -0.2255 0.8412 +vn 0.1337 -0.5254 0.8403 +vn 0.1148 0.2277 0.9669 +vn 0.2362 0.0566 0.9700 +vn 0.0000 0.0526 0.9986 +vn -0.0009 -0.0926 0.9957 +vn 0.2385 -0.0982 0.9662 +vn 0.7220 0.1360 -0.6784 +vn 0.9918 0.1212 0.0408 +vn 0.9923 0.0130 0.1231 +vn 0.7610 0.0535 -0.6465 +vn 0.6859 0.0936 0.7216 +vn 0.6979 -0.1956 0.6890 +vn 0.2849 0.1291 -0.9498 +vn 0.2636 0.0932 -0.9601 +vn 0.2253 -0.1212 -0.9667 +vn 0.6874 -0.1083 -0.7181 +vn 0.7076 0.0146 -0.7065 +vn 0.2536 0.0037 -0.9673 +vn 0.9995 -0.0294 0.0078 +vn 0.6908 0.0535 0.7211 +vn 0.6725 0.0801 0.7358 +vn 0.9981 0.0555 0.0252 +vn 0.2430 0.0914 0.9657 +vn -0.0000 0.0953 0.9955 +vn 0.0000 0.0740 0.9973 +vn 0.2426 0.0741 0.9673 +vn 0.2022 0.9739 0.1031 +vn 0.2013 0.6974 0.6878 +vn 0.1541 0.6819 0.7150 +vn 0.1655 0.9817 0.0947 +vn 0.3004 0.0419 -0.9529 +vn 0.1688 0.0258 -0.9853 +vn 0.4500 -0.1523 0.8799 +vn -0.4454 -0.1944 0.8740 +vn -0.3460 -0.2779 0.8962 +vn 0.3766 -0.1964 0.9053 +vn 0.5997 -0.3336 0.7274 +vn 0.8694 -0.0087 -0.4940 +vn 0.0000 0.3057 -0.9521 +vn 0.0000 0.1113 -0.9938 +vn 0.2453 0.2787 0.9285 +vn 0.0000 -0.1176 -0.9931 +vn 0.0000 0.0149 -0.9999 +vn -0.0000 0.1493 -0.9888 +vn 0.0000 0.0659 -0.9978 +vn 0.6091 0.0343 0.7924 +vn 0.0000 -0.0920 -0.9958 +vn 0.0000 0.1404 -0.9901 +vn 0.0000 -0.4846 -0.8747 +vn 0.1067 -0.4160 0.9031 +vn 0.3116 -0.0248 -0.9499 +vn -0.0013 -0.6103 0.7922 +vn 0.0003 -0.9575 0.2883 +vn -0.0006 -0.9977 -0.0685 +vn -0.5755 -0.7952 -0.1911 +vn -0.5898 -0.6462 0.4843 +vn -0.0011 -0.1368 -0.9906 +vn -0.1764 -0.3704 -0.9120 +vn -0.0020 -0.7436 -0.6687 +vn -0.9340 -0.1822 0.3072 +vn -0.9184 0.0606 -0.3909 +vn -0.9107 0.0462 -0.4104 +vn -0.9365 -0.1814 0.3002 +vn -0.9426 -0.1215 0.3112 +vn -0.9450 -0.1377 -0.2966 +vn -0.9243 -0.0652 -0.3762 +vn -0.9228 -0.2105 0.3226 +vn 0.8756 0.0396 -0.4815 +vn 0.9476 -0.0151 0.3190 +vn 0.9181 -0.0664 0.3906 +vn 0.9077 0.0723 -0.4133 +vn -0.4433 -0.1404 -0.8853 +vn 0.3698 -0.0259 -0.9287 +vn 0.3708 0.1432 -0.9176 +vn -0.4292 0.0769 -0.8999 +vn 0.8919 0.1157 -0.4372 +vn 0.9256 -0.1284 0.3562 +vn 0.9305 -0.2256 0.2887 +vn 0.8784 -0.1012 -0.4671 +vn 0.8901 0.1775 -0.4199 +vn 0.9257 -0.0826 0.3690 +vn -0.3939 0.2181 -0.8929 +vn -0.4115 0.2799 -0.8674 +vn 0.3324 0.2377 -0.9127 +vn 0.3702 -0.2797 0.8858 +vn -0.3684 -0.3290 0.8695 +vn -0.3734 -0.3396 0.8633 +vn 0.3699 -0.3096 0.8760 +vn -0.3218 -0.2828 0.9036 +vn -0.9240 -0.1780 0.3384 +vn -0.9034 -0.0051 0.4289 +vn -0.1555 0.0783 0.9847 +vn 0.2807 -0.0015 -0.9598 +vn -0.4512 0.0172 -0.8923 +vn 0.4510 -0.2961 0.8420 +vn -0.9203 -0.0430 -0.3889 +vn -0.9785 0.0388 -0.2025 +vn 0.1337 0.2725 -0.9528 +vn -0.6002 0.0760 -0.7962 +vn 0.6684 0.1792 0.7219 +vn 0.9807 0.1591 0.1132 +vn 0.0231 -0.5921 0.8055 +vn -0.0441 -0.9976 -0.0530 +vn -0.0785 -0.9958 -0.0464 +vn -0.0514 -0.6426 0.7645 +vn 0.1031 0.6834 0.7227 +vn 0.0614 0.1922 0.9794 +vn -0.0142 0.1514 0.9884 +vn 0.0418 0.7022 0.7108 +vn 0.0286 0.0423 -0.9987 +vn 0.1078 0.7277 -0.6773 +vn 0.0901 0.7629 -0.6402 +vn 0.0343 0.0455 -0.9984 +vn 0.1349 0.9851 0.1063 +vn 0.0795 0.9895 0.1207 +vn 0.2886 0.7054 -0.6474 +vn 0.2213 0.7031 -0.6758 +vn 0.1543 0.8126 -0.5621 +vn 0.1374 0.9816 0.1329 +vn 0.3013 0.9452 0.1258 +vn 0.1981 0.8835 -0.4245 +vn 0.0229 -0.9997 -0.0021 +vn 0.0623 -0.6655 -0.7438 +vn 0.0341 -0.7440 -0.6674 +vn 0.1430 -0.9897 0.0080 +vn 0.0423 0.1045 0.9936 +vn 0.0253 -0.6320 0.7746 +vn 0.0240 -0.7308 0.6822 +vn -0.3127 0.2099 0.9264 +vn 0.0965 0.0398 -0.9945 +vn -0.1350 0.1050 -0.9853 +vn -0.0256 -0.6368 -0.7706 +vn -0.0322 -0.6542 -0.7556 +vn 0.1349 0.8453 0.5169 +vn -0.4932 0.4930 0.7168 +vn -0.0139 0.8416 0.5400 +vn 0.0750 0.8493 -0.5225 +vn -0.1172 0.0807 -0.9898 +vn 0.2518 0.9644 0.0807 +vn -0.1514 -0.7066 -0.6913 +vn 0.0599 -0.9980 0.0190 +vn 0.1047 0.7466 0.6570 +vn 0.0280 0.9910 0.1306 +vn -0.0336 0.7148 0.6986 +vn 0.0289 0.7181 0.6953 +vn 0.0847 0.9874 0.1334 +vn -0.1332 -0.6247 0.7694 +vn -0.1064 -0.9942 -0.0166 +vn -0.0407 -0.9991 0.0079 +vn -0.0547 -0.6278 0.7764 +vn -0.0227 0.1091 0.9938 +vn 0.0379 0.0500 -0.9980 +vn 0.0584 0.7850 -0.6168 +vn 0.0999 0.7937 -0.6001 +vn 0.0656 0.0439 -0.9969 +vn -0.0380 -0.6635 -0.7472 +vn 0.0085 -0.6713 -0.7411 +vn 0.8732 -0.4872 0.0139 +vn 0.1845 0.8730 0.4514 +vn 0.2209 0.9735 0.0600 +vn -0.5726 -0.6425 0.5092 +vn -0.1516 -0.9884 -0.0104 +vn 0.1681 -0.8946 0.4139 +vn 0.3284 0.3520 0.8765 +vn 0.1938 0.8999 -0.3906 +vn 0.0779 0.1056 -0.9914 +vn 0.2088 0.0236 -0.9777 +vn 0.4326 0.8074 -0.4012 +vn 0.8096 0.4726 -0.3482 +vn 0.6046 -0.1712 -0.7779 +vn 0.4070 0.9114 0.0607 +vn 0.9199 0.3892 0.0477 +vn -0.3275 -0.9444 -0.0292 +vn -0.1998 -0.8212 -0.5345 +vn 0.2486 -0.8576 -0.4502 +vn 0.4146 -0.9085 0.0525 +vn 0.0541 0.2120 0.9758 +vn -0.2886 -0.7432 0.6036 +vn 0.1971 -0.7858 0.5863 +vn 0.5629 -0.0268 0.8261 +vn -0.0910 -0.8166 -0.5699 +vn 0.5147 0.8181 0.2567 +vn 0.2374 -0.8666 -0.4388 +vn 0.9324 -0.3612 0.0109 +vn 0.7483 0.4975 0.4388 +vn 0.3372 0.8276 0.4488 +vn -0.1555 -0.8122 -0.5622 +vn 0.0736 0.0760 -0.9944 +vn 0.1485 0.0722 -0.9863 +vn -0.1638 -0.8029 -0.5731 +vn 0.3115 0.9481 0.0636 +vn 0.3176 0.8353 0.4488 +vn 0.3268 0.8416 0.4299 +vn 0.3667 0.9284 0.0598 +vn -0.0652 -0.7833 0.6183 +vn -0.1876 -0.9821 -0.0192 +vn -0.2699 -0.9624 -0.0318 +vn -0.1672 -0.7777 0.6060 +vn 0.2008 0.1620 0.9661 +vn 0.1350 0.2102 0.9683 +vn 0.2863 0.8648 -0.4125 +vn 0.3527 0.8371 -0.4183 +vn -0.5104 0.6254 0.5902 +vn -0.7359 -0.6521 0.1822 +vn -0.5788 -0.7442 0.3334 +vn -0.4153 0.5623 0.7151 +vn 0.3722 -0.5937 -0.7135 +vn 0.7782 -0.6218 -0.0880 +vn 0.0267 -0.7229 0.6905 +vn 0.5714 0.7000 -0.4283 +vn 0.1233 0.3691 0.9212 +vn 0.8710 0.4712 0.1392 +vn 0.8376 0.2166 -0.5015 +vn -0.0000 -1.0000 0.0000 +vn 0.0084 -0.6750 -0.7378 +vn 0.3680 -0.6369 -0.6774 +vn 0.6015 -0.6121 -0.5133 +vn -0.6355 -0.6986 -0.3287 +vn -0.3370 -0.7295 -0.5952 +vn -0.7034 -0.7107 -0.0097 +vn -0.5972 -0.8019 0.0184 +vn 0.6442 -0.7370 -0.2048 +vn 0.6648 -0.7399 -0.1029 +vn 0.6664 -0.7454 0.0181 +vn 0.6517 -0.7303 0.2050 +vn -0.5592 -0.6963 0.4499 +vn -0.6826 -0.7042 0.1955 +vn -0.6484 -0.7443 0.1597 +vn -0.6932 -0.7206 0.0148 +vn 0.7022 -0.6799 -0.2114 +vn 0.6342 -0.7655 -0.1091 +vn -0.2498 -0.6440 0.7231 +vn 0.1061 -0.6663 0.7381 +vn 0.4440 -0.6813 0.5820 +vn 0.6185 -0.7625 -0.1899 +vn -0.6115 -0.7763 0.1532 +vn 0.6837 0.0752 0.7259 +vn 0.1525 0.1246 0.9804 +vn 0.4903 0.1374 -0.8606 +vn 0.7599 0.1599 -0.6301 +vn -0.9665 0.0230 0.2556 +vn -0.9782 0.0452 0.2028 +vn 0.9493 0.0983 -0.2986 +vn 0.9839 0.0914 -0.1536 +vn 0.9960 0.0812 0.0373 +vn 0.9159 0.1392 -0.3765 +vn 0.9780 0.0775 -0.1938 +vn -0.7453 0.1106 0.6575 +vn -0.9495 0.1270 0.2870 +vn -0.9970 0.0745 -0.0194 +vn -0.8740 0.0598 -0.4823 +vn -0.9951 0.0798 0.0585 +vn -0.8303 0.5511 0.0824 +vn -0.8523 0.3910 0.3476 +vn -0.9916 0.1283 0.0146 +vn -0.8484 0.5290 -0.0195 +vn -0.7014 0.4470 0.5552 +vn -0.8576 0.4754 0.1962 +vn 0.9409 0.0844 0.3281 +vn 0.8080 0.4997 0.3121 +vn 0.4611 0.4221 0.7805 +vn 0.8317 0.4977 -0.2460 +vn 0.8603 0.4813 -0.1678 +vn 0.7603 0.2592 -0.5956 +vn 0.8765 0.3184 -0.3611 +vn 0.0027 0.0640 -0.9979 +vn 0.0179 0.2134 -0.9768 +vn 0.5328 0.2307 -0.8142 +vn -0.3109 0.0989 0.9453 +vn -0.2355 0.3923 0.8892 +vn -0.9029 0.1928 -0.3842 +vn -0.5381 0.2055 -0.8174 +vn -0.5082 0.0300 -0.8607 +vn -0.9301 0.2315 0.2851 +vn -0.9798 0.1938 -0.0499 +vn 0.8834 0.4680 0.0259 +vn 0.9585 0.0708 -0.2762 +vn 0.8740 0.4463 -0.1921 +vn 0.9188 0.3598 -0.1622 +vn -0.8756 0.2043 0.4377 +vn -0.9977 0.0637 -0.0225 +vn 0.4477 0.8941 0.0123 +vn 0.4095 0.8844 0.2238 +vn 0.4740 0.2525 -0.8436 +vn 0.7825 0.3019 -0.5445 +vn 0.3908 0.8872 0.2453 +vn 0.4131 0.9101 -0.0329 +vn 0.7671 0.6409 -0.0280 +vn -0.5579 0.2025 -0.8048 +vn -0.0554 0.2166 -0.9747 +vn 0.4242 0.8999 -0.1010 +vn -0.9151 0.1009 -0.3904 +vn -0.3681 0.9119 0.1818 +vn -0.4525 0.6615 0.5981 +vn -0.3940 0.9187 -0.0283 +vn -0.4085 0.8532 0.3243 +vn -0.5208 0.8521 0.0525 +vn 0.2601 0.7859 0.5609 +vn 0.8501 0.4447 -0.2822 +vn -0.1194 0.7620 0.6365 +vn -0.8841 0.1773 -0.4324 +vn -0.4165 0.3095 -0.8548 +vn 0.3278 0.3994 -0.8562 +vn 0.6951 0.4915 -0.5247 +vn -0.9970 0.0755 -0.0137 +vn -0.1212 0.4521 -0.8837 +vn -0.1949 0.4563 0.8682 +vn -0.7707 0.2211 0.5976 +vn 0.8312 0.5333 -0.1575 +vn 0.6322 0.7536 0.1800 +vn 0.2893 0.7521 0.5922 +vn -0.9457 0.1443 0.2913 +vn -0.9824 0.1864 0.0134 +vn -0.9998 -0.0209 -0.0009 +vn -0.9564 -0.0041 0.2922 +vn -0.7078 0.6423 0.2941 +vn -0.7379 0.6737 0.0405 +vn -0.7695 0.1073 0.6296 +vn -0.6460 0.4611 0.6084 +vn -0.7886 0.0331 0.6140 +vn -0.3103 0.2935 -0.9042 +vn -0.7379 0.2250 -0.6362 +vn -0.6054 0.6169 -0.5030 +vn -0.3025 0.7212 -0.6232 +vn -0.3308 0.0001 -0.9437 +vn -0.7562 -0.0103 -0.6543 +vn -0.1084 0.3109 -0.9442 +vn -0.0989 0.0136 -0.9950 +vn -0.1472 0.7442 -0.6515 +vn -0.3550 -0.2096 -0.9110 +vn -0.7477 -0.1850 -0.6378 +vn -0.3752 -0.5647 -0.7350 +vn -0.6791 -0.4538 -0.5770 +vn -0.1368 -0.2014 -0.9699 +vn -0.1695 -0.6011 -0.7810 +vn -0.4474 -0.0538 0.8927 +vn -0.8083 -0.0705 0.5845 +vn -0.7731 -0.4655 0.4308 +vn -0.4742 -0.5231 0.7082 +vn -0.4476 0.0625 0.8920 +vn -0.1322 -0.0587 0.9895 +vn -0.1444 0.0607 0.9877 +vn -0.2144 -0.6358 0.7415 +vn -0.4047 0.1590 0.9005 +vn -0.3492 0.5807 0.7354 +vn -0.1784 0.2024 0.9629 +vn -0.2000 0.6209 0.7580 +vn -0.9553 -0.1362 0.2625 +vn -0.9850 -0.1721 -0.0131 +vn -0.8217 -0.5659 -0.0678 +vn -0.8333 -0.5293 0.1596 +vn -0.9458 -0.1792 -0.2708 +vn -0.7735 -0.5442 -0.3250 +vn -0.9657 -0.0170 -0.2589 +vn -0.9520 0.1879 -0.2416 +vn -0.7174 0.6660 -0.2046 +vn -0.2950 0.9058 0.3042 +vn -0.3086 0.9496 0.0553 +vn -0.1702 0.9347 0.3120 +vn -0.1370 0.9891 0.0545 +vn -0.2964 0.9341 -0.1988 +vn -0.1395 0.9681 -0.2083 +vn -0.5914 -0.8002 -0.0998 +vn -0.8795 -0.4694 -0.0788 +vn -0.6394 -0.6842 0.3507 +vn -0.4555 -0.8719 0.1795 +vn -0.4438 -0.7827 -0.4363 +vn -0.1549 -0.8154 -0.5578 +vn -0.2065 -0.9492 0.2376 +vn -0.2417 -0.5411 -0.8054 +vn -0.6646 -0.5276 -0.5291 +vn -0.5830 0.4427 -0.6813 +vn -0.7131 0.6934 0.1032 +vn -0.3650 -0.6772 0.6388 +vn -0.3422 0.2291 0.9113 +vn -0.2776 0.7794 -0.5617 +vn -0.2187 0.9685 0.1193 +vn -0.2215 0.4915 0.8423 +vn -0.5299 0.4783 0.7003 +vn -0.3187 0.1528 -0.9355 +vn -0.2344 0.7167 0.6568 +vn -0.2099 0.4105 -0.8874 +vn -0.3673 -0.9301 0.0098 +vn -0.0690 -0.9969 -0.0385 +vn -0.1794 -0.6195 -0.7642 +vn -0.3974 -0.5924 -0.7008 +vn -0.1877 0.1073 -0.9763 +vn -0.2022 -0.1020 -0.9740 +vn -0.4933 -0.2459 -0.8344 +vn -0.2476 0.0854 -0.9651 +vn -0.2329 0.2954 0.9266 +vn -0.2403 0.1796 0.9539 +vn -0.2128 0.2478 0.9452 +vn -0.1148 0.2277 0.9669 +vn -0.1337 -0.5254 0.8403 +vn -0.4914 -0.2255 0.8412 +vn -0.2364 0.0565 0.9700 +vn -0.2399 -0.1022 0.9654 +vn -0.7225 0.1357 -0.6780 +vn -0.7618 0.0546 -0.6455 +vn -0.9917 0.0127 0.1276 +vn -0.9918 0.1202 0.0423 +vn -0.6966 -0.1992 0.6893 +vn -0.6855 0.0928 0.7222 +vn -0.2849 0.1291 -0.9498 +vn -0.2631 0.0936 -0.9602 +vn -0.2253 -0.1212 -0.9667 +vn -0.2536 0.0037 -0.9673 +vn -0.7077 0.0144 -0.7064 +vn -0.6874 -0.1083 -0.7181 +vn -0.9995 -0.0294 0.0078 +vn -0.9982 0.0549 0.0255 +vn -0.6726 0.0797 0.7357 +vn -0.6908 0.0535 0.7211 +vn -0.2430 0.0914 0.9657 +vn -0.2426 0.0740 0.9673 +vn -0.2022 0.9739 0.1031 +vn -0.1655 0.9817 0.0947 +vn -0.1541 0.6819 0.7150 +vn -0.2013 0.6974 0.6878 +vn -0.1688 0.0258 -0.9853 +vn -0.3004 0.0419 -0.9529 +vn -0.4369 -0.1528 0.8864 +vn -0.3610 -0.1963 0.9117 +vn 0.3613 -0.2783 0.8900 +vn 0.4588 -0.1943 0.8670 +vn -0.5930 -0.3361 0.7317 +vn -0.8740 -0.0068 -0.4859 +vn -0.2453 0.2787 0.9285 +vn -0.6091 0.0343 0.7924 +vn -0.1059 -0.4199 0.9014 +vn -0.3165 -0.0232 -0.9483 +vn 0.5913 -0.6474 0.4808 +vn 0.5748 -0.7942 -0.1971 +vn 0.1734 -0.3703 -0.9126 +vn 0.9391 -0.1828 0.2909 +vn 0.9414 -0.1820 0.2838 +vn 0.9035 0.0456 -0.4262 +vn 0.9115 0.0601 -0.4068 +vn 0.9472 -0.1220 0.2966 +vn 0.9282 -0.2111 0.3065 +vn 0.9176 -0.0657 -0.3922 +vn 0.9397 -0.1373 -0.3132 +vn -0.8826 0.0408 -0.4683 +vn -0.9147 0.0729 -0.3974 +vn -0.9113 -0.0659 0.4065 +vn -0.9426 -0.0146 0.3336 +vn 0.4306 -0.1368 -0.8921 +vn 0.4136 0.0768 -0.9072 +vn -0.3866 0.1436 -0.9110 +vn -0.3825 -0.0232 -0.9236 +vn -0.8993 0.1163 -0.4216 +vn -0.8859 -0.1006 -0.4528 +vn -0.9258 -0.2249 0.3039 +vn -0.9193 -0.1278 0.3722 +vn -0.8971 0.1781 -0.4043 +vn -0.9193 -0.0821 0.3850 +vn 0.3966 0.2797 -0.8743 +vn 0.3785 0.2178 -0.8996 +vn -0.3480 0.2379 -0.9068 +vn -0.3550 -0.2796 0.8921 +vn -0.3548 -0.3094 0.8823 +vn 0.3881 -0.3399 0.8567 +vn 0.3832 -0.3294 0.8629 +vn 0.3361 -0.2834 0.8982 +vn 0.1673 0.0823 0.9825 +vn 0.9087 -0.0107 0.4173 +vn 0.9291 -0.1794 0.3233 +vn 0.4369 0.0163 -0.8994 +vn -0.2959 -0.0015 -0.9552 +vn -0.4374 -0.2958 0.8492 +vn 0.9138 -0.0443 -0.4037 +vn 0.9765 0.0369 -0.2121 +vn 0.5910 0.0741 -0.8033 +vn -0.1439 0.2747 -0.9507 +vn -0.9780 0.1679 0.1240 +vn -0.6588 0.1851 0.7292 +vn -0.0231 -0.5921 0.8055 +vn 0.0514 -0.6426 0.7645 +vn 0.0785 -0.9958 -0.0464 +vn 0.0441 -0.9976 -0.0530 +vn -0.1031 0.6834 0.7227 +vn -0.0417 0.7022 0.7107 +vn 0.0142 0.1514 0.9884 +vn -0.0614 0.1922 0.9794 +vn -0.0286 0.0423 -0.9987 +vn -0.0343 0.0455 -0.9984 +vn -0.0901 0.7629 -0.6402 +vn -0.1078 0.7277 -0.6773 +vn -0.0795 0.9895 0.1207 +vn -0.1349 0.9851 0.1063 +vn -0.2213 0.7031 -0.6758 +vn -0.2886 0.7054 -0.6474 +vn -0.1543 0.8126 -0.5621 +vn -0.1981 0.8835 -0.4245 +vn -0.3013 0.9452 0.1258 +vn -0.1374 0.9816 0.1329 +vn -0.0229 -0.9997 -0.0021 +vn -0.1430 -0.9897 0.0080 +vn -0.0341 -0.7440 -0.6674 +vn -0.0623 -0.6655 -0.7438 +vn -0.0423 0.1045 0.9936 +vn 0.3127 0.2099 0.9264 +vn -0.0240 -0.7308 0.6822 +vn -0.0253 -0.6320 0.7746 +vn 0.1350 0.1050 -0.9853 +vn -0.0965 0.0398 -0.9945 +vn 0.0256 -0.6368 -0.7706 +vn 0.0322 -0.6542 -0.7556 +vn -0.1349 0.8453 0.5169 +vn 0.0139 0.8416 0.5400 +vn 0.4932 0.4930 0.7168 +vn 0.1172 0.0807 -0.9898 +vn -0.0750 0.8493 -0.5225 +vn -0.2518 0.9644 0.0807 +vn -0.0599 -0.9980 0.0190 +vn 0.1514 -0.7066 -0.6913 +vn -0.1047 0.7466 0.6570 +vn -0.0280 0.9910 0.1306 +vn -0.0847 0.9874 0.1334 +vn -0.0289 0.7181 0.6953 +vn 0.0336 0.7148 0.6986 +vn 0.1332 -0.6247 0.7694 +vn 0.0547 -0.6278 0.7764 +vn 0.0407 -0.9991 0.0079 +vn 0.1064 -0.9942 -0.0166 +vn 0.0227 0.1091 0.9938 +vn -0.0379 0.0500 -0.9980 +vn -0.0656 0.0439 -0.9969 +vn -0.0999 0.7937 -0.6001 +vn -0.0584 0.7850 -0.6168 +vn 0.0380 -0.6635 -0.7472 +vn -0.0085 -0.6713 -0.7411 +vn -0.8732 -0.4872 0.0139 +vn -0.2209 0.9735 0.0600 +vn -0.1845 0.8730 0.4514 +vn 0.5726 -0.6425 0.5092 +vn -0.1681 -0.8946 0.4139 +vn 0.1516 -0.9884 -0.0104 +vn -0.3284 0.3520 0.8765 +vn -0.0779 0.1056 -0.9914 +vn -0.1938 0.8999 -0.3906 +vn -0.2088 0.0236 -0.9777 +vn -0.6045 -0.1712 -0.7779 +vn -0.8096 0.4726 -0.3482 +vn -0.4326 0.8074 -0.4012 +vn -0.9199 0.3892 0.0477 +vn -0.4070 0.9114 0.0607 +vn 0.3275 -0.9444 -0.0292 +vn -0.4146 -0.9085 0.0526 +vn -0.2486 -0.8576 -0.4502 +vn 0.1998 -0.8212 -0.5345 +vn -0.0541 0.2120 0.9758 +vn -0.5629 -0.0268 0.8261 +vn -0.1971 -0.7858 0.5863 +vn 0.2886 -0.7432 0.6036 +vn 0.0910 -0.8166 -0.5699 +vn -0.2374 -0.8666 -0.4388 +vn -0.5147 0.8181 0.2567 +vn -0.9324 -0.3612 0.0109 +vn -0.7483 0.4975 0.4388 +vn -0.3372 0.8276 0.4487 +vn 0.1555 -0.8122 -0.5622 +vn 0.1638 -0.8029 -0.5731 +vn -0.1485 0.0722 -0.9863 +vn -0.0736 0.0760 -0.9944 +vn -0.3115 0.9481 0.0636 +vn -0.3667 0.9284 0.0598 +vn -0.3268 0.8416 0.4299 +vn -0.3176 0.8353 0.4488 +vn 0.0652 -0.7833 0.6183 +vn 0.1672 -0.7777 0.6060 +vn 0.2699 -0.9624 -0.0318 +vn 0.1876 -0.9821 -0.0192 +vn -0.1350 0.2102 0.9683 +vn -0.2008 0.1620 0.9661 +vn -0.2863 0.8648 -0.4125 +vn -0.3527 0.8371 -0.4183 +vn 0.5104 0.6254 0.5902 +vn 0.4153 0.5623 0.7151 +vn 0.5788 -0.7442 0.3334 +vn 0.7359 -0.6521 0.1822 +vn -0.0267 -0.7229 0.6905 +vn -0.7782 -0.6218 -0.0880 +vn -0.3722 -0.5937 -0.7135 +vn -0.5714 0.7000 -0.4283 +vn -0.1233 0.3691 0.9212 +vn -0.8710 0.4712 0.1392 +vn -0.8425 0.2212 -0.4912 +vn -0.6016 -0.6121 -0.5133 +vn -0.3680 -0.6369 -0.6774 +vn -0.0084 -0.6750 -0.7378 +vn 0.6355 -0.6986 -0.3287 +vn 0.3370 -0.7295 -0.5952 +vn 0.7034 -0.7107 -0.0097 +vn 0.5971 -0.8019 0.0184 +vn -0.6648 -0.7399 -0.1029 +vn -0.6442 -0.7370 -0.2047 +vn -0.6517 -0.7302 0.2050 +vn -0.6664 -0.7454 0.0181 +vn 0.5592 -0.6963 0.4499 +vn 0.6826 -0.7042 0.1954 +vn 0.6484 -0.7443 0.1597 +vn 0.6931 -0.7206 0.0148 +vn -0.6342 -0.7655 -0.1091 +vn -0.7022 -0.6799 -0.2114 +vn 0.2498 -0.6440 0.7231 +vn -0.1061 -0.6663 0.7381 +vn -0.4440 -0.6813 0.5820 +vn -0.6185 -0.7625 -0.1899 +vn 0.6115 -0.7763 0.1532 +vn -0.1525 0.1246 0.9804 +vn -0.6837 0.0753 0.7259 +vn -0.7599 0.1599 -0.6301 +vn -0.4904 0.1374 -0.8606 +vn 0.9782 0.0451 0.2028 +vn 0.9665 0.0229 0.2556 +vn -0.9839 0.0914 -0.1535 +vn -0.9493 0.0983 -0.2986 +vn -0.9960 0.0812 0.0373 +vn -0.9780 0.0775 -0.1937 +vn -0.9159 0.1393 -0.3765 +vn 0.9495 0.1269 0.2870 +vn 0.7453 0.1105 0.6575 +vn 0.9970 0.0744 -0.0194 +vn 0.8740 0.0597 -0.4823 +vn 0.9951 0.0798 0.0585 +vn 0.8523 0.3909 0.3475 +vn 0.8304 0.5511 0.0824 +vn 0.9916 0.1283 0.0146 +vn 0.8484 0.5290 -0.0195 +vn 0.8577 0.4753 0.1961 +vn 0.7014 0.4469 0.5552 +vn -0.9409 0.0844 0.3281 +vn -0.4611 0.4221 0.7805 +vn -0.8080 0.4997 0.3121 +vn -0.8603 0.4813 -0.1678 +vn -0.8317 0.4977 -0.2460 +vn -0.8765 0.3184 -0.3610 +vn -0.7604 0.2592 -0.5955 +vn -0.0027 0.0640 -0.9979 +vn -0.5328 0.2307 -0.8142 +vn -0.0179 0.2134 -0.9768 +vn 0.3109 0.0989 0.9453 +vn 0.2355 0.3923 0.8892 +vn 0.5083 0.0300 -0.8607 +vn 0.5382 0.2055 -0.8174 +vn 0.9029 0.1928 -0.3841 +vn 0.9798 0.1938 -0.0499 +vn 0.9302 0.2314 0.2851 +vn -0.8834 0.4680 0.0259 +vn -0.9585 0.0708 -0.2762 +vn -0.8740 0.4463 -0.1920 +vn -0.9188 0.3598 -0.1622 +vn 0.9977 0.0636 -0.0225 +vn 0.8756 0.2043 0.4376 +vn -0.4095 0.8845 0.2238 +vn -0.4477 0.8941 0.0123 +vn -0.7826 0.3019 -0.5445 +vn -0.4740 0.2525 -0.8436 +vn -0.4131 0.9101 -0.0329 +vn -0.3908 0.8872 0.2454 +vn -0.7671 0.6409 -0.0280 +vn 0.0554 0.2166 -0.9747 +vn 0.5579 0.2025 -0.8048 +vn -0.4242 0.8999 -0.1010 +vn 0.9151 0.1009 -0.3904 +vn 0.4525 0.6614 0.5981 +vn 0.3681 0.9118 0.1818 +vn 0.3940 0.9187 -0.0283 +vn 0.5208 0.8521 0.0525 +vn 0.4086 0.8532 0.3243 +vn -0.2601 0.7859 0.5609 +vn -0.8501 0.4447 -0.2822 +vn 0.1194 0.7620 0.6365 +vn 0.4148 0.3089 -0.8559 +vn 0.8842 0.1771 -0.4323 +vn -0.6931 0.4946 -0.5244 +vn -0.3279 0.4006 -0.8556 +vn 0.9973 0.0714 -0.0155 +vn 0.1210 0.4544 -0.8825 +vn 0.7698 0.2210 0.5987 +vn 0.1922 0.4608 0.8664 +vn -0.8281 0.5387 -0.1555 +vn -0.6279 0.7573 0.1795 +vn -0.2868 0.7541 0.5909 +usemtl skin +s 1 +f 1/1/1 27/2/2 41/3/3 30/4/4 +f 1/1/1 30/4/4 40/5/5 16/6/6 +f 1/1/1 16/6/6 36/7/7 15/8/8 +f 1/1/1 15/8/8 39/9/9 27/2/2 +f 2/10/10 23/11/11 35/12/12 14/13/13 +f 2/10/10 14/13/13 38/14/14 25/15/15 +f 65/16/16 77/17/17 380/18/18 353/19/19 +f 65/16/16 353/19/19 378/20/20 75/21/21 +f 3/22/22 25/15/15 38/14/14 19/23/23 +f 3/22/22 19/23/23 33/24/24 18/25/25 +f 66/26/26 73/27/27 373/28/28 354/29/29 +f 66/26/26 354/29/29 380/18/18 77/17/17 +f 4/30/30 20/31/31 34/32/32 21/33/33 +f 4/30/30 21/33/33 39/9/9 26/34/34 +f 67/35/35 78/36/36 381/37/37 356/38/38 +f 67/35/35 356/38/38 375/39/39 74/40/40 +f 5/41/41 26/34/34 39/9/9 15/8/8 +f 5/41/41 15/8/8 36/7/7 24/42/42 +f 68/43/43 76/44/44 379/45/45 357/46/46 +f 68/43/43 357/46/46 381/37/37 78/36/36 +f 6/47/47 13/48/48 37/49/49 29/50/50 +f 6/47/47 29/50/50 41/3/3 27/2/2 +f 6/47/47 27/2/2 39/9/9 21/33/33 +f 6/47/47 21/33/33 34/32/32 13/48/48 +f 7/51/51 17/52/52 33/24/24 19/23/23 +f 7/51/51 19/23/23 38/14/14 28/53/53 +f 7/51/51 28/53/53 41/3/3 29/50/50 +f 7/51/51 29/50/50 37/49/49 17/52/52 +f 8/54/54 28/53/53 38/14/14 14/13/13 +f 8/54/54 14/13/13 35/12/12 22/55/55 +f 8/54/54 22/55/55 40/5/5 30/4/4 +f 8/54/54 30/4/4 41/3/3 28/53/53 +f 9/56/56 16/6/6 40/5/5 31/57/57 +f 69/58/58 79/59/59 383/60/60 362/61/61 +f 69/62/58 362/63/61 379/45/45 76/44/44 +f 9/64/56 24/42/42 36/7/7 16/6/6 +f 10/65/62 22/55/55 35/12/12 23/11/11 +f 70/66/63 75/21/21 378/20/20 363/67/64 +f 70/66/63 363/67/64 383/60/60 79/59/59 +f 10/65/62 31/57/57 40/5/5 22/55/55 +f 32/68/65 12/69/66 44/70/67 43/71/68 +f 11/72/69 18/25/25 33/24/24 17/52/52 +f 11/72/69 17/52/52 37/49/49 32/68/65 +f 71/73/70 366/74/71 373/28/28 73/27/27 +f 12/69/66 32/68/65 37/49/49 13/48/48 +f 12/69/66 13/48/48 34/32/32 20/31/31 +f 72/75/72 74/40/40 375/39/39 367/76/73 +f 71/73/70 11/72/69 42/77/74 80/78/75 +f 42/77/74 43/71/68 46/79/76 45/80/77 +f 81/81/78 384/82/79 389/83/80 83/84/81 +f 11/72/69 32/68/65 43/71/68 42/77/74 +f 72/75/72 367/76/73 384/82/79 81/81/78 +f 45/85/77 46/86/76 49/87/82 48/88/83 +f 83/89/81 389/90/80 394/91/84 85/92/85 +f 43/71/68 44/70/67 47/93/86 46/79/76 +f 80/78/75 42/77/74 45/80/77 82/94/87 +f 46/86/76 47/95/86 50/96/88 49/87/82 +f 82/97/87 45/85/77 48/88/83 84/98/89 +f 169/99/90 165/100/91 133/101/92 134/102/93 +f 93/103/94 61/104/95 51/105/96 86/106/97 +f 92/107/98 417/108/99 399/109/100 87/110/101 +f 168/111/102 167/112/103 135/113/104 136/114/105 +f 741/115/106 735/116/107 404/117/108 89/118/109 +f 739/119/110 736/120/111 55/121/112 54/122/113 +f 736/123/111 738/124/114 56/125/115 55/126/112 +f 740/127/116 739/119/110 54/122/113 88/128/117 +f 753/129/118 748/130/119 59/131/120 90/132/121 +f 750/133/122 747/134/123 57/135/124 58/136/125 +f 748/130/119 750/137/122 58/138/125 59/131/120 +f 752/139/126 749/140/127 409/141/128 91/142/129 +f 163/143/130 164/144/131 132/145/132 131/146/133 +f 85/92/85 394/91/84 417/108/99 92/107/98 +f 84/98/89 48/88/83 61/104/95 93/103/94 +f 165/100/91 166/147/134 138/148/135 133/101/92 +f 119/149/136 106/150/137 95/151/138 113/152/139 +f 55/153/112 56/154/115 111/155/140 +f 55/156/112 121/157/141 54/158/113 +f 395/159/142 84/98/89 93/103/94 416/160/143 +f 50/96/88 85/92/85 92/107/98 60/161/144 +f 747/134/123 752/139/126 91/142/129 57/135/124 +f 751/162/145 753/129/118 90/132/121 413/163/146 +f 737/164/147 740/127/116 88/128/117 405/165/148 +f 738/124/114 741/115/106 89/118/109 56/125/115 +f 60/161/144 92/107/98 87/110/101 53/166/149 +f 416/160/143 93/103/94 86/106/97 400/167/150 +f 390/168/151 82/97/87 84/98/89 395/159/142 +f 385/169/152 80/78/75 82/94/87 390/170/151 +f 47/95/86 83/89/81 85/92/85 50/96/88 +f 12/69/66 72/75/72 81/81/78 44/70/67 +f 44/70/67 81/81/78 83/84/81 47/93/86 +f 366/74/71 71/73/70 80/78/75 385/169/152 +f 12/69/66 20/31/31 74/40/40 72/75/72 +f 11/72/69 71/73/70 73/27/27 18/25/25 +f 10/65/62 70/66/63 79/59/59 31/57/57 +f 10/65/62 23/11/11 75/21/21 70/66/63 +f 9/64/56 69/62/58 76/44/44 24/42/42 +f 9/56/56 31/57/57 79/59/59 69/58/58 +f 5/41/41 68/43/43 78/36/36 26/34/34 +f 5/41/41 24/42/42 76/44/44 68/43/43 +f 4/30/30 67/35/35 74/40/40 20/31/31 +f 4/30/30 26/34/34 78/36/36 67/35/35 +f 3/22/22 66/26/26 77/17/17 25/15/15 +f 3/22/22 18/25/25 73/27/27 66/26/26 +f 2/10/10 65/16/16 75/21/21 23/11/11 +f 2/10/10 25/15/15 77/17/17 65/16/16 +f 62/171/153 56/154/115 89/172/109 +f 64/173/154 88/174/117 54/158/113 +f 404/175/108 451/176/155 62/171/153 89/172/109 +f 482/177/156 421/178/157 63/179/158 120/180/159 +f 405/181/148 88/174/117 64/173/154 452/182/160 +f 110/183/161 63/184/158 421/185/157 471/186/162 +f 797/187/163 791/188/164 98/189/165 124/190/166 +f 129/191/167 108/192/168 94/193/169 122/194/170 +f 128/195/171 107/196/172 97/197/173 123/198/174 +f 118/199/175 109/200/176 96/201/177 112/202/178 +f 125/203/179 101/204/180 105/205/181 127/206/182 +f 796/207/183 793/208/184 101/204/180 125/203/179 +f 114/209/185 795/210/186 100/211/187 +f 794/212/188 792/213/189 99/214/190 115/215/191 +f 103/216/192 126/217/193 216/218/194 327/219/195 +f 114/209/185 100/211/187 104/220/196 116/221/197 +f 115/215/191 99/214/190 103/216/192 117/222/198 +f 124/190/166 98/189/165 102/223/199 126/217/193 +f 110/183/161 64/173/154 109/200/176 118/199/175 +f 121/157/141 55/156/112 107/196/172 128/195/171 +f 120/180/159 63/179/158 108/192/168 129/191/167 +f 111/155/140 62/171/153 106/150/137 119/149/136 +f 55/153/112 111/155/140 119/149/136 107/224/172 +f 63/184/158 110/183/161 118/199/175 108/225/168 +f 101/226/180 115/215/191 117/222/198 105/227/181 +f 98/228/165 114/209/185 116/221/197 102/229/199 +f 126/217/193 102/223/199 220/230/200 216/218/194 +f 793/231/184 794/212/188 115/215/191 101/226/180 +f 791/232/164 795/210/186 114/209/185 98/228/165 +f 108/225/168 118/199/175 112/202/178 94/233/169 +f 64/173/154 110/183/161 471/186/162 452/182/160 +f 107/224/172 119/149/136 113/152/139 97/234/173 +f 116/221/197 104/220/196 221/235/201 218/236/202 +f 62/171/153 120/180/159 129/191/167 106/150/137 +f 64/173/154 121/157/141 128/195/171 109/200/176 +f 99/214/190 124/190/166 126/217/193 103/216/192 +f 105/227/181 117/222/198 217/237/203 219/238/204 +f 796/207/183 125/203/179 100/211/187 +f 100/211/187 125/203/179 127/206/182 104/220/196 +f 109/200/176 128/195/171 123/198/174 96/201/177 +f 106/150/137 129/191/167 122/194/170 95/151/138 +f 792/213/189 797/187/163 124/190/166 99/214/190 +f 451/176/155 482/177/156 120/180/159 62/171/153 +f 62/171/153 111/155/140 56/154/115 +f 64/173/154 54/158/113 121/157/141 +f 781/239/205 780/240/206 143/241/207 144/242/208 +f 779/243/209 775/244/210 145/245/211 141/246/212 +f 776/247/213 777/248/214 139/249/215 147/250/216 +f 777/248/214 778/251/217 140/252/218 139/249/215 +f 166/147/134 170/253/219 130/254/220 138/148/135 +f 164/144/131 168/111/102 136/114/105 132/145/132 +f 170/253/219 163/143/130 131/146/133 130/254/220 +f 167/112/103 169/255/90 134/256/93 135/113/104 +f 765/257/221 761/258/222 148/259/223 137/260/224 +f 764/261/225 763/262/226 150/263/227 151/264/228 +f 762/265/229 759/266/230 152/267/231 153/268/232 +f 763/262/226 760/269/233 155/270/234 150/263/227 +f 778/251/217 779/243/209 141/246/212 140/252/218 +f 782/271/235 776/247/213 147/250/216 142/272/236 +f 775/244/210 781/239/205 144/242/208 145/245/211 +f 780/273/206 782/271/235 142/272/236 143/274/207 +f 149/275/237 153/276/232 177/277/238 173/278/239 +f 155/279/234 137/280/224 171/281/240 179/282/241 +f 137/280/224 148/283/223 172/284/242 171/281/240 +f 151/285/228 150/286/227 174/287/243 175/288/244 +f 760/269/233 765/257/221 137/260/224 155/270/234 +f 766/289/245 762/265/229 153/268/232 149/290/237 +f 759/266/230 764/291/225 151/292/228 152/267/231 +f 761/258/222 766/289/245 149/290/237 148/259/223 +f 727/293/246 724/294/247 162/295/248 160/296/249 +f 725/297/250 722/298/251 159/299/252 156/300/253 +f 162/295/248 724/294/247 146/301/254 +f 726/302/255 723/303/256 161/304/257 158/305/258 +f 721/306/259 726/302/255 158/305/258 157/307/260 +f 725/297/250 156/300/253 146/301/254 +f 722/308/251 721/306/259 157/307/260 159/309/252 +f 723/303/256 727/293/246 160/296/249 161/304/257 +f 53/166/149 52/310/261 169/255/90 167/112/103 +f 48/88/83 49/87/82 163/143/130 170/253/219 +f 50/96/88 60/161/144 168/111/102 164/144/131 +f 61/104/95 48/88/83 170/253/219 166/147/134 +f 51/105/96 61/104/95 166/147/134 165/100/91 +f 49/87/82 50/96/88 164/144/131 163/143/130 +f 60/161/144 53/166/149 167/112/103 168/111/102 +f 52/311/261 51/105/96 165/100/91 169/99/90 +f 172/284/242 173/278/239 181/312/262 180/313/263 +f 176/314/264 175/288/244 183/315/265 184/316/266 +f 173/278/239 177/277/238 185/317/267 181/312/262 +f 179/282/241 171/281/240 154/318/268 187/319/269 +f 152/320/231 151/285/228 175/288/244 176/314/264 +f 148/283/223 149/275/237 173/278/239 172/284/242 +f 150/321/227 155/279/234 179/282/241 174/322/243 +f 153/276/232 152/323/231 176/324/264 177/277/238 +f 703/325/270 702/326/271 178/327/272 195/328/273 +f 702/326/271 699/329/274 188/330/275 178/327/272 +f 700/331/276 697/332/277 190/333/278 191/334/279 +f 701/335/280 698/336/281 192/337/282 193/338/283 +f 174/322/243 179/282/241 187/319/269 182/339/284 +f 184/340/266 185/317/267 205/341/285 206/342/286 +f 175/288/244 174/287/243 182/343/284 183/315/265 +f 171/281/240 172/284/242 180/313/263 154/318/268 +f 194/344/287 193/338/283 192/337/282 191/345/279 +f 195/328/273 194/344/287 191/345/279 190/346/278 +f 178/327/272 188/330/275 194/344/287 195/328/273 +f 188/330/275 189/347/288 193/338/283 194/344/287 +f 704/348/289 701/335/280 193/338/283 189/347/288 +f 698/349/281 700/331/276 191/334/279 192/350/282 +f 699/329/274 704/348/289 189/347/288 188/330/275 +f 697/351/277 703/325/270 195/328/273 190/346/278 +f 710/352/290 708/353/291 201/354/292 196/355/293 +f 712/356/294 709/357/295 202/358/296 199/359/297 +f 711/360/298 705/361/299 186/362/300 198/363/301 +f 709/357/295 706/364/302 197/365/303 202/358/296 +f 706/364/302 711/366/298 198/367/301 197/365/303 +f 705/361/299 710/368/290 196/369/293 186/362/300 +f 707/370/304 712/356/294 199/359/297 200/371/305 +f 708/353/291 707/370/304 200/371/305 201/354/292 +f 203/372/306 204/373/307 208/374/308 207/375/309 +f 176/314/264 184/316/266 206/376/286 204/377/307 +f 185/317/267 177/277/238 203/372/306 205/341/285 +f 177/277/238 176/324/264 204/373/307 203/372/306 +f 208/378/308 210/379/310 214/380/311 212/381/312 +f 204/377/307 206/376/286 210/379/310 208/378/308 +f 205/341/285 203/372/306 207/375/309 209/382/313 +f 206/342/286 205/341/285 209/382/313 210/383/310 +f 211/384/314 212/385/312 214/386/311 213/387/315 +f 209/382/313 207/375/309 211/384/314 213/387/315 +f 210/383/310 209/382/313 213/387/315 214/386/311 +f 207/375/309 208/374/308 212/385/312 211/384/314 +f 104/220/196 127/206/182 215/388/316 221/235/201 +f 127/206/182 105/205/181 219/389/204 215/388/316 +f 102/229/199 116/221/197 218/236/202 220/390/200 +f 117/222/198 103/216/192 327/219/195 217/237/203 +f 241/391/317 229/392/318 225/393/319 240/394/320 +f 242/395/321 224/396/322 229/392/318 241/391/317 +f 248/397/323 226/398/324 230/399/317 247/400/317 +f 247/400/317 230/399/317 228/401/325 246/402/326 +f 239/403/317 233/404/317 232/405/327 238/406/328 +f 237/407/329 231/408/330 233/404/317 239/403/317 +f 309/409/331 235/410/332 236/411/317 308/412/317 +f 308/412/317 236/411/317 234/413/333 307/414/334 +f 222/415/335 237/407/329 239/403/317 227/416/336 +f 227/416/336 239/403/317 238/406/328 223/417/337 +f 235/410/332 242/395/321 241/391/317 236/411/317 +f 236/411/317 241/391/317 240/394/320 234/413/333 +f 230/399/317 245/418/317 244/419/338 228/401/325 +f 226/398/324 243/420/339 245/418/317 230/399/317 +f 233/404/317 247/400/317 246/402/326 232/405/327 +f 231/408/330 248/397/323 247/400/317 233/404/317 +f 223/421/337 250/422/340 254/423/341 227/424/336 +f 225/425/319 252/426/342 263/427/343 240/428/320 +f 243/429/339 265/430/344 311/431/345 309/432/331 +f 228/433/325 255/434/346 267/435/347 246/436/326 +f 246/436/326 267/435/347 258/437/348 232/438/327 +f 234/439/333 259/440/349 310/441/350 307/442/334 +f 237/443/329 261/444/351 257/445/352 231/446/330 +f 240/428/320 263/427/343 259/440/349 234/439/333 +f 309/432/331 311/431/345 260/447/353 235/448/332 +f 235/448/332 260/447/353 264/449/354 242/450/321 +f 253/451/355 273/452/356 284/453/357 265/430/344 +f 268/454/358 287/455/359 273/452/356 253/451/355 +f 261/444/351 280/456/360 276/457/361 257/445/352 +f 262/458/362 281/459/363 270/460/364 250/422/340 +f 255/434/346 274/461/365 286/462/366 267/435/347 +f 263/427/343 282/463/367 278/464/368 259/440/349 +f 256/465/369 275/466/370 272/467/371 252/426/342 +f 249/468/372 269/469/373 280/456/360 261/444/351 +f 264/449/354 283/470/374 271/471/375 251/472/376 +f 257/445/352 276/457/361 287/455/359 268/454/358 +f 311/431/345 313/473/377 279/474/378 260/447/353 +f 258/437/348 277/475/379 281/459/363 262/458/362 +f 252/426/342 272/467/371 282/463/367 263/427/343 +f 266/476/380 285/477/381 274/461/365 255/434/346 +f 310/441/350 312/478/382 285/477/381 266/476/380 +f 251/472/376 271/471/375 275/479/370 256/480/369 +f 267/435/347 286/462/366 277/475/379 258/437/348 +f 260/447/353 279/474/378 283/470/374 264/449/354 +f 313/473/377 315/481/383 298/482/384 279/474/378 +f 277/475/379 296/483/385 300/484/386 281/459/363 +f 272/467/371 291/485/387 301/486/388 282/463/367 +f 285/477/381 304/487/389 293/488/390 274/461/365 +f 312/478/382 314/489/391 304/487/389 285/477/381 +f 271/471/375 290/490/392 294/491/393 275/479/370 +f 286/462/366 305/492/394 296/483/385 277/475/379 +f 279/474/378 298/482/384 302/493/395 283/470/374 +f 273/452/356 292/494/396 303/495/397 284/453/357 +f 287/455/359 306/496/398 292/494/396 273/452/356 +f 280/456/360 299/497/399 295/498/400 276/457/361 +f 281/459/363 300/484/386 289/499/401 270/460/364 +f 274/461/365 293/488/390 305/492/394 286/462/366 +f 282/463/367 301/486/388 297/500/402 278/464/368 +f 275/466/370 294/501/393 291/485/387 272/467/371 +f 269/469/373 288/502/403 299/497/399 280/456/360 +f 283/470/374 302/493/395 290/490/392 271/471/375 +f 276/457/361 295/498/400 306/496/398 287/455/359 +f 290/490/392 302/493/395 322/503/404 319/504/405 +f 301/486/388 291/485/387 317/505/406 316/506/407 +f 302/493/395 298/482/384 323/507/408 322/503/404 +f 291/485/387 294/501/393 318/508/409 317/505/406 +f 315/481/383 303/495/397 325/509/410 324/510/411 +f 297/500/402 301/486/388 316/506/407 321/511/412 +f 294/491/393 290/490/392 319/504/405 318/512/409 +f 314/489/391 297/500/402 321/511/412 320/513/413 +f 303/495/397 304/487/389 326/514/414 325/509/410 +f 269/469/373 270/460/364 289/499/401 288/502/403 +f 300/484/386 296/483/385 295/498/400 299/497/399 +f 306/496/398 295/498/400 296/483/385 305/492/394 +f 305/492/394 293/488/390 292/494/396 306/496/398 +f 303/495/397 292/494/396 293/488/390 304/487/389 +f 304/487/389 314/489/391 320/513/413 326/514/414 +f 298/482/384 315/481/383 324/510/411 323/507/408 +f 278/464/368 297/500/402 314/489/391 312/478/382 +f 284/453/357 303/495/397 315/481/383 313/473/377 +f 259/440/349 278/464/368 312/478/382 310/441/350 +f 265/430/344 284/453/357 313/473/377 311/431/345 +f 227/424/336 254/423/341 249/468/372 222/515/335 +f 245/418/317 308/412/317 307/414/334 244/419/338 +f 243/420/339 309/409/331 308/412/317 245/418/317 +f 221/516/201 215/517/316 316/506/407 317/505/406 +f 318/512/409 319/504/405 221/518/201 +f 221/516/201 317/505/406 318/508/409 +f 219/519/204 217/520/203 320/513/413 321/511/412 +f 220/521/200 218/522/202 322/503/404 323/507/408 +f 215/517/316 219/519/204 321/511/412 316/506/407 +f 218/522/202 221/518/201 319/504/405 322/503/404 +f 327/523/195 216/524/194 324/510/411 325/509/410 +f 325/509/410 326/514/414 327/523/195 +f 217/520/203 327/523/195 326/514/414 320/513/413 +f 216/524/194 220/521/200 323/507/408 324/510/411 +f 222/515/335 249/468/372 261/444/351 237/443/329 +f 242/450/321 264/449/354 251/472/376 224/525/322 +f 248/526/323 268/454/358 253/451/355 226/527/324 +f 224/525/322 251/472/376 256/480/369 229/528/318 +f 231/446/330 257/445/352 268/454/358 248/526/323 +f 307/442/334 310/441/350 266/476/380 244/529/338 +f 232/438/327 258/437/348 262/458/362 238/530/328 +f 244/529/338 266/476/380 255/434/346 228/433/325 +f 226/527/324 253/451/355 265/430/344 243/429/339 +f 229/531/318 256/465/369 252/426/342 225/425/319 +f 238/530/328 262/458/362 250/422/340 223/421/337 +f 299/497/399 288/502/403 289/499/401 300/484/386 +f 250/422/340 270/460/364 254/423/341 +f 254/423/341 269/469/373 249/468/372 +f 269/469/373 254/423/341 270/460/364 +f 328/532/415 361/533/416 382/534/417 358/535/418 +f 328/532/415 343/536/419 377/537/420 361/533/416 +f 328/532/415 342/538/421 371/539/422 343/536/419 +f 328/532/415 358/535/418 376/540/423 342/538/421 +f 329/541/424 341/542/425 370/543/426 350/544/427 +f 329/541/424 352/545/428 374/546/429 341/542/425 +f 422/547/430 353/548/19 380/549/18 434/550/431 +f 422/547/430 432/551/432 378/552/20 353/548/19 +f 330/553/433 346/554/434 374/546/429 352/545/428 +f 330/553/433 345/555/435 368/556/436 346/554/434 +f 423/557/437 354/558/29 373/559/28 430/560/438 +f 423/557/437 434/550/431 380/549/18 354/558/29 +f 331/561/439 348/562/440 369/563/441 347/564/442 +f 331/561/439 355/565/443 376/540/423 348/562/440 +f 424/566/444 356/38/38 381/37/37 435/567/445 +f 424/566/444 431/568/446 375/39/39 356/38/38 +f 332/569/447 342/538/421 376/540/423 355/565/443 +f 332/569/447 351/570/448 371/539/422 342/538/421 +f 425/571/449 357/46/46 379/45/45 433/572/450 +f 425/571/449 435/567/445 381/37/37 357/46/46 +f 333/573/451 360/574/452 372/575/453 340/576/454 +f 333/573/451 358/535/418 382/534/417 360/574/452 +f 333/573/451 348/562/440 376/540/423 358/535/418 +f 333/573/451 340/576/454 369/563/441 348/562/440 +f 334/577/455 346/554/434 368/556/436 344/578/456 +f 334/577/455 359/579/457 374/546/429 346/554/434 +f 334/577/455 360/574/452 382/534/417 359/579/457 +f 334/577/455 344/578/456 372/575/453 360/574/452 +f 335/580/458 341/542/425 374/546/429 359/579/457 +f 335/580/458 349/581/459 370/543/426 341/542/425 +f 335/580/458 361/533/416 377/537/420 349/581/459 +f 335/580/458 359/579/457 382/534/417 361/533/416 +f 336/582/460 364/583/461 377/537/420 343/536/419 +f 426/584/462 362/585/61 383/586/60 436/587/463 +f 426/588/462 433/572/450 379/45/45 362/63/61 +f 336/589/460 343/536/419 371/539/422 351/570/448 +f 337/590/464 350/544/427 370/543/426 349/581/459 +f 427/591/465 363/592/64 378/552/20 432/551/432 +f 427/591/465 436/587/463 383/586/60 363/592/64 +f 337/590/464 349/581/459 377/537/420 364/583/461 +f 365/593/466 387/594/467 388/595/468 339/596/469 +f 338/597/470 344/578/456 368/556/436 345/555/435 +f 338/597/470 365/593/466 372/575/453 344/578/456 +f 428/598/471 430/560/438 373/559/28 366/599/71 +f 339/596/469 340/576/454 372/575/453 365/593/466 +f 339/596/469 347/564/442 369/563/441 340/576/454 +f 429/600/472 367/76/73 375/39/39 431/568/446 +f 428/598/471 437/601/473 386/602/474 338/597/470 +f 386/602/474 391/603/475 392/604/476 387/594/467 +f 438/605/477 440/606/478 389/83/80 384/82/79 +f 338/597/470 386/602/474 387/594/467 365/593/466 +f 429/600/472 438/605/477 384/82/79 367/76/73 +f 391/607/475 396/608/479 397/609/480 392/610/476 +f 440/611/478 442/612/481 394/91/84 389/90/80 +f 387/594/467 392/604/476 393/613/482 388/595/468 +f 437/601/473 439/614/483 391/603/475 386/602/474 +f 392/610/476 397/609/480 398/615/484 393/616/482 +f 439/617/483 441/618/485 396/608/479 391/607/475 +f 530/619/486 495/620/487 494/621/488 526/622/489 +f 450/623/490 443/624/491 401/625/492 415/626/493 +f 449/627/494 444/628/495 399/109/100 417/108/99 +f 529/629/496 497/630/497 496/631/498 528/632/499 +f 746/633/500 446/634/501 404/117/108 735/116/107 +f 744/635/502 406/636/503 407/637/504 742/638/505 +f 742/639/505 407/640/504 408/641/506 743/642/507 +f 745/643/508 445/644/509 406/636/503 744/635/502 +f 758/645/510 447/646/511 412/647/512 755/648/513 +f 756/649/514 411/650/515 410/651/516 754/652/517 +f 755/648/513 412/647/512 411/653/515 756/654/514 +f 757/655/518 448/656/519 409/141/128 749/140/127 +f 524/657/520 492/658/521 493/659/522 525/660/523 +f 442/612/481 449/627/494 417/108/99 394/91/84 +f 441/618/485 450/623/490 415/626/493 396/608/479 +f 526/622/489 494/621/488 499/661/524 527/662/525 +f 479/663/526 473/664/527 454/665/528 465/666/529 +f 407/667/504 470/668/530 408/669/506 +f 407/670/504 406/671/503 481/672/531 +f 395/159/142 416/160/143 450/623/490 441/618/485 +f 398/615/484 414/673/532 449/627/494 442/612/481 +f 754/652/517 410/651/516 448/656/519 757/655/518 +f 751/162/145 413/163/146 447/646/511 758/645/510 +f 737/164/147 405/165/148 445/644/509 745/643/508 +f 743/642/507 408/641/506 446/634/501 746/633/500 +f 414/673/532 403/674/533 444/628/495 449/627/494 +f 416/160/143 400/167/150 443/624/491 450/623/490 +f 390/168/151 395/159/142 441/618/485 439/617/483 +f 385/675/152 390/676/151 439/614/483 437/601/473 +f 393/616/482 398/615/484 442/612/481 440/611/478 +f 339/596/469 388/595/468 438/605/477 429/600/472 +f 388/595/468 393/613/482 440/606/478 438/605/477 +f 366/599/71 385/675/152 437/601/473 428/598/471 +f 339/596/469 429/600/472 431/568/446 347/564/442 +f 338/597/470 345/555/435 430/560/438 428/598/471 +f 337/590/464 364/583/461 436/587/463 427/591/465 +f 337/590/464 427/591/465 432/551/432 350/544/427 +f 336/589/460 351/570/448 433/572/450 426/588/462 +f 336/582/460 426/584/462 436/587/463 364/583/461 +f 332/569/447 355/565/443 435/567/445 425/571/449 +f 332/569/447 425/571/449 433/572/450 351/570/448 +f 331/561/439 347/564/442 431/568/446 424/566/444 +f 331/561/439 424/566/444 435/567/445 355/565/443 +f 330/553/433 352/545/428 434/550/431 423/557/437 +f 330/553/433 423/557/437 430/560/438 345/555/435 +f 329/541/424 350/544/427 432/551/432 422/547/430 +f 329/541/424 422/547/430 434/550/431 352/545/428 +f 418/677/534 446/678/501 408/669/506 +f 420/679/535 406/671/503 445/680/509 +f 404/175/108 446/678/501 418/677/534 451/176/155 +f 482/177/156 480/681/536 419/682/537 421/178/157 +f 405/181/148 452/182/160 420/679/535 445/680/509 +f 469/683/538 471/186/162 421/185/157 419/684/537 +f 804/685/539 485/686/540 457/687/541 798/688/542 +f 490/689/543 483/690/544 453/691/545 467/692/546 +f 489/693/547 484/694/548 456/695/549 466/696/550 +f 478/697/551 472/698/552 455/699/553 468/700/554 +f 486/701/555 488/702/556 464/703/557 460/704/558 +f 803/705/559 486/701/555 460/704/558 800/706/560 +f 802/707/561 474/708/562 459/709/563 +f 801/710/564 475/711/565 458/712/566 799/713/567 +f 462/714/568 688/715/569 577/716/570 487/717/571 +f 474/708/562 476/718/572 463/719/573 459/709/563 +f 475/711/565 477/720/574 462/714/568 458/712/566 +f 485/686/540 487/717/571 461/721/575 457/687/541 +f 469/683/538 478/697/551 468/700/554 420/679/535 +f 481/672/531 489/693/547 466/696/550 407/670/504 +f 480/681/536 490/689/543 467/692/546 419/682/537 +f 470/668/530 479/663/526 465/666/529 418/677/534 +f 407/667/504 466/722/550 479/663/526 470/668/530 +f 419/684/537 467/723/546 478/697/551 469/683/538 +f 460/724/558 464/725/557 477/720/574 475/711/565 +f 457/726/541 461/727/575 476/718/572 474/708/562 +f 487/717/571 577/716/570 581/728/576 461/721/575 +f 800/729/560 460/724/558 475/711/565 801/710/564 +f 798/730/542 457/726/541 474/708/562 802/707/561 +f 467/723/546 453/731/545 472/698/552 478/697/551 +f 420/679/535 452/182/160 471/186/162 469/683/538 +f 466/722/550 456/732/549 473/664/527 479/663/526 +f 476/718/572 579/733/577 582/734/578 463/719/573 +f 418/677/534 465/666/529 490/689/543 480/681/536 +f 420/679/535 468/700/554 489/693/547 481/672/531 +f 458/712/566 462/714/568 487/717/571 485/686/540 +f 464/725/557 580/735/579 578/736/580 477/720/574 +f 459/709/563 486/701/555 803/705/559 +f 459/709/563 463/719/573 488/702/556 486/701/555 +f 468/700/554 455/699/553 484/694/548 489/693/547 +f 465/666/529 454/665/528 483/690/544 490/689/543 +f 799/713/567 458/712/566 485/686/540 804/685/539 +f 451/176/155 418/677/534 480/681/536 482/177/156 +f 418/677/534 408/669/506 470/668/530 +f 420/679/535 481/672/531 406/671/503 +f 789/737/581 505/738/582 504/739/583 788/740/584 +f 787/741/585 502/742/586 506/743/587 783/744/588 +f 784/745/589 508/746/590 500/747/591 785/748/592 +f 785/748/592 500/747/591 501/749/593 786/750/594 +f 527/662/525 499/661/524 491/751/595 531/752/596 +f 525/660/523 493/659/522 497/630/497 529/629/496 +f 531/752/596 491/751/595 492/658/521 524/657/520 +f 528/632/499 496/631/498 495/753/487 530/754/486 +f 773/755/597 498/756/598 509/757/599 769/758/600 +f 772/759/601 512/760/602 511/761/603 771/762/604 +f 770/763/605 514/764/606 513/765/607 767/766/608 +f 771/762/604 511/761/603 516/767/609 768/768/610 +f 786/750/594 501/749/593 502/742/586 787/741/585 +f 790/769/611 503/770/612 508/746/590 784/745/589 +f 783/744/588 506/743/587 505/738/582 789/737/581 +f 788/771/584 504/772/583 503/770/612 790/769/611 +f 510/773/613 534/774/614 538/775/615 514/776/606 +f 516/777/609 540/778/616 532/779/617 498/780/598 +f 498/780/598 532/779/617 533/781/618 509/782/599 +f 512/783/602 536/784/619 535/785/620 511/786/603 +f 768/768/610 516/767/609 498/756/598 773/755/597 +f 774/787/621 510/788/613 514/764/606 770/763/605 +f 767/766/608 513/765/607 512/789/602 772/790/601 +f 769/758/600 509/757/599 510/788/613 774/787/621 +f 734/791/622 521/792/623 523/793/624 731/794/625 +f 732/795/626 517/796/627 520/797/628 729/798/629 +f 731/794/625 523/793/624 507/799/630 +f 733/800/631 519/801/632 522/802/633 730/803/634 +f 728/804/635 518/805/636 519/801/632 733/800/631 +f 507/799/630 517/796/627 732/795/626 +f 729/806/629 520/807/628 518/805/636 728/804/635 +f 730/803/634 522/802/633 521/792/623 734/791/622 +f 403/674/533 528/632/499 530/754/486 402/808/637 +f 396/608/479 531/752/596 524/657/520 397/609/480 +f 398/615/484 525/660/523 529/629/496 414/673/532 +f 415/626/493 527/662/525 531/752/596 396/608/479 +f 401/625/492 526/622/489 527/662/525 415/626/493 +f 397/609/480 524/657/520 525/660/523 398/615/484 +f 414/673/532 529/629/496 528/632/499 403/674/533 +f 402/809/637 530/619/486 526/622/489 401/625/492 +f 533/781/618 541/810/638 542/811/639 534/774/614 +f 537/812/640 545/813/641 544/814/642 536/784/619 +f 534/774/614 542/811/639 546/815/643 538/775/615 +f 540/778/616 548/816/644 515/817/645 532/779/617 +f 513/818/607 537/812/640 536/784/619 512/783/602 +f 509/782/599 533/781/618 534/774/614 510/773/613 +f 511/819/603 535/820/620 540/778/616 516/777/609 +f 514/776/606 538/775/615 537/821/640 513/822/607 +f 695/823/646 556/824/647 539/825/648 694/826/649 +f 694/826/649 539/825/648 549/827/650 691/828/651 +f 692/829/652 552/830/653 551/831/654 689/832/655 +f 693/833/656 554/834/657 553/835/658 690/836/659 +f 535/820/620 543/837/660 548/816/644 540/778/616 +f 545/838/641 567/839/661 566/840/662 546/815/643 +f 536/784/619 544/814/642 543/841/660 535/785/620 +f 532/779/617 515/817/645 541/810/638 533/781/618 +f 555/842/663 552/843/653 553/835/658 554/834/657 +f 556/824/647 551/844/654 552/843/653 555/842/663 +f 539/825/648 556/824/647 555/842/663 549/827/650 +f 549/827/650 555/842/663 554/834/657 550/845/664 +f 696/846/665 550/845/664 554/834/657 693/833/656 +f 690/847/659 553/848/658 552/830/653 692/829/652 +f 691/828/651 549/827/650 550/845/664 696/846/665 +f 689/849/655 551/844/654 556/824/647 695/823/646 +f 718/850/666 557/851/667 562/852/668 716/853/669 +f 720/854/670 560/855/671 563/856/672 717/857/673 +f 719/858/674 559/859/675 547/860/676 713/861/677 +f 717/857/673 563/856/672 558/862/678 714/863/679 +f 714/863/679 558/862/678 559/864/675 719/865/674 +f 713/861/677 547/860/676 557/866/667 718/867/666 +f 715/868/680 561/869/681 560/855/671 720/854/670 +f 716/853/669 562/852/668 561/869/681 715/868/680 +f 564/870/682 568/871/683 569/872/684 565/873/685 +f 537/812/640 565/874/685 567/875/661 545/813/641 +f 546/815/643 566/840/662 564/870/682 538/775/615 +f 538/775/615 564/870/682 565/873/685 537/821/640 +f 569/876/684 573/877/686 575/878/687 571/879/688 +f 565/874/685 569/876/684 571/879/688 567/875/661 +f 566/840/662 570/880/689 568/871/683 564/870/682 +f 567/839/661 571/881/688 570/880/689 566/840/662 +f 572/882/690 574/883/691 575/884/687 573/885/686 +f 570/880/689 574/883/691 572/882/690 568/871/683 +f 571/881/688 575/884/687 574/883/691 570/880/689 +f 568/871/683 572/882/690 573/885/686 569/872/684 +f 463/719/573 582/734/578 576/886/692 488/702/556 +f 488/702/556 576/886/692 580/887/579 464/703/557 +f 461/727/575 581/888/576 579/733/577 476/718/572 +f 477/720/574 578/736/580 688/715/569 462/714/568 +f 602/889/317 601/890/693 586/891/694 590/892/695 +f 603/893/696 602/889/317 590/892/695 585/894/697 +f 609/895/698 608/896/317 591/897/317 587/898/699 +f 608/896/317 607/899/700 589/900/701 591/897/317 +f 600/901/317 599/902/702 593/903/703 594/904/317 +f 598/905/704 600/901/317 594/904/317 592/906/705 +f 670/907/706 669/908/317 597/909/317 596/910/707 +f 669/908/317 668/911/708 595/912/709 597/909/317 +f 583/913/710 588/914/711 600/901/317 598/905/704 +f 588/914/711 584/915/712 599/902/702 600/901/317 +f 596/910/707 597/909/317 602/889/317 603/893/696 +f 597/909/317 595/912/709 601/890/693 602/889/317 +f 591/897/317 589/900/701 605/916/713 606/917/317 +f 587/898/699 591/897/317 606/917/317 604/918/714 +f 594/904/317 593/903/703 607/899/700 608/896/317 +f 592/906/705 594/904/317 608/896/317 609/895/698 +f 584/919/712 588/920/711 615/921/715 611/922/716 +f 586/923/694 601/924/693 624/925/717 613/926/718 +f 604/927/714 670/928/706 672/929/719 626/930/720 +f 589/931/701 607/932/700 628/933/721 616/934/722 +f 607/932/700 593/935/703 619/936/723 628/933/721 +f 595/937/709 668/938/708 671/939/724 620/940/725 +f 598/941/704 592/942/705 618/943/726 622/944/727 +f 601/924/693 595/937/709 620/940/725 624/925/717 +f 670/928/706 596/945/707 621/946/728 672/929/719 +f 596/945/707 603/947/696 625/948/729 621/946/728 +f 614/949/730 626/930/720 645/950/731 634/951/732 +f 629/952/733 614/949/730 634/951/732 648/953/734 +f 622/944/727 618/943/726 637/954/735 641/955/736 +f 623/956/737 611/922/716 631/957/738 642/958/739 +f 616/934/722 628/933/721 647/959/740 635/960/741 +f 624/925/717 620/940/725 639/961/742 643/962/743 +f 617/963/744 613/926/718 633/964/745 636/965/746 +f 610/966/747 622/944/727 641/955/736 630/967/748 +f 625/948/729 612/968/749 632/969/750 644/970/751 +f 618/943/726 629/952/733 648/953/734 637/954/735 +f 672/929/719 621/946/728 640/971/752 674/972/753 +f 619/936/723 623/956/737 642/958/739 638/973/754 +f 613/926/718 624/925/717 643/962/743 633/964/745 +f 627/974/755 616/934/722 635/960/741 646/975/756 +f 671/939/724 627/974/755 646/975/756 673/976/757 +f 612/968/749 617/977/744 636/978/746 632/969/750 +f 628/933/721 619/936/723 638/973/754 647/959/740 +f 621/946/728 625/948/729 644/970/751 640/971/752 +f 674/972/753 640/971/752 659/979/758 676/980/759 +f 638/973/754 642/958/739 661/981/760 657/982/761 +f 633/964/745 643/962/743 662/983/762 652/984/763 +f 646/975/756 635/960/741 654/985/764 665/986/765 +f 673/976/757 646/975/756 665/986/765 675/987/766 +f 632/969/750 636/978/746 655/988/767 651/989/768 +f 647/959/740 638/973/754 657/982/761 666/990/769 +f 640/971/752 644/970/751 663/991/770 659/979/758 +f 634/951/732 645/950/731 664/992/771 653/993/772 +f 648/953/734 634/951/732 653/993/772 667/994/773 +f 641/955/736 637/954/735 656/995/774 660/996/775 +f 642/958/739 631/957/738 650/997/776 661/981/760 +f 635/960/741 647/959/740 666/990/769 654/985/764 +f 643/962/743 639/961/742 658/998/777 662/983/762 +f 636/965/746 633/964/745 652/984/763 655/999/767 +f 630/967/748 641/955/736 660/996/775 649/1000/778 +f 644/970/751 632/969/750 651/989/768 663/991/770 +f 637/954/735 648/953/734 667/994/773 656/995/774 +f 651/989/768 680/1001/779 683/1002/780 663/991/770 +f 662/983/762 677/1003/781 678/1004/782 652/984/763 +f 663/991/770 683/1002/780 684/1005/783 659/979/758 +f 652/984/763 678/1004/782 679/1006/784 655/999/767 +f 676/980/759 685/1007/785 686/1008/786 664/992/771 +f 658/998/777 682/1009/787 677/1003/781 662/983/762 +f 655/988/767 679/1010/784 680/1001/779 651/989/768 +f 675/987/766 681/1011/788 682/1009/787 658/998/777 +f 664/992/771 686/1008/786 687/1012/789 665/986/765 +f 630/967/748 649/1000/778 650/997/776 631/957/738 +f 661/981/760 660/996/775 656/995/774 657/982/761 +f 667/994/773 666/990/769 657/982/761 656/995/774 +f 666/990/769 667/994/773 653/993/772 654/985/764 +f 664/992/771 665/986/765 654/985/764 653/993/772 +f 665/986/765 687/1012/789 681/1011/788 675/987/766 +f 659/979/758 684/1005/783 685/1007/785 676/980/759 +f 639/961/742 673/976/757 675/987/766 658/998/777 +f 645/950/731 674/972/753 676/980/759 664/992/771 +f 620/940/725 671/939/724 673/976/757 639/961/742 +f 626/930/720 672/929/719 674/972/753 645/950/731 +f 588/920/711 583/1013/710 610/966/747 615/921/715 +f 606/917/317 605/916/713 668/911/708 669/908/317 +f 604/918/714 606/917/317 669/908/317 670/907/706 +f 582/1014/578 678/1004/782 677/1003/781 576/1015/692 +f 679/1010/784 582/1016/578 680/1001/779 +f 582/1014/578 679/1006/784 678/1004/782 +f 580/1017/579 682/1009/787 681/1011/788 578/1018/580 +f 581/1019/576 684/1005/783 683/1002/780 579/1020/577 +f 576/1015/692 677/1003/781 682/1009/787 580/1017/579 +f 579/1020/577 683/1002/780 680/1001/779 582/1016/578 +f 688/1021/569 686/1008/786 685/1007/785 577/1022/570 +f 686/1008/786 688/1021/569 687/1012/789 +f 578/1018/580 681/1011/788 687/1012/789 688/1021/569 +f 577/1022/570 685/1007/785 684/1005/783 581/1019/576 +f 583/1013/710 598/941/704 622/944/727 610/966/747 +f 603/947/696 585/1023/697 612/968/749 625/948/729 +f 609/1024/698 587/1025/699 614/949/730 629/952/733 +f 585/1023/697 590/1026/695 617/977/744 612/968/749 +f 592/942/705 609/1024/698 629/952/733 618/943/726 +f 668/938/708 605/1027/713 627/974/755 671/939/724 +f 593/935/703 599/1028/702 623/956/737 619/936/723 +f 605/1027/713 589/931/701 616/934/722 627/974/755 +f 587/1025/699 604/927/714 626/930/720 614/949/730 +f 590/1029/695 586/923/694 613/926/718 617/963/744 +f 599/1028/702 584/919/712 611/922/716 623/956/737 +f 660/996/775 661/981/760 650/997/776 649/1000/778 +f 611/922/716 615/921/715 631/957/738 +f 615/921/715 610/966/747 630/967/748 +f 630/967/748 631/957/738 615/921/715 +f 557/851/667 689/849/655 695/823/646 562/852/668 +f 560/855/671 691/828/651 696/846/665 563/856/672 +f 559/859/675 690/847/659 692/829/652 547/860/676 +f 563/856/672 696/846/665 693/833/656 558/862/678 +f 558/862/678 693/833/656 690/836/659 559/864/675 +f 547/860/676 692/829/652 689/832/655 557/866/667 +f 561/869/681 694/826/649 691/828/651 560/855/671 +f 562/852/668 695/823/646 694/826/649 561/869/681 +f 196/355/293 201/354/292 703/325/270 697/351/277 +f 199/359/297 202/358/296 704/348/289 699/329/274 +f 198/363/301 186/362/300 700/331/276 698/349/281 +f 202/358/296 197/365/303 701/335/280 704/348/289 +f 197/365/303 198/367/301 698/336/281 701/335/280 +f 186/362/300 196/369/293 697/332/277 700/331/276 +f 200/371/305 199/359/297 699/329/274 702/326/271 +f 201/354/292 200/371/305 702/326/271 703/325/270 +f 187/319/269 154/318/268 707/370/304 708/353/291 +f 154/318/268 180/313/263 712/356/294 707/370/304 +f 183/315/265 182/343/284 710/368/290 705/361/299 +f 185/317/267 184/340/266 711/366/298 706/364/302 +f 181/312/262 185/317/267 706/364/302 709/357/295 +f 184/316/266 183/315/265 705/361/299 711/360/298 +f 180/313/263 181/312/262 709/357/295 712/356/294 +f 182/339/284 187/319/269 708/353/291 710/352/290 +f 548/816/644 716/853/669 715/868/680 515/817/645 +f 515/817/645 715/868/680 720/854/670 541/810/638 +f 544/814/642 713/861/677 718/867/666 543/841/660 +f 546/815/643 714/863/679 719/865/674 545/838/641 +f 542/811/639 717/857/673 714/863/679 546/815/643 +f 545/813/641 719/858/674 713/861/677 544/814/642 +f 541/810/638 720/854/670 717/857/673 542/811/639 +f 543/837/660 718/850/666 716/853/669 548/816/644 +f 139/249/215 140/252/218 727/293/246 723/303/256 +f 143/274/207 142/272/236 721/306/259 722/308/251 +f 145/245/211 144/242/208 725/297/250 +f 142/272/236 147/250/216 726/302/255 721/306/259 +f 147/250/216 139/249/215 723/303/256 726/302/255 +f 141/246/212 145/245/211 724/294/247 +f 144/242/208 143/241/207 722/298/251 725/297/250 +f 140/252/218 141/246/212 724/294/247 727/293/246 +f 500/747/591 730/803/634 734/791/622 501/749/593 +f 504/772/583 729/806/629 728/804/635 503/770/612 +f 505/738/582 506/743/587 732/795/626 +f 503/770/612 728/804/635 733/800/631 508/746/590 +f 508/746/590 733/800/631 730/803/634 500/747/591 +f 502/742/586 731/794/625 506/743/587 +f 505/738/582 732/795/626 729/798/629 504/739/583 +f 501/749/593 734/791/622 731/794/625 502/742/586 +f 410/651/516 743/642/507 746/633/500 448/656/519 +f 413/163/146 737/164/147 745/643/508 447/646/511 +f 447/646/511 745/643/508 744/635/502 412/647/512 +f 411/650/515 742/639/505 743/642/507 410/651/516 +f 412/647/512 744/635/502 742/638/505 411/653/515 +f 448/656/519 746/633/500 735/116/107 409/141/128 +f 57/135/124 91/142/129 741/115/106 738/124/114 +f 413/163/146 90/132/121 740/127/116 737/164/147 +f 90/132/121 59/131/120 739/119/110 740/127/116 +f 58/136/125 57/135/124 738/124/114 736/123/111 +f 59/131/120 58/138/125 736/120/111 739/119/110 +f 91/142/129 409/141/128 735/116/107 741/115/106 +f 400/167/150 751/162/145 758/645/510 443/624/491 +f 403/674/533 754/652/517 757/655/518 444/628/495 +f 444/628/495 757/655/518 749/140/127 399/109/100 +f 401/625/492 755/648/513 756/654/514 402/809/637 +f 402/808/637 756/649/514 754/652/517 403/674/533 +f 443/624/491 758/645/510 755/648/513 401/625/492 +f 400/167/150 86/106/97 753/129/118 751/162/145 +f 53/166/149 87/110/101 752/139/126 747/134/123 +f 87/110/101 399/109/100 749/140/127 752/139/126 +f 51/105/96 52/311/261 750/137/122 748/130/119 +f 52/310/261 53/166/149 747/134/123 750/133/122 +f 86/106/97 51/105/96 748/130/119 753/129/118 +f 160/296/249 162/295/248 766/289/245 761/258/222 +f 156/300/253 159/299/252 764/291/225 759/266/230 +f 162/295/248 146/301/254 762/265/229 766/289/245 +f 158/305/258 161/304/257 765/257/221 760/269/233 +f 157/307/260 158/305/258 760/269/233 763/262/226 +f 146/301/254 156/300/253 759/266/230 762/265/229 +f 159/309/252 157/307/260 763/262/226 764/261/225 +f 161/304/257 160/296/249 761/258/222 765/257/221 +f 521/792/623 769/758/600 774/787/621 523/793/624 +f 517/796/627 767/766/608 772/790/601 520/797/628 +f 523/793/624 774/787/621 770/763/605 507/799/630 +f 519/801/632 768/768/610 773/755/597 522/802/633 +f 518/805/636 771/762/604 768/768/610 519/801/632 +f 507/799/630 770/763/605 767/766/608 517/796/627 +f 520/807/628 772/759/601 771/762/604 518/805/636 +f 522/802/633 773/755/597 769/758/600 521/792/623 +f 134/102/93 133/101/92 782/271/235 780/273/206 +f 136/114/105 135/113/104 781/239/205 775/244/210 +f 133/101/92 138/148/135 776/247/213 782/271/235 +f 131/146/133 132/145/132 779/243/209 778/251/217 +f 130/254/220 131/146/133 778/251/217 777/248/214 +f 138/148/135 130/254/220 777/248/214 776/247/213 +f 132/145/132 136/114/105 775/244/210 779/243/209 +f 135/113/104 134/256/93 780/240/206 781/239/205 +f 495/620/487 788/771/584 790/769/611 494/621/488 +f 497/630/497 783/744/588 789/737/581 496/631/498 +f 494/621/488 790/769/611 784/745/589 499/661/524 +f 492/658/521 786/750/594 787/741/585 493/659/522 +f 491/751/595 785/748/592 786/750/594 492/658/521 +f 499/661/524 784/745/589 785/748/592 491/751/595 +f 493/659/522 787/741/585 783/744/588 497/630/497 +f 496/631/498 789/737/581 788/740/584 495/753/487 +f 506/743/587 507/799/630 732/795/626 +f 145/245/211 146/301/254 724/294/247 +f 506/743/587 731/794/625 507/799/630 +f 145/245/211 725/297/250 146/301/254 +f 95/151/138 122/194/170 797/187/163 792/213/189 +f 96/201/177 123/198/174 796/207/183 +f 94/233/169 112/202/178 795/210/186 791/232/164 +f 97/234/173 113/152/139 794/212/188 793/231/184 +f 113/152/139 95/151/138 792/213/189 794/212/188 +f 112/202/178 96/201/177 795/210/186 +f 123/198/174 97/197/173 793/208/184 796/207/183 +f 122/194/170 94/193/169 791/188/164 797/187/163 +f 454/665/528 799/713/567 804/685/539 483/690/544 +f 484/694/548 455/699/553 803/705/559 +f 453/731/545 798/730/542 802/707/561 472/698/552 +f 456/732/549 800/729/560 801/710/564 473/664/527 +f 473/664/527 801/710/564 799/713/567 454/665/528 +f 472/698/552 802/707/561 455/699/553 +f 484/694/548 803/705/559 800/706/560 456/695/549 +f 483/690/544 804/685/539 798/688/542 453/691/545 +f 455/699/553 459/709/563 803/705/559 +f 96/201/177 100/211/187 795/210/186 +f 455/699/553 802/707/561 459/709/563 +f 96/201/177 796/207/183 100/211/187 diff --git a/examples/models/resources/models/obj/character_diffuse.png b/examples/models/resources/models/obj/character_diffuse.png new file mode 100644 index 0000000000000000000000000000000000000000..0fae9d8944dce8f7f307d73b454f86200d569afc GIT binary patch literal 41705 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 F{U1buITru` literal 0 HcmV?d00001 From 79fd6be0084a80dcbf7c7af2c1ccb12b78b77c47 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 25 Oct 2025 11:14:53 +0200 Subject: [PATCH 24/25] Minor format tweaks --- src/external/rlsw.h | 73 ++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 626cb2cc5..bc7a813d3 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -791,8 +791,7 @@ typedef struct { } sw_vertex_t; typedef struct { - - uint8_t* pixels; // Texture pixels (RGBA32) + uint8_t *pixels; // Texture pixels (RGBA32) int width, height; // Dimensions of the texture int wMinus1, hMinus1; // Dimensions minus one @@ -805,7 +804,6 @@ typedef struct { float tx; // Texel width float ty; // Texel height - } sw_texture_t; typedef struct { @@ -881,7 +879,6 @@ typedef struct { int freeTextureIdCount; uint32_t stateFlags; - } sw_context_t; //---------------------------------------------------------------------------------- @@ -966,11 +963,7 @@ static inline int sw_clampi(int v, int min, int max) return v; } -static inline void sw_lerp_vertex_PTCH( - sw_vertex_t *SW_RESTRICT out, - const sw_vertex_t *SW_RESTRICT a, - const sw_vertex_t *SW_RESTRICT b, - float t) +static inline void sw_lerp_vertex_PTCH(sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT a, const sw_vertex_t *SW_RESTRICT b, float t) { const float tInv = 1.0f - t; @@ -997,11 +990,7 @@ static inline void sw_lerp_vertex_PTCH( out->homogeneous[3] = a->homogeneous[3]*tInv + b->homogeneous[3]*t; } -static inline void sw_get_vertex_grad_PTCH( - sw_vertex_t *SW_RESTRICT out, - const sw_vertex_t *SW_RESTRICT a, - const sw_vertex_t *SW_RESTRICT b, - float scale) +static inline void sw_get_vertex_grad_PTCH(sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT a, const sw_vertex_t *SW_RESTRICT b, float scale) { // Calculate gradients for Position out->position[0] = (b->position[0] - a->position[0])*scale; @@ -1026,9 +1015,7 @@ static inline void sw_get_vertex_grad_PTCH( out->homogeneous[3] = (b->homogeneous[3] - a->homogeneous[3])*scale; } -static inline void sw_add_vertex_grad_PTCH( - sw_vertex_t *SW_RESTRICT out, - const sw_vertex_t *SW_RESTRICT gradients) +static inline void sw_add_vertex_grad_PTCH(sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT gradients) { // Add gradients to Position out->position[0] += gradients->position[0]; @@ -1065,7 +1052,6 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) uint8x8_t narrow8 = vmovn_u16(vcombine_u16(narrow16, narrow16)); vst1_lane_u32((uint32_t*)dst, vreinterpret_u32_u8(narrow8), 0); - #elif defined(SW_HAS_SSE41) __m128 values = _mm_loadu_ps(src); __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); @@ -1075,7 +1061,6 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) clamped = _mm_packus_epi32(clamped, clamped); clamped = _mm_packus_epi16(clamped, clamped); *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); - #elif defined(SW_HAS_SSE2) __m128 values = _mm_loadu_ps(src); __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); @@ -1085,7 +1070,6 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) clamped = _mm_packs_epi32(clamped, clamped); clamped = _mm_packus_epi16(clamped, clamped); *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); - #else for (int i = 0; i < 4; i++) { @@ -1110,14 +1094,12 @@ static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) float32x4_t floats = vcvtq_f32_u32(ints); floats = vmulq_n_f32(floats, 1.0f/255.0f); vst1q_f32(dst, floats); - #elif defined(SW_HAS_SSE41) __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); __m128i ints = _mm_cvtepu8_epi32(bytes); __m128 floats = _mm_cvtepi32_ps(ints); floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); _mm_storeu_ps(dst, floats); - #elif defined(SW_HAS_SSE2) __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); bytes = _mm_unpacklo_epi8(bytes, _mm_setzero_si128()); @@ -1125,7 +1107,6 @@ static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) __m128 floats = _mm_cvtepi32_ps(ints); floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); _mm_storeu_ps(dst, floats); - #else dst[0] = (float)src[0]/255.0f; dst[1] = (float)src[1]/255.0f; @@ -1188,7 +1169,7 @@ static inline sw_half_t sw_half_from_float(float i) } // Framebuffer management functions - +//------------------------------------------------------------------------------------------- static inline bool sw_framebuffer_load(int w, int h) { int size = w*h; @@ -1731,9 +1712,10 @@ DEFINE_FRAMEBUFFER_BLIT_BEGIN(R8G8B8A8, uint8_t) dst += 4; } DEFINE_FRAMEBUFFER_BLIT_END() +//------------------------------------------------------------------------------------------- // Pixel format management functions - +//------------------------------------------------------------------------------------------- static inline int sw_get_pixel_format(SWformat format, SWtype type) { int channels = 0; @@ -1920,9 +1902,10 @@ static inline void sw_get_pixel(uint8_t *color, const void *pixels, uint32_t off } } } +//------------------------------------------------------------------------------------------- // Texture sampling functionality - +//------------------------------------------------------------------------------------------- static inline void sw_texture_fetch(float* color, const sw_texture_t* tex, int x, int y) { sw_float_from_unorm8_simd(color, &tex->pixels[4*(y*tex->width + x)]); @@ -2016,9 +1999,10 @@ static inline void sw_texture_sample(float *color, const sw_texture_t *tex, floa default: break; } } +//------------------------------------------------------------------------------------------- -// Color Blending functionality - +// Color blending functionality +//------------------------------------------------------------------------------------------- static inline void sw_factor_zero(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = factor[1] = factor[2] = factor[3] = 0.0f; @@ -2091,17 +2075,19 @@ static inline void sw_blend_colors(float *SW_RESTRICT dst/*[4]*/, const float *S dst[2] = srcFactor[2]*src[2] + dstFactor[2]*dst[2]; dst[3] = srcFactor[3]*src[3] + dstFactor[3]*dst[3]; } +//------------------------------------------------------------------------------------------- // Projection helper functions - +//------------------------------------------------------------------------------------------- static inline void sw_project_ndc_to_screen(float screen[2], const float ndc[4]) { screen[0] = RLSW.vpCenter[0] + ndc[0]*RLSW.vpHalfSize[0]; screen[1] = RLSW.vpCenter[1] - ndc[1]*RLSW.vpHalfSize[1]; } +//------------------------------------------------------------------------------------------- -// Polygon Clipping management - +// Polygon clipping management +//------------------------------------------------------------------------------------------- #define DEFINE_CLIP_FUNC(name, FUNC_IS_INSIDE, FUNC_COMPUTE_T) \ static inline int sw_clip_##name( \ sw_vertex_t output[SW_MAX_CLIPPED_POLYGON_VERTICES], \ @@ -2133,9 +2119,10 @@ static inline int sw_clip_##name( \ return outputCount; \ } +//------------------------------------------------------------------------------------------- // Frustum cliping functions - +//------------------------------------------------------------------------------------------- #define IS_INSIDE_PLANE_W(h) ((h)[3] >= SW_CLIP_EPSILON) #define IS_INSIDE_PLANE_X_POS(h) ((h)[0] <= (h)[3]) #define IS_INSIDE_PLANE_X_NEG(h) (-(h)[0] <= (h)[3]) @@ -2159,9 +2146,10 @@ DEFINE_CLIP_FUNC(y_pos, IS_INSIDE_PLANE_Y_POS, COMPUTE_T_PLANE_Y_POS) DEFINE_CLIP_FUNC(y_neg, IS_INSIDE_PLANE_Y_NEG, COMPUTE_T_PLANE_Y_NEG) DEFINE_CLIP_FUNC(z_pos, IS_INSIDE_PLANE_Z_POS, COMPUTE_T_PLANE_Z_POS) DEFINE_CLIP_FUNC(z_neg, IS_INSIDE_PLANE_Z_NEG, COMPUTE_T_PLANE_Z_NEG) +//------------------------------------------------------------------------------------------- // Scissor clip functions - +//------------------------------------------------------------------------------------------- #define COMPUTE_T_SCISSOR_X_MIN(hPrev, hCurr) (((RLSW.scClipMin[0])*(hPrev)[3] - (hPrev)[0])/(((hCurr)[0] - (RLSW.scClipMin[0])*(hCurr)[3]) - ((hPrev)[0] - (RLSW.scClipMin[0])*(hPrev)[3]))) #define COMPUTE_T_SCISSOR_X_MAX(hPrev, hCurr) (((RLSW.scClipMax[0])*(hPrev)[3] - (hPrev)[0])/(((hCurr)[0] - (RLSW.scClipMax[0])*(hCurr)[3]) - ((hPrev)[0] - (RLSW.scClipMax[0])*(hPrev)[3]))) #define COMPUTE_T_SCISSOR_Y_MIN(hPrev, hCurr) (((RLSW.scClipMin[1])*(hPrev)[3] - (hPrev)[1])/(((hCurr)[1] - (RLSW.scClipMin[1])*(hCurr)[3]) - ((hPrev)[1] - (RLSW.scClipMin[1])*(hPrev)[3]))) @@ -2176,9 +2164,9 @@ DEFINE_CLIP_FUNC(scissor_x_min, IS_INSIDE_SCISSOR_X_MIN, COMPUTE_T_SCISSOR_X_MIN DEFINE_CLIP_FUNC(scissor_x_max, IS_INSIDE_SCISSOR_X_MAX, COMPUTE_T_SCISSOR_X_MAX) DEFINE_CLIP_FUNC(scissor_y_min, IS_INSIDE_SCISSOR_Y_MIN, COMPUTE_T_SCISSOR_Y_MIN) DEFINE_CLIP_FUNC(scissor_y_max, IS_INSIDE_SCISSOR_Y_MAX, COMPUTE_T_SCISSOR_Y_MAX) +//------------------------------------------------------------------------------------------- -// Main clip function - +// Main polygon clip function static inline bool sw_polygon_clip(sw_vertex_t polygon[SW_MAX_CLIPPED_POLYGON_VERTICES], int *vertexCounter) { static sw_vertex_t tmp[SW_MAX_CLIPPED_POLYGON_VERTICES]; @@ -2218,7 +2206,7 @@ static inline bool sw_polygon_clip(sw_vertex_t polygon[SW_MAX_CLIPPED_POLYGON_VE } // Triangle rendering logic - +//------------------------------------------------------------------------------------------- static inline bool sw_triangle_face_culling(void) { // NOTE: Face culling is done before clipping to avoid unnecessary computations. @@ -2556,9 +2544,10 @@ static inline void sw_triangle_render(void) #undef TRIANGLE_RASTER } +//------------------------------------------------------------------------------------------- // Quad rendering logic - +//------------------------------------------------------------------------------------------- static inline bool sw_quad_face_culling(void) { // NOTE: Face culling is done before clipping to avoid unnecessary computations. @@ -2930,9 +2919,10 @@ static inline void sw_quad_render(void) #undef TRIANGLE_RASTER } +//------------------------------------------------------------------------------------------- // Line rendering logic - +//------------------------------------------------------------------------------------------- static inline bool sw_line_clip_coord(float q, float p, float *t0, float *t1) { if (fabsf(p) < SW_CLIP_EPSILON) @@ -3243,9 +3233,10 @@ static inline void sw_line_render(sw_vertex_t *vertices) else sw_line_raster(&vertices[0], &vertices[1]); } } +//------------------------------------------------------------------------------------------- // Point rendering logic - +//------------------------------------------------------------------------------------------- static inline bool sw_point_clip_and_project(sw_vertex_t *v) { if (v->homogeneous[3] != 1.0f) @@ -3409,9 +3400,10 @@ static inline void sw_point_render(sw_vertex_t *v) else sw_point_raster(v->screen[0], v->screen[1], v->homogeneous[2], v->color); } } +//------------------------------------------------------------------------------------------- // Polygon modes mendering logic - +//------------------------------------------------------------------------------------------- static inline void sw_poly_point_render(void) { for (int i = 0; i < RLSW.vertexCounter; i++) sw_point_render(&RLSW.vertexBuffer[i]); @@ -3546,6 +3538,7 @@ static inline bool sw_is_blend_dst_factor_valid(int blend) return result; } +//------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------- // Module Functions Definition From efe62f0e0df426ff64b14edc75dafd934552f1c0 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 25 Oct 2025 11:15:30 +0200 Subject: [PATCH 25/25] Formating review to follow raylib code conventions #5298 --- examples/models/models_decals.c | 397 +++++++++++++++----------------- 1 file changed, 181 insertions(+), 216 deletions(-) diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index 81bbd2796..af1ecfc6d 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -2,7 +2,7 @@ * * raylib [models] example - decals * -* Example complexity rating: [★★★☆] 3/4 +* Example complexity rating: [★★★★] 4/4 * * Example originally created with raylib 5.6-dev * @@ -17,15 +17,19 @@ ********************************************************************************************/ #include "raylib.h" + #include "raymath.h" -#include +#include // Required for: memcpy() #undef FLT_MAX #define FLT_MAX 340282346638528859811704183484516925440.0f // Maximum value of a float, from bit pattern 01111111011111111111111111111111 #define MAX_DECALS 256 +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- typedef struct MeshBuilder { int vertexCount; int vertexCapacity; @@ -33,14 +37,14 @@ typedef struct MeshBuilder { Vector2 *uvs; } MeshBuilder; -// Utility functions -void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]); -void FreeMeshBuilder(MeshBuilder *mb); -Mesh BuildMesh(MeshBuilder *mb); - -Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); - -Mesh GenMeshDecal(Mesh inputMesh, Ray ray); +//------------------------------------------------------------------------------------ +// Module Functions Declaration +//------------------------------------------------------------------------------------ +static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]); +static void FreeMeshBuilder(MeshBuilder *mb); +static Mesh BuildMesh(MeshBuilder *mb); +static Mesh GenMeshDecal(Mesh inputMesh, Ray ray); +static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); //------------------------------------------------------------------------------------ // Program main entry point @@ -67,11 +71,10 @@ int main(void) Model model = LoadModel("resources/models/obj/character.obj"); // Apply character skin - Texture2D modelTexture = LoadTexture("resources/models/obj/character_diffuse.png"); // Load model texture + Texture2D modelTexture = LoadTexture("resources/models/obj/character_diffuse.png"); SetTextureFilter(modelTexture, TEXTURE_FILTER_BILINEAR); - model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = modelTexture; // Set model diffuse texture + model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = modelTexture; - BoundingBox modelBBox = GetMeshBoundingBox(model.meshes[0]); // Get mesh bounding box camera.target = Vector3Lerp(modelBBox.min, modelBBox.max, 0.5f); @@ -79,14 +82,12 @@ int main(void) camera.position.x *= 0.1f; float modelSize = fminf( - fminf(fabsf(modelBBox.max.x - modelBBox.min.x), - fabsf(modelBBox.max.y - modelBBox.min.y)), - fabsf(modelBBox.max.z - modelBBox.min.z) - ); + fminf(fabsf(modelBBox.max.x - modelBBox.min.x), fabsf(modelBBox.max.y - modelBBox.min.y)), + fabsf(modelBBox.max.z - modelBBox.min.z)); - camera.position = (Vector3){ 0, modelBBox.max.y * 1.2f, modelSize * 3.0f }; + camera.position = (Vector3){ 0.0f, modelBBox.max.y*1.2f, modelSize*3.0f }; - float decalSize = modelSize * .25f; + float decalSize = modelSize*0.25f; float decalOffset = 0.01f; Model placementCube = LoadModelFromMesh(GenMeshCube(decalSize, decalSize, decalSize)); @@ -96,7 +97,7 @@ int main(void) decalMaterial.maps[0].color = YELLOW; Image decalImage = LoadImage("resources/raylib_logo.png"); - ImageResizeNN(&decalImage, decalImage.width / 4, decalImage.height / 4); + ImageResizeNN(&decalImage, decalImage.width/4, decalImage.height/4); Texture decalTexture = LoadTextureFromImage(decalImage); UnloadImage(decalImage); @@ -106,28 +107,23 @@ int main(void) // We're going to use these to build up our decal meshes // They'll resize automatically as we go, we'll free them at the end - MeshBuilder mesh_builders[2] = { {0}, {0} }; + MeshBuilder meshBuilders[2] = { 0 }; bool showModel = true; - Model decalModels[MAX_DECALS] = {0}; + Model decalModels[MAX_DECALS] = { 0 }; int decalCount = 0; - SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- + // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { // Update //---------------------------------------------------------------------------------- - - if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { - UpdateCamera(&camera, CAMERA_THIRD_PERSON); - } - - if (IsKeyPressed(KEY_SPACE)) { - showModel = !showModel; - } + if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) UpdateCamera(&camera, CAMERA_THIRD_PERSON); + + if (IsKeyPressed(KEY_SPACE)) showModel = !showModel; // Display information about closest hit RayCollision collision = { 0 }; @@ -140,7 +136,7 @@ int main(void) // Check ray collision against bounding box first, before trying the full ray-mesh test RayCollision boxHitInfo = GetRayCollisionBox(ray, modelBBox); - if ((boxHitInfo.hit) && decalCount < MAX_DECALS) + if ((boxHitInfo.hit) && (decalCount < MAX_DECALS)) { // Check ray collision against model meshes RayCollision meshHitInfo = { 0 }; @@ -153,52 +149,47 @@ int main(void) if (meshHitInfo.hit) { // Save the closest hit mesh - if ((!collision.hit) || (collision.distance > meshHitInfo.distance)) collision = meshHitInfo; + if (!collision.hit || (collision.distance > meshHitInfo.distance)) collision = meshHitInfo; } } - if (meshHitInfo.hit) - { - collision = meshHitInfo; - } + if (meshHitInfo.hit) collision = meshHitInfo; } - // -------------- - - if (collision.hit && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && decalCount < MAX_DECALS) + // Add decal to mesh on hit point + if (collision.hit && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && (decalCount < MAX_DECALS)) { - // Create the transformation to project the decal Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); - Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); + Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){ 0.0f, 1.0f, 0.0f }); // Spin the placement around a bit - splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD * ((float)GetRandomValue(-180, 180)))); - + splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD*((float)GetRandomValue(-180, 180)))); Matrix splatInv = MatrixInvert(splat); // Reset the mesh builders - mesh_builders[0].vertexCount = 0; - mesh_builders[1].vertexCount = 0; + meshBuilders[0].vertexCount = 0; + meshBuilders[1].vertexCount = 0; // We'll be flip-flopping between the two mesh builders // Reading from one and writing to the other, then swapping - int mb_index = 0; + int mbIndex = 0; - // First pass, just get any triangle inside the bounding box - // (for each mesh of the model) - for (int meshIndex = 0; meshIndex < model.meshCount; meshIndex++) { + // First pass, just get any triangle inside the bounding box (for each mesh of the model) + for (int meshIndex = 0; meshIndex < model.meshCount; meshIndex++) + { Mesh mesh = model.meshes[meshIndex]; for (int tri = 0; tri < mesh.triangleCount; tri++) { - Vector3 vertices[3]; + Vector3 vertices[3] = { 0 }; // The way we calculate the vertices of the mesh triangle // depend on whether the mesh vertices are indexed or not if (mesh.indices == 0) { - for (int v = 0; v < 3; v++) { - vertices[v] = (Vector3) { + for (int v = 0; v < 3; v++) + { + vertices[v] = (Vector3){ mesh.vertices[3*3*tri + 3*v + 0], mesh.vertices[3*3*tri + 3*v + 1], mesh.vertices[3*3*tri + 3*v + 2] @@ -209,7 +200,7 @@ int main(void) { for (int v = 0; v < 3; v++) { - vertices[v] = (Vector3) { + vertices[v] = (Vector3){ mesh.vertices[ 3*mesh.indices[3*tri+0] + v], mesh.vertices[ 3*mesh.indices[3*tri+1] + v], mesh.vertices[ 3*mesh.indices[3*tri+2] + v] @@ -225,19 +216,14 @@ int main(void) // To splat space Vector3 v = Vector3Transform(vertices[i], splat); - if (fabsf(v.x) 0) - { - AddTriangleToMeshBuilder(&mesh_builders[mb_index], vertices); - } + if (insideCount > 0) AddTriangleToMeshBuilder(&meshBuilders[mbIndex], vertices); } } @@ -250,20 +236,21 @@ int main(void) { 0, 0, 1 }, { 0, 0, -1 } }; + for (int face = 0; face < 6; face++) { // Swap current model builder (so we read from the one we just wrote to) - mb_index = 1 - mb_index; + mbIndex = 1 - mbIndex; - MeshBuilder *inMesh = &mesh_builders[1-mb_index]; - MeshBuilder *outMesh = &mesh_builders[mb_index]; + MeshBuilder *inMesh = &meshBuilders[1 - mbIndex]; + MeshBuilder *outMesh = &meshBuilders[mbIndex]; // Reset write builder outMesh->vertexCount = 0; - float s = 0.5f * decalSize; + float s = 0.5f*decalSize; - for (int i = 0; i < inMesh->vertexCount; i+=3) + for (int i = 0; i < inMesh->vertexCount; i += 3) { Vector3 nV1, nV2, nV3, nV4; @@ -271,100 +258,101 @@ int main(void) float d2 = Vector3DotProduct(inMesh->vertices[ i + 1 ], planes[face] ) - s; float d3 = Vector3DotProduct(inMesh->vertices[ i + 2 ], planes[face] ) - s; - int v1Out = d1 > 0; - int v2Out = d2 > 0; - int v3Out = d3 > 0; + int v1Out = (d1 > 0); + int v2Out = (d2 > 0); + int v3Out = (d3 > 0); - // calculate, how many vertices of the face lie outside of the clipping plane + // Calculate, how many vertices of the face lie outside of the clipping plane int total = v1Out + v2Out + v3Out; - switch ( total ) { - case 0: { - // the entire face lies inside of the plane, no clipping needed + switch (total) + { + case 0: + { + // The entire face lies inside of the plane, no clipping needed AddTriangleToMeshBuilder(outMesh, (Vector3[3]){inMesh->vertices[i], inMesh->vertices[i+1], inMesh->vertices[i+2]}); - break; - } - - case 1: { - // one vertex lies outside of the plane, perform clipping - - if (v1Out) { - nV1 = inMesh->vertices[ i + 1 ]; - nV2 = inMesh->vertices[ i + 2 ]; - nV3 = ClipSegment( inMesh->vertices[ i ], nV1, planes[face], s ); - nV4 = ClipSegment( inMesh->vertices[ i ], nV2, planes[face], s ); + } break; + case 1: + { + // One vertex lies outside of the plane, perform clipping + if (v1Out) + { + nV1 = inMesh->vertices[i + 1]; + nV2 = inMesh->vertices[i + 2]; + nV3 = ClipSegment(inMesh->vertices[i], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i], nV2, planes[face], s); } - if (v2Out) { - nV1 = inMesh->vertices[ i ]; - nV2 = inMesh->vertices[ i + 2 ]; - nV3 = ClipSegment( inMesh->vertices[ i + 1 ], nV1, planes[face], s ); - nV4 = ClipSegment( inMesh->vertices[ i + 1 ], nV2, planes[face], s ); + if (v2Out) + { + nV1 = inMesh->vertices[i]; + nV2 = inMesh->vertices[i + 2]; + nV3 = ClipSegment(inMesh->vertices[i + 1], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i + 1], nV2, planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV3, nV2, nV1}); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV2, nV3, nV4}); break; } - if (v3Out) { - nV1 = inMesh->vertices[ i ]; - nV2 = inMesh->vertices[ i + 1 ]; - nV3 = ClipSegment( inMesh->vertices[ i + 2 ], nV1, planes[face], s ); - nV4 = ClipSegment( inMesh->vertices[ i + 2 ], nV2, planes[face], s ); + if (v3Out) + { + nV1 = inMesh->vertices[i]; + nV2 = inMesh->vertices[i + 1]; + nV3 = ClipSegment(inMesh->vertices[i + 2], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i + 2], nV2, planes[face], s); } AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2}); - - break; - } - - case 2: { - // two vertices lies outside of the plane, perform clipping - if (!v1Out) { - nV1 = inMesh->vertices[ i ]; - nV2 = ClipSegment( nV1, inMesh->vertices[ i + 1 ], planes[face], s ); - nV3 = ClipSegment( nV1, inMesh->vertices[ i + 2 ], planes[face], s ); + } break; + case 2: + { + // Two vertices lies outside of the plane, perform clipping + if (!v1Out) + { + nV1 = inMesh->vertices[i]; + nV2 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); } - if (!v2Out) { - nV1 = inMesh->vertices[ i + 1 ]; - nV2 = ClipSegment( nV1, inMesh->vertices[ i + 2 ], planes[face], s ); - nV3 = ClipSegment( nV1, inMesh->vertices[ i ], planes[face], s ); + + if (!v2Out) + { + nV1 = inMesh->vertices[i + 1]; + nV2 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); } - if (!v3Out) { - nV1 = inMesh->vertices[ i + 2 ]; - nV2 = ClipSegment( nV1, inMesh->vertices[ i ], planes[face], s ); - nV3 = ClipSegment( nV1, inMesh->vertices[ i + 1 ], planes[face], s ); + + if (!v3Out) + { + nV1 = inMesh->vertices[i + 2]; + nV2 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); } - break; - } - - case 3: { - // the entire face lies outside of the plane, so let's discard the corresponding vertices - break; - } - + } break; + case 3: // The entire face lies outside of the plane, so let's discard the corresponding vertices + default: break; } } } // Now we just need to re-transform the vertices - MeshBuilder *theMesh = &mesh_builders[mb_index]; + MeshBuilder *theMesh = &meshBuilders[mbIndex]; // Allocate room for UVs if (theMesh->vertexCount > 0) { - theMesh->uvs = (Vector2*)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); + theMesh->uvs = (Vector2 *)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); + for (int i = 0; i < theMesh->vertexCount; i++) { - // Calculate the UVs based on the projected coords // They are clipped to (-decalSize .. decalSize) and we want them (0..1) - theMesh->uvs[i].x = (theMesh->vertices[i].x / decalSize + .5f); - theMesh->uvs[i].y = (theMesh->vertices[i].y / decalSize + .5f); + theMesh->uvs[i].x = (theMesh->vertices[i].x/decalSize + 0.5f); + theMesh->uvs[i].y = (theMesh->vertices[i].y/decalSize + 0.5f); // From splat space to world space theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], splatInv); @@ -378,89 +366,73 @@ int main(void) decalModels[decalIndex] = LoadModelFromMesh(BuildMesh(theMesh)); decalModels[decalIndex].materials[0] = decalMaterial; } - } - //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(RAYWHITE); BeginMode3D(camera); - // Draw the model at the origin and default scale - if (showModel) - { - DrawModel(model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); - } + if (showModel) DrawModel(model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); // Draw the decal models - for (int i = 0; i < decalCount; i++) - { - DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); - } + for (int i = 0; i < decalCount; i++) DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); // If we hit the mesh, draw the box for the decal if (collision.hit) { - Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); - placementCube.transform = MatrixInvert(splat); DrawModel(placementCube, (Vector3){0}, 1.0f, Fade(WHITE, 0.5f)); } DrawGrid(10, 10.0f); - EndMode3D(); + + float yPos = 10; + float x0 = GetScreenWidth() - 300; + float x1 = x0 + 100; + float x2 = x1 + 100; + DrawText("Vertices", x1, yPos, 10, LIME); + DrawText("Triangles", x2, yPos, 10, LIME); + yPos += 15; + + int vertexCount = 0; + int triangleCount = 0; + + for (int i = 0; i < model.meshCount; i++) { - float yPos = 10; - float x0 = GetScreenWidth() - 300; - float x1 = x0 + 100; - float x2 = x1 + 100; + vertexCount += model.meshes[i].vertexCount; + triangleCount += model.meshes[i].triangleCount; + } + + DrawText("Main model", x0, yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + yPos += 15; + + for (int i = 0; i < decalCount; i++) + { + DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); - DrawText("Vertices", x1, yPos, 10, LIME); - DrawText("Triangles", x2, yPos, 10, LIME); - yPos += 15; - - int vertexCount = 0; - int triangleCount = 0; - - for (int i = 0; i < model.meshCount; i++) - { - vertexCount += model.meshes[i].vertexCount; - triangleCount += model.meshes[i].triangleCount; - } - - DrawText("Main model", x0, yPos, 10, LIME); - DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); - yPos += 15; - - for (int i = 0; i < decalCount; i++) - { - DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); - DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); - - vertexCount += decalModels[i].meshes[0].vertexCount; - triangleCount += decalModels[i].meshes[0].triangleCount; - yPos += 15; - } - - DrawText("TOTAL", x0, yPos, 10, LIME); - DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + vertexCount += decalModels[i].meshes[0].vertexCount; + triangleCount += decalModels[i].meshes[0].triangleCount; yPos += 15; } + + DrawText("TOTAL", x0, yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + yPos += 15; DrawText("Hold RMB to move camera", 10, 430, 10, GRAY); - DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY); DrawFPS(10, 10); @@ -474,15 +446,13 @@ int main(void) UnloadModel(model); UnloadTexture(modelTexture); - for (int i = 0; i < decalCount; i++) - { - UnloadModel(decalModels[i]); - } - + // TODO: WARNING: This line crashes program on closing + //for (int i = 0; i < decalCount; i++) UnloadModel(decalModels[i]); + UnloadTexture(decalTexture); - FreeMeshBuilder(&mesh_builders[0]); - FreeMeshBuilder(&mesh_builders[1]); + FreeMeshBuilder(&meshBuilders[0]); + FreeMeshBuilder(&meshBuilders[1]); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- @@ -490,18 +460,24 @@ int main(void) return 0; } -// A really simple dynamic array manager -void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) { +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Add triangles to mesh builder (dynamic array manager) +static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) +{ // Reallocate and copy if we need to - if (mb->vertexCapacity <= mb->vertexCount + 3 ) + if (mb->vertexCapacity <= (mb->vertexCount + 3)) { - int newVertexCapacity = (1 + (mb->vertexCapacity / 256)) * 256; - Vector3 *newVertices = (Vector3*)MemAlloc(newVertexCapacity * sizeof(Vector3)); + int newVertexCapacity = (1 + (mb->vertexCapacity/256))*256; + Vector3 *newVertices = (Vector3 *)MemAlloc(newVertexCapacity*sizeof(Vector3)); + if (mb->vertexCapacity > 0) { - memcpy(newVertices, mb->vertices, mb->vertexCount * sizeof(Vector3)); + memcpy(newVertices, mb->vertices, mb->vertexCount*sizeof(Vector3)); MemFree(mb->vertices); } + mb->vertices = newVertices; mb->vertexCapacity = newVertexCapacity; } @@ -510,44 +486,33 @@ void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) { int index = mb->vertexCount; mb->vertexCount += 3; - for (int i = 0; i < 3; i++) - { - mb->vertices[index+i] = vertices[i]; - } + for (int i = 0; i < 3; i++) mb->vertices[index+i] = vertices[i]; } -void FreeMeshBuilder(MeshBuilder *mb) +// Free mesh builder +static void FreeMeshBuilder(MeshBuilder *mb) { MemFree(mb->vertices); - - if (mb->uvs) - { - MemFree(mb->uvs); - } - + if (mb->uvs) MemFree(mb->uvs); *mb = (MeshBuilder){ 0 }; } -// Construct a Raylib Mesh from our MeshBuilder data -Mesh BuildMesh(MeshBuilder *mb) +// Build a Mesh from MeshBuilder data +static Mesh BuildMesh(MeshBuilder *mb) { Mesh outMesh = { 0 }; outMesh.vertexCount = mb->vertexCount; - outMesh.triangleCount = mb->vertexCount / 3; - - outMesh.vertices = MemAlloc(outMesh.vertexCount * 3 * sizeof(float)); - - if (mb->uvs) - { - outMesh.texcoords = MemAlloc(outMesh.vertexCount * 2 * sizeof(float)); - } - + outMesh.triangleCount = mb->vertexCount/3; + outMesh.vertices = MemAlloc(outMesh.vertexCount*3*sizeof(float)); + if (mb->uvs) outMesh.texcoords = MemAlloc(outMesh.vertexCount*2*sizeof(float)); + for (int i = 0; i < mb->vertexCount; i++) { outMesh.vertices[3*i+0] = mb->vertices[i].x; outMesh.vertices[3*i+1] = mb->vertices[i].y; outMesh.vertices[3*i+2] = mb->vertices[i].z; + if (mb->uvs) { outMesh.texcoords[2*i+0] = mb->uvs[i].x; @@ -560,14 +525,14 @@ Mesh BuildMesh(MeshBuilder *mb) return outMesh; } -Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) +// Clip segment +static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) { float d0 = Vector3DotProduct(v0, p) - s; float d1 = Vector3DotProduct(v1, p) - s; - - float s0 = d0 / ( d0 - d1 ); + float s0 = d0/(d0 - d1); Vector3 position = Vector3Lerp(v0, v1, s0); return position; -} \ No newline at end of file +}