From 41c9975118c7e1065ff9311f62904bc557898188 Mon Sep 17 00:00:00 2001 From: iann Date: Tue, 11 Nov 2025 03:48:20 +0900 Subject: [PATCH] reduce LOC --- examples/Makefile | 2 +- examples/core/core_3d_camera_view.c | 265 ++++++ examples/core/core_3d_camera_view_opengl33.c | 407 --------- .../core/core_3d_camera_view_opengl33.png | Bin 103636 -> 0 bytes examples/core/core_3d_that_one_example.c | 852 ++++++++++++++++++ 5 files changed, 1118 insertions(+), 408 deletions(-) create mode 100644 examples/core/core_3d_camera_view.c delete mode 100644 examples/core/core_3d_camera_view_opengl33.c delete mode 100644 examples/core/core_3d_camera_view_opengl33.png create mode 100644 examples/core/core_3d_that_one_example.c diff --git a/examples/Makefile b/examples/Makefile index 06be40e5e..b41de1944 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -523,7 +523,7 @@ CORE = \ core/core_3d_picking \ core/core_3d_fixed_function_didactic \ core/core_3d_camera_view_opengl11 \ - core/core_3d_camera_view_opengl33 \ + core/core_3d_camera_view \ core/core_automation_events \ core/core_basic_screen_manager \ core/core_basic_window \ diff --git a/examples/core/core_3d_camera_view.c b/examples/core/core_3d_camera_view.c new file mode 100644 index 000000000..12537f14a --- /dev/null +++ b/examples/core/core_3d_camera_view.c @@ -0,0 +1,265 @@ +/******************************************************************************************* +* +* raylib [core] example - Camera View +* Example complexity rating: [★★★★] 4/4 +* +* Example originally created with raylib 6.0? (target) +* +* Example contributed by IANN (@meisei4) and reviewed by Ramon Santamaria (@raysan5) and community +* +* 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 @meisei4 +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" +#include + +#define BAHAMA_BLUE CLITERAL(Color){ 0, 102, 153, 255 } +#define SUNFLOWER CLITERAL(Color){ 255, 204, 153, 255 } +#define ANAKIWA CLITERAL(Color){ 153, 204, 255, 255 } +#define MARINER CLITERAL(Color){ 51, 102, 204, 255 } +#define NEON_CARROT CLITERAL(Color){ 255, 153, 51, 255 } +#define EGGPLANT CLITERAL(Color){ 102, 68, 102, 255 } +#define HOPBUSH CLITERAL(Color){ 204, 102, 153, 255 } + +typedef unsigned short Triangle[3]; + +enum Flags +{ + FLAG_PAUSE = 1u<<1, + FLAG_JUGEMU = 1u<<2, + FLAG_ORTHO = 1u<<3, + GEN_CUBE = 1u<<4, + GEN_SPHERE = 1u<<5, + GEN_KNOT = 1u<<6 +}; + +static unsigned int gflags = FLAG_JUGEMU | GEN_CUBE; + +#define PAUSED() ((gflags & FLAG_PAUSE) != 0) +#define JUGEMU_MODE() ((gflags & FLAG_JUGEMU) != 0) +#define ORTHO_MODE() ((gflags & FLAG_ORTHO) != 0) +#define TOGGLE(K, F) do { if (IsKeyPressed(K)) { gflags ^= (F); } } while (0) + +static unsigned int targetMesh = 0; +#define NUM_MODELS 3 +#define CYCLE_MESH(K, I, F) do { if (IsKeyPressed(K)) { targetMesh = (I); gflags = (gflags & ~(GEN_CUBE|GEN_SPHERE|GEN_KNOT)) | (F); } } while (0) + +static float fovyPerspective = 60.0f; +static float nearPlaneHeightOrthographic = 1.0f; +static Vector3 yAxis = { 0.0f, 1.0f, 0.0f }; +static Vector3 modelPos = { 0.0f, 0.0f, 0.0f }; +static Vector3 modelScale = { 1.0f, 1.0f, 1.0f }; +static Vector3 mainPos = { 0.0f, 0.0f, 2.0f }; +static Vector3 jugemuPos = { 3.0f, 1.0f, 3.0f }; + +static Vector3 *ComputeFrustumCorners(const Camera3D *main, float aspect, float near, float far); +static void OrbitSpace(Camera3D *jugemu, float dt); +static float OrthoBlendFactor(float dt); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [core] example - camera view"); + RenderTexture2D perspectiveCorrectRenderTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); + float near = 1.0f; + float far = 3.0f; + float aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); + nearPlaneHeightOrthographic = 2.0f*near*tanf(DEG2RAD*fovyPerspective*0.5f); + float meshRotation = 0.0f; + + Camera3D main = { 0 }; + main.position = mainPos; + main.target = modelPos; + main.up = yAxis; + main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; + main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic: fovyPerspective; + + Camera3D jugemu = (Camera3D){ 0 }; + jugemu.position = jugemuPos; + jugemu.target = modelPos; + jugemu.up = yAxis; + jugemu.fovy = fovyPerspective; + jugemu.projection = CAMERA_PERSPECTIVE; + + Model models[NUM_MODELS] = { 0 }; + models[0] = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f)); + models[1] = LoadModelFromMesh(GenMeshSphere(0.5f, 8, 8)); + models[2] = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 16, 128)); + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + while (!WindowShouldClose()) + { + // Update + //---------------------------------------------------------------------------------- + aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); + TOGGLE(KEY_SPACE, FLAG_PAUSE); + TOGGLE(KEY_J, FLAG_JUGEMU); + TOGGLE(KEY_O, FLAG_ORTHO); + CYCLE_MESH(KEY_ONE, 0, GEN_CUBE); + CYCLE_MESH(KEY_TWO, 1, GEN_SPHERE); + CYCLE_MESH(KEY_THREE, 2, GEN_KNOT); + + OrthoBlendFactor(GetFrameTime()); + + if (!PAUSED()) meshRotation -= 1.25f*GetFrameTime(); + + OrbitSpace(&jugemu, GetFrameTime()); + main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; + main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic : fovyPerspective; + + Model *displayModel = &models[targetMesh]; + + BeginTextureMode(perspectiveCorrectRenderTexture); + ClearBackground(BLANK); + BeginMode3D(main); + DrawModelWiresEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, MARINER); + EndMode3D(); + EndTextureMode(); + + Vector3 *corners = ComputeFrustumCorners(&main, aspect, near, far); + Vector3 *nearPts = corners; + Vector3 *farPts = corners + 4; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + if (JUGEMU_MODE()) BeginMode3D(jugemu); else BeginMode3D(main); + Vector3 depth = Vector3Normalize(Vector3Subtract(main.target, main.position)); + Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main.up)); + Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); + DrawLine3D(main.position, Vector3Add(main.position, right), NEON_CARROT); + DrawLine3D(main.position, Vector3Add(main.position, up), NEON_CARROT); + DrawLine3D(main.position, Vector3Add(main.position, depth), MARINER); + + DrawModelWiresEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, MARINER); + + if (JUGEMU_MODE()) + { + // Draw Frustum wires + DrawLine3D(nearPts[0], nearPts[1], NEON_CARROT); + DrawLine3D(nearPts[1], nearPts[2], NEON_CARROT); + DrawLine3D(nearPts[2], nearPts[3], NEON_CARROT); + DrawLine3D(nearPts[3], nearPts[0], NEON_CARROT); + + DrawLine3D(farPts[0], farPts[1], EGGPLANT); + DrawLine3D(farPts[1], farPts[2], EGGPLANT); + DrawLine3D(farPts[2], farPts[3], EGGPLANT); + DrawLine3D(farPts[3], farPts[0], EGGPLANT); + + DrawLine3D(nearPts[0], farPts[0], HOPBUSH); + DrawLine3D(nearPts[1], farPts[1], HOPBUSH); + DrawLine3D(nearPts[2], farPts[2], HOPBUSH); + DrawLine3D(nearPts[3], farPts[3], HOPBUSH); + + //Capture RenderTexture for near clip plane + rlSetTexture(perspectiveCorrectRenderTexture.texture.id); + rlBegin(RL_QUADS); + rlColor4ub(255, 255, 255, 255); + rlTexCoord2f(0.0f, 1.0f); rlVertex3f(nearPts[0].x, nearPts[0].y, nearPts[0].z); + rlTexCoord2f(0.0f, 0.0f); rlVertex3f(nearPts[3].x, nearPts[3].y, nearPts[3].z); + rlTexCoord2f(1.0f, 0.0f); rlVertex3f(nearPts[2].x, nearPts[2].y, nearPts[2].z); + rlTexCoord2f(1.0f, 1.0f); rlVertex3f(nearPts[1].x, nearPts[1].y, nearPts[1].z); + rlEnd(); + rlSetTexture(0); + } + + EndMode3D(); + + DrawText("[1]: CUBE [2]: SPHERE [3]: KNOT", 12, 12, 20, NEON_CARROT); + DrawText("ARROWS: MOVE | SPACEBAR: PAUSE", 12, 38, 20, NEON_CARROT); + DrawText("W S : ZOOM ", 12, 64, 20, NEON_CARROT); + DrawText((targetMesh == 0)? "GEN_CUBE" : (targetMesh == 1)? "GEN_SPHERE" : "GEN_KNOT", 12, 205, 20, NEON_CARROT); + DrawText("LENS [ O ]:", 510, 366, 20, SUNFLOWER); + DrawText((ORTHO_MODE())? "ORTHOGRAPHIC" : "PERSPECTIVE", 630, 366, 20, (ORTHO_MODE())? BAHAMA_BLUE : ANAKIWA); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadModel(models[0]); + UnloadModel(models[1]); + UnloadModel(models[2]); + if (perspectiveCorrectRenderTexture.id) UnloadRenderTexture(perspectiveCorrectRenderTexture); + CloseWindow(); + //-------------------------------------------------------------------------------------- + + return 0; +} + +static Vector3 *ComputeFrustumCorners(const Camera3D *main, float aspect, float near, float far) +{ + + Vector3 depth = Vector3Normalize(Vector3Subtract(main->target, main->position)); + Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main->up)); + Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); + + float perspHalfHNear = near*tanf(DEG2RAD*fovyPerspective*0.5f); + float orthoHalfH = 0.5f*nearPlaneHeightOrthographic; + float halfHNear = Lerp(perspHalfHNear, orthoHalfH, OrthoBlendFactor(0.0f)); + float halfWNear = halfHNear*aspect; + + float perspHalfHFar = far*tanf(DEG2RAD*fovyPerspective*0.5f); + float halfHFar = Lerp(perspHalfHFar, orthoHalfH, OrthoBlendFactor(0.0f)); + float halfWFar = halfHFar*aspect; + + Vector3 centerNear = Vector3Add(main->position, Vector3Scale(depth, near)); + Vector3 centerFar = Vector3Add(main->position, Vector3Scale(depth, far)); + + static Vector3 corners[8]; + corners[0] = Vector3Add(centerNear, Vector3Add(Vector3Scale(up, halfHNear), Vector3Scale(right, -halfWNear))); + corners[1] = Vector3Add(centerNear, Vector3Add(Vector3Scale(up, halfHNear), Vector3Scale(right, halfWNear))); + corners[2] = Vector3Add(centerNear, Vector3Add(Vector3Scale(up, -halfHNear), Vector3Scale(right, halfWNear))); + corners[3] = Vector3Add(centerNear, Vector3Add(Vector3Scale(up, -halfHNear), Vector3Scale(right, -halfWNear))); + + corners[4] = Vector3Add(centerFar, Vector3Add(Vector3Scale(up, halfHFar), Vector3Scale(right, -halfWFar))); + corners[5] = Vector3Add(centerFar, Vector3Add(Vector3Scale(up, halfHFar), Vector3Scale(right, halfWFar))); + corners[6] = Vector3Add(centerFar, Vector3Add(Vector3Scale(up, -halfHFar), Vector3Scale(right, halfWFar))); + corners[7] = Vector3Add(centerFar, Vector3Add(Vector3Scale(up, -halfHFar), Vector3Scale(right, -halfWFar))); + + return corners; +} + +static void OrbitSpace(Camera3D *jugemu, float dt) +{ + float radius = Vector3Length(jugemu->position); + float azimuth = atan2f(jugemu->position.z, jugemu->position.x); + float horizontalRadius = sqrtf(jugemu->position.x*jugemu->position.x + jugemu->position.z*jugemu->position.z); + float elevation = atan2f(jugemu->position.y, horizontalRadius); + if (IsKeyDown(KEY_LEFT)) azimuth += 1.0f*dt; + if (IsKeyDown(KEY_RIGHT)) azimuth -= 1.0f*dt; + if (IsKeyDown(KEY_UP)) elevation += 1.0f*dt; + if (IsKeyDown(KEY_DOWN)) elevation -= 1.0f*dt; + if (IsKeyDown(KEY_W)) radius -= 1.0f*dt; + if (IsKeyDown(KEY_S)) radius += 1.0f*dt; + elevation = Clamp(elevation, -PI/2 + 0.1f, PI/2 - 0.1f); + jugemu->position.x = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*cosf(azimuth); + jugemu->position.y = Clamp(radius, 0.25f, 10.0f)*sinf(elevation); + jugemu->position.z = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*sinf(azimuth); +} + +static float OrthoBlendFactor(float dt) +{ + static float blend = 0.0f; + if (dt > 0.0f) blend = Clamp(blend + ((ORTHO_MODE())? 1.0f : -1.0f)*5.0f*dt, 0.0f, 1.0f); + return blend; +} \ No newline at end of file diff --git a/examples/core/core_3d_camera_view_opengl33.c b/examples/core/core_3d_camera_view_opengl33.c deleted file mode 100644 index 7ab5e20db..000000000 --- a/examples/core/core_3d_camera_view_opengl33.c +++ /dev/null @@ -1,407 +0,0 @@ -/******************************************************************************************* -* -* raylib [core] example - Camera View -* Example complexity rating: [★★★★] 4/4 -* -* Example originally created with raylib 6.0? (target) -* -* Example contributed by IANN (@meisei4) and reviewed by Ramon Santamaria (@raysan5) and community -* -* 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 @meisei4 -* -********************************************************************************************/ - -#include "raylib.h" -#include "raymath.h" -#include "rlgl.h" -#include -#include -#include - -static const char *vert = - "#version 330\n" - "in vec3 vertexPosition;\n" - "in vec2 vertexTexCoord;\n" - "in vec3 vertexNormal;\n" - "in vec4 vertexColor;\n" - "uniform mat4 mvp;\n" - "out vec2 fragTexCoord;\n" - "out vec4 fragColor;\n" - "uniform int useVertexColors;\n" - "void main()\n" - "{\n" - " if (useVertexColors == 1) {\n" - " fragColor = vertexColor;\n" - " } else {\n" - " fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" - " }\n" - " fragTexCoord = vertexTexCoord;\n" - " gl_Position = mvp * vec4(vertexPosition, 1.0);\n" - "}\n"; - -static const char *frag = - "#version 330\n" - "in vec2 fragTexCoord;\n" - "in vec4 fragColor;\n" - "uniform sampler2D texture0;\n" - "uniform vec4 colDiffuse;\n" - "out vec4 finalColor;\n" - "void main()\n" - "{\n" - " vec4 texelColor = texture(texture0, fragTexCoord);\n" - " vec4 outColor = texelColor*fragColor*colDiffuse;\n" - " if (outColor.a <= 0.0) discard; \n" - " finalColor = outColor;\n" - "}\n"; - -static Shader customShader = { 0 }; -static int useVertexColorsLoc = -1; - -#define BAHAMA_BLUE CLITERAL(Color){ 0, 102, 153, 255 } -#define SUNFLOWER CLITERAL(Color){ 255, 204, 153, 255 } -#define ANAKIWA CLITERAL(Color){ 153, 204, 255, 255 } -#define MARINER CLITERAL(Color){ 51, 102, 204, 255 } -#define NEON_CARROT CLITERAL(Color){ 255, 153, 51, 255 } -#define EGGPLANT CLITERAL(Color){ 102, 68, 102, 255 } -#define HOPBUSH CLITERAL(Color){ 204, 102, 153, 255 } -#define LILAC CLITERAL(Color){ 204, 153, 204, 255 } -#define RED_DAMASK CLITERAL(Color){ 221, 102, 68, 255 } -#define CHESTNUT_ROSE CLITERAL(Color){ 204, 102, 102, 255 } - -typedef unsigned short Triangle[3]; - -enum Flags -{ - FLAG_ASPECT = 1u<<0, - FLAG_PAUSE = 1u<<1, - FLAG_JUGEMU = 1u<<2, - FLAG_ORTHO = 1u<<3, - GEN_CUBE = 1u<<4, - GEN_SPHERE = 1u<<5, - GEN_KNOT = 1u<<6 -}; - -static unsigned int gflags = FLAG_ASPECT | FLAG_JUGEMU | GEN_CUBE; - -#define ASPECT_CORRECT() ((gflags & FLAG_ASPECT) != 0) -#define PAUSED() ((gflags & FLAG_PAUSE) != 0) -#define JUGEMU_MODE() ((gflags & FLAG_JUGEMU) != 0) -#define ORTHO_MODE() ((gflags & FLAG_ORTHO) != 0) -#define TOGGLE(K, F) do { if (IsKeyPressed(K)) { gflags ^= (F); } } while (0) - -static unsigned int targetMesh = 0; -#define NUM_MODELS 3 -#define CYCLE_MESH(K, I, F) do { if (IsKeyPressed(K)) { targetMesh = (I); gflags = (gflags & ~(GEN_CUBE|GEN_SPHERE|GEN_KNOT)) | (F); } } while (0) - -static int fontSize = 20; -static float angularVelocity = 1.25f; -static float fovyPerspective = 60.0f; -static float nearPlaneHeightOrthographic = 1.0f; -static float blendScalar = 5.0f; -static Vector3 yAxis = { 0.0f, 1.0f, 0.0f }; -static Vector3 modelPos = { 0.0f, 0.0f, 0.0f }; -static Vector3 modelScale = { 1.0f, 1.0f, 1.0f }; -static Vector3 mainPos = { 0.0f, 0.0f, 2.0f }; -static Vector3 jugemuPosIso = { 3.0f, 1.0f, 3.0f }; - -static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame); -static void DrawSpatialFrame(Mesh *spatialFrame); -static void PerspectiveCorrectCapture(Camera3D *main, Model *model, RenderTexture2D *perspectiveCorrectRenderTexture, float rotation); -static void FillVertexColors(Mesh *mesh); -static void OrbitSpace(Camera3D *jugemu, float dt); -static Vector3 TranslateRotateScale(Vector3 coordinate, Vector3 pos, Vector3 scale, float rotation); -static Vector3 Intersect(Camera3D *main, float near, Vector3 worldCoord); -static float OrthoBlendFactor(float dt); - -//------------------------------------------------------------------------------------ -// Program main entry point -//------------------------------------------------------------------------------------ -int main(void) -{ - // Initialization - //-------------------------------------------------------------------------------------- - const int screenWidth = 800; - const int screenHeight = 450; - - InitWindow(screenWidth, screenHeight, "raylib [core] example - fixed function didactic"); - customShader = LoadShaderFromMemory(vert, frag); - useVertexColorsLoc = GetShaderLocation(customShader, "useVertexColors"); - RenderTexture2D perspectiveCorrectRenderTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); - float near = 1.0f; - float far = 3.0f; - float aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); - nearPlaneHeightOrthographic = 2.0f*near*tanf(DEG2RAD*fovyPerspective*0.5f); - float meshRotation = 0.0f; - - Camera3D main = { 0 }; - main.position = mainPos; - main.target = modelPos; - main.up = yAxis; - main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; - main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic: fovyPerspective; - - Camera3D jugemu = (Camera3D){ 0 }; - jugemu.position = jugemuPosIso; - jugemu.target = modelPos; - jugemu.up = yAxis; - jugemu.fovy = fovyPerspective; - jugemu.projection = CAMERA_PERSPECTIVE; - - Model models[NUM_MODELS] = { 0 }; - models[0] = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f)); - models[1] = LoadModelFromMesh(GenMeshSphere(0.5f, 8, 8)); - models[2] = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 16, 128)); - for (int i = 0; i < NUM_MODELS; i++) - { - Mesh *mesh = &models[i].meshes[0]; - mesh->vaoId = 0; - if (!mesh->indices) - { - mesh->indices = RL_CALLOC(mesh->vertexCount, sizeof(unsigned short)); - for (int j = 0; j < mesh->vertexCount; j++) mesh->indices[j] = (unsigned short)j; - mesh->triangleCount = mesh->vertexCount/3; - } - FillVertexColors(mesh); - UploadMesh(mesh, true); - } - - Mesh tempCube = GenMeshCube(1.0f, 1.0f, 1.0f); - Mesh spatialFrame = { 0 }; - spatialFrame.vertexCount = tempCube.vertexCount; - spatialFrame.triangleCount = tempCube.triangleCount; - spatialFrame.vertices = RL_MALLOC(spatialFrame.vertexCount * 3 * sizeof(float)); - spatialFrame.normals = RL_MALLOC(spatialFrame.vertexCount * 3 * sizeof(float)); - spatialFrame.texcoords = RL_MALLOC(spatialFrame.vertexCount * 2 * sizeof(float)); - spatialFrame.indices = RL_MALLOC(spatialFrame.triangleCount * 3 * sizeof(unsigned short)); - spatialFrame.colors = RL_CALLOC(spatialFrame.vertexCount, sizeof(Color)); - memcpy(spatialFrame.vertices, tempCube.vertices, spatialFrame.vertexCount * 3 * sizeof(float)); - memcpy(spatialFrame.normals, tempCube.normals, spatialFrame.vertexCount * 3 * sizeof(float)); - memcpy(spatialFrame.texcoords, tempCube.texcoords, spatialFrame.vertexCount * 2 * sizeof(float)); - memcpy(spatialFrame.indices, tempCube.indices, spatialFrame.triangleCount * 3 * sizeof(unsigned short)); - for (int i = 0; i < spatialFrame.vertexCount; i++) ((Color *)spatialFrame.colors)[i] = (Color){ 255, 255, 255, 0 }; - for (int i = 0; i < 4; i++) ((Color *)spatialFrame.colors)[i].a = 255; - UnloadMesh(tempCube); - UploadMesh(&spatialFrame, true); - Model spatialFrameModel = LoadModelFromMesh(spatialFrame); - spatialFrameModel.materials[0].shader = customShader; - SetTargetFPS(60); - //-------------------------------------------------------------------------------------- - - while (!WindowShouldClose()) - { - // Update - //---------------------------------------------------------------------------------- - aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); - TOGGLE(KEY_Q, FLAG_ASPECT); - TOGGLE(KEY_SPACE, FLAG_PAUSE); - TOGGLE(KEY_J, FLAG_JUGEMU); - TOGGLE(KEY_O, FLAG_ORTHO); - CYCLE_MESH(KEY_ONE, 0, GEN_CUBE); - CYCLE_MESH(KEY_TWO, 1, GEN_SPHERE); - CYCLE_MESH(KEY_THREE, 2, GEN_KNOT); - - OrthoBlendFactor(GetFrameTime()); - - if (!PAUSED()) meshRotation -= angularVelocity*GetFrameTime(); - - OrbitSpace(&jugemu, GetFrameTime()); - main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; - main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic : fovyPerspective; - - Model *displayModel = &models[targetMesh]; - - PerspectiveCorrectCapture(&main, displayModel, &perspectiveCorrectRenderTexture, meshRotation); - - UpdateSpatialFrame(&main, aspect, near, far, &spatialFrame); - UpdateMeshBuffer(spatialFrameModel.meshes[0], RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, spatialFrame.vertices, spatialFrame.vertexCount*sizeof(Vector3), 0); - //---------------------------------------------------------------------------------- - - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); - - ClearBackground(BLACK); - if (JUGEMU_MODE()) BeginMode3D(jugemu); else BeginMode3D(main); - Vector3 depth = Vector3Normalize(Vector3Subtract(main.target, main.position)); - Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main.up)); - Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); - DrawLine3D(main.position, Vector3Add(main.position, right), NEON_CARROT); - DrawLine3D(main.position, Vector3Add(main.position, up), LILAC); - DrawLine3D(main.position, Vector3Add(main.position, depth), MARINER); - - if (JUGEMU_MODE()) DrawSpatialFrame(&spatialFrame); - - DrawModelEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, WHITE); - - unsigned int cacheID = displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id; - displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = rlGetTextureIdDefault(); - DrawModelWiresEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, MARINER); - rlSetPointSize(4.0f); - DrawModelPointsEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, LILAC); - displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = cacheID; - - if (JUGEMU_MODE()) - { - Vector3 *vertices = (Vector3 *)displayModel->meshes[0].vertices; - Triangle *triangles = (Triangle *)displayModel->meshes[0].indices; - for (int i = 0; i < displayModel->meshes[0].triangleCount; i++) - { - Vector3 a = TranslateRotateScale(vertices[triangles[i][0]], modelPos, modelScale, meshRotation); - Vector3 b = TranslateRotateScale(vertices[triangles[i][1]], modelPos, modelScale, meshRotation); - Vector3 c = TranslateRotateScale(vertices[triangles[i][2]], modelPos, modelScale, meshRotation); - if (Vector3DotProduct(Vector3Normalize(Vector3CrossProduct(Vector3Subtract(b, a), Vector3Subtract(c, a))), depth) > 0.0f) continue; - DrawLine3D(a, Intersect(&main, near, a), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); - DrawLine3D(b, Intersect(&main, near, b), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); - DrawLine3D(c, Intersect(&main, near, c), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); - } - } - spatialFrameModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = perspectiveCorrectRenderTexture.texture; - //NOTE: there is still the alpha threshold issue without custom frag shader... - int useColors = 1; - SetShaderValue(spatialFrameModel.materials[0].shader, useVertexColorsLoc, &useColors, SHADER_UNIFORM_INT); - if (JUGEMU_MODE()) DrawModel(spatialFrameModel, modelPos, 1.0f, WHITE); - - EndMode3D(); - - DrawText("[1]: CUBE [2]: SPHERE [3]: KNOT", 12, 12, fontSize, NEON_CARROT); - DrawText("ARROWS: MOVE | SPACEBAR: PAUSE", 12, 38, fontSize, NEON_CARROT); - DrawText("W S : ZOOM ", 12, 64, fontSize, NEON_CARROT); - DrawText((targetMesh == 0)? "GEN_CUBE" : (targetMesh == 1)? "GEN_SPHERE" : "GEN_KNOT", 12, 205, fontSize, NEON_CARROT); - DrawText("ASPECT [ Q ]:", 12, 392, fontSize, SUNFLOWER); - DrawText((ASPECT_CORRECT())? "CORRECT" : "INCORRECT", 230, 392, fontSize, (ASPECT_CORRECT())? ANAKIWA : CHESTNUT_ROSE); - DrawText("LENS [ O ]:", 510, 366, fontSize, SUNFLOWER); - DrawText((ORTHO_MODE())? "ORTHOGRAPHIC" : "PERSPECTIVE", 630, 366, fontSize, (ORTHO_MODE())? BAHAMA_BLUE : ANAKIWA); - - EndDrawing(); - //---------------------------------------------------------------------------------- - } - - // De-Initialization - //-------------------------------------------------------------------------------------- - UnloadModel(models[0]); - UnloadModel(models[1]); - UnloadModel(models[2]); - UnloadModel(spatialFrameModel); - if (perspectiveCorrectRenderTexture.id) UnloadRenderTexture(perspectiveCorrectRenderTexture); - UnloadShader(customShader); - CloseWindow(); - //-------------------------------------------------------------------------------------- - - return 0; -} - -static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame) -{ - Vector3 depth = Vector3Normalize(Vector3Subtract(main->target, main->position)); - Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main->up)); - Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); - float halfHNear = Lerp(near*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); - float halfWNear = halfHNear*aspect; - float halfHFar = Lerp(far*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); - float halfWFar = halfHFar*aspect; - float halfDepth = 0.5f*(far - near); - Vector3 centerNear = Vector3Add(main->position, Vector3Scale(depth, near)); - - for (int i = 0; i < spatialFrame->vertexCount; ++i) - { - Vector3 offset = Vector3Subtract(((Vector3 *)spatialFrame->vertices)[i], centerNear); - float xSign = (Vector3DotProduct(offset, right) >= 0.0f)? 1.0f : -1.0f; - float ySign = (Vector3DotProduct(offset, up) >= 0.0f)? 1.0f : -1.0f; - float farMask = (Vector3DotProduct(offset, depth) > halfDepth)? 1.0f : 0.0f; - float finalHalfW = halfWNear + farMask*(halfWFar - halfWNear); - float finalHalfH = halfHNear + farMask*(halfHFar - halfHNear); - Vector3 center = Vector3Add(centerNear, Vector3Scale(depth, farMask*2.0f*halfDepth)); - ((Vector3 *)spatialFrame->vertices)[i] = Vector3Add(center, Vector3Add(Vector3Scale(right, xSign*finalHalfW), Vector3Scale(up, ySign*finalHalfH))); - } -} - -static void DrawSpatialFrame(Mesh *spatialFrame) -{ - static int frontFaces[4][2] = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 } }; - static int backFaces[4][2] = { { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 } }; - static int ribFaces[4][2] = { { 0, 4 }, { 1, 7 }, { 2, 6 }, { 3, 5 } }; - static int (*faces[3])[2] = { frontFaces, backFaces, ribFaces }; - - for (int i = 0; i < 3; i++) - for (int j = 0; j < 4; j++) - { - Vector3 startPosition = ((Vector3 *)spatialFrame->vertices)[faces[i][j][0]]; - Vector3 endPosition = ((Vector3 *)spatialFrame->vertices)[faces[i][j][1]]; - DrawLine3D(startPosition, endPosition, (i == 0)? NEON_CARROT : (i == 1)? EGGPLANT : HOPBUSH); - } -} - -static void PerspectiveCorrectCapture(Camera3D *main, Model *model, RenderTexture2D *perspectiveCorrectRenderTexture, float rotation) -{ - BeginTextureMode(*perspectiveCorrectRenderTexture); - ClearBackground(BLANK); - BeginMode3D(*main); - DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); - DrawModelWiresEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, MARINER); - EndMode3D(); - EndTextureMode(); -} - -static void OrbitSpace(Camera3D *jugemu, float dt) -{ - float radius = Vector3Length(jugemu->position); - float azimuth = atan2f(jugemu->position.z, jugemu->position.x); - float horizontalRadius = sqrtf(jugemu->position.x*jugemu->position.x + jugemu->position.z*jugemu->position.z); - float elevation = atan2f(jugemu->position.y, horizontalRadius); - if (IsKeyDown(KEY_LEFT)) azimuth += 1.0f*dt; - if (IsKeyDown(KEY_RIGHT)) azimuth -= 1.0f*dt; - if (IsKeyDown(KEY_UP)) elevation += 1.0f*dt; - if (IsKeyDown(KEY_DOWN)) elevation -= 1.0f*dt; - if (IsKeyDown(KEY_W)) radius -= 1.0f*dt; - if (IsKeyDown(KEY_S)) radius += 1.0f*dt; - elevation = Clamp(elevation, -PI/2 + 0.1f, PI/2 - 0.1f); - jugemu->position.x = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*cosf(azimuth); - jugemu->position.y = Clamp(radius, 0.25f, 10.0f)*sinf(elevation); - jugemu->position.z = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*sinf(azimuth); -} - -static void FillVertexColors(Mesh *mesh) -{ - if (!mesh->colors) mesh->colors = RL_CALLOC(mesh->vertexCount, sizeof(Color)); - Color *colors = (Color *)mesh->colors; - Vector3 *vertices = (Vector3 *)mesh->vertices; - BoundingBox bounds = GetMeshBoundingBox(*mesh); - - for (int i = 0; i < mesh->vertexCount; ++i) - { - Vector3 vertex = vertices[i]; - float nx = (vertex.x - 0.5f*(bounds.min.x + bounds.max.x))/(0.5f*(bounds.max.x - bounds.min.x)); - float ny = (vertex.y - 0.5f*(bounds.min.y + bounds.max.y))/(0.5f*(bounds.max.y - bounds.min.y)); - float nz = (vertex.z - 0.5f*(bounds.min.z + bounds.max.z))/(0.5f*(bounds.max.z - bounds.min.z)); - float len = sqrtf(nx*nx + ny*ny + nz*nz); - colors[i] = (Color){ lrintf(127.5f*(nx/len + 1.0f)), lrintf(127.5f*(ny/len + 1.0f)), lrintf(127.5f*(nz/len + 1.0f)), 255 }; - } -} - -static Vector3 TranslateRotateScale(Vector3 coordinate, Vector3 pos, Vector3 scale, float rotation) -{ - return Vector3Transform(coordinate, MatrixMultiply(MatrixMultiply(MatrixScale(scale.x, scale.y, scale.z), MatrixRotateY(rotation)), MatrixTranslate(pos.x, pos.y, pos.z))); -} - -static Vector3 Intersect(Camera3D *main, float near, Vector3 worldCoord) -{ - Vector3 viewDir = Vector3Normalize(Vector3Subtract(main->target, main->position)); - Vector3 mainCameraToPoint = Vector3Subtract(worldCoord, main->position); - float depthAlongView = Vector3DotProduct(mainCameraToPoint, viewDir); - Vector3 centerNearPlane = Vector3Add(main->position, Vector3Scale(viewDir, near)); - if (depthAlongView <= 0.0f) return centerNearPlane; - float scaleToNear = near/depthAlongView; - Vector3 resultPerspective = Vector3Add(main->position, Vector3Scale(mainCameraToPoint, scaleToNear)); - Vector3 resultOrtho = Vector3Add(worldCoord, Vector3Scale(viewDir, Vector3DotProduct(Vector3Subtract(centerNearPlane, worldCoord), viewDir))); - return Vector3Lerp(resultPerspective, resultOrtho, OrthoBlendFactor(0.0f)); -} - -static float OrthoBlendFactor(float dt) -{ - static float blend = 0.0f; - if (dt > 0.0f) blend = Clamp(blend + ((ORTHO_MODE())? 1.0f : -1.0f)*blendScalar*dt, 0.0f, 1.0f); - return blend; -} \ No newline at end of file diff --git a/examples/core/core_3d_camera_view_opengl33.png b/examples/core/core_3d_camera_view_opengl33.png deleted file mode 100644 index cdac66493e0fcfce95189973e7dafa8a0de39076..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103636 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

ocoe8&2>f__OQm4eE+ZCyx*Fe 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 diff --git a/examples/core/core_3d_that_one_example.c b/examples/core/core_3d_that_one_example.c new file mode 100644 index 000000000..c849ceded --- /dev/null +++ b/examples/core/core_3d_that_one_example.c @@ -0,0 +1,852 @@ +/******************************************************************************************* +* +* raylib [core] example - Fixed-function didactic +* +* RESOURCES: +* - https://en.wikipedia.org/wiki/Fixed-function_(computer_graphics) +* - https://en.wikipedia.org/wiki/Texture_mapping#Perspective_correctness +* - Etay Meiri (OGLDEV) Perspective Projection Tutorial: https://www.youtube.com/watch?v=LhQ85bPCAJ8 +* - Keenan Crane Computer Graphics (CMU 15-462/662): https://www.youtube.com/watch?v=_4Q4O2Kgdo4 +* +* Example complexity rating: [★★★★] 4/4 +* +* Example originally created with raylib 6.0? (target) +* +* Example contributed by IANN (@meisei4) and reviewed by Ramon Santamaria (@raysan5) and community +* +* 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 @meisei4 +* +********************************************************************************************/ +// TODO list: +// 1. finish proper clipping toggle with some sort of visibility mask to geometry outside clip volume (also enable moving mesh out of clip planes more clear/allow moving the main camera's target away from the meshes) +// 2. improve didactic annotations (ideally with spatial labeling rather than simple flat screen overlay) +// 3. improve code didactic, code should read in order of fixed function staging... difficult but long term goal... +// 4. add scripted toggling/navigation of ordered fixed function staging visualization (a "play button"-like thing) +// 5. add some sort of ghosting effect between fixed function stages, to emphasize previous stages perhaps) +// 6. general improvements to toggling and space navigation + +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" +#include +#include +#include + +#if defined(GRAPHICS_API_OPENGL_33) +static const char *vert = + "#version 330\n" + "in vec3 vertexPosition;\n" + "in vec2 vertexTexCoord;\n" + "in vec3 vertexNormal;\n" + "in vec4 vertexColor;\n" + "uniform mat4 mvp;\n" + "out vec2 fragTexCoord;\n" + "out vec4 fragColor;\n" + "uniform int useVertexColors;\n" + "uniform int flipTexcoordY;\n" + "void main()\n" + "{\n" + " if (useVertexColors == 1) {\n" + " fragColor = vertexColor;\n" + " } else {\n" + " fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" + " }\n" + " fragTexCoord = vec2(vertexTexCoord.x, (flipTexcoordY == 1)? (1.0 - vertexTexCoord.y) : vertexTexCoord.y);\n" + " gl_Position = mvp * vec4(vertexPosition, 1.0);\n" + "}\n"; + +static const char *frag = + "#version 330\n" + "in vec2 fragTexCoord;\n" + "in vec4 fragColor;\n" + "uniform sampler2D texture0;\n" + "uniform vec4 colDiffuse;\n" + "out vec4 finalColor;\n" + "void main()\n" + "{\n" + " vec4 texelColor = texture(texture0, fragTexCoord);\n" + " vec4 outColor = texelColor*fragColor*colDiffuse;\n" + " if (outColor.a <= 0.0) discard; \n" + " finalColor = outColor;\n" + "}\n"; + +static Shader customShader = { 0 }; +static int useVertexColorsLoc = -1; +static int flipTexcoordYLoc = -1; +#endif + +#define BAHAMA_BLUE CLITERAL(Color){ 0, 102, 153, 255 } +#define SUNFLOWER CLITERAL(Color){ 255, 204, 153, 255 } +#define PALE_CANARY CLITERAL(Color){ 255, 255, 153, 255 } +#define ANAKIWA CLITERAL(Color){ 153, 204, 255, 255 } +#define MARINER CLITERAL(Color){ 51, 102, 204, 255 } +#define NEON_CARROT CLITERAL(Color){ 255, 153, 51, 255 } +#define EGGPLANT CLITERAL(Color){ 102, 68, 102, 255 } +#define HOPBUSH CLITERAL(Color){ 204, 102, 153, 255 } +#define LILAC CLITERAL(Color){ 204, 153, 204, 255 } +#define RED_DAMASK CLITERAL(Color){ 221, 102, 68, 255 } +#define CHESTNUT_ROSE CLITERAL(Color){ 204, 102, 102, 255 } + +typedef unsigned short Triangle[3]; + +enum Flags +{ + FLAG_NDC = 1u<<0, + FLAG_REFLECT_Y = 1u<<1, FLAG_ASPECT = 1u<<2, + FLAG_PERSPECTIVE_CORRECT = 1u<<3, + FLAG_PAUSE = 1u<<4, + FLAG_COLOR_MODE = 1u<<5, FLAG_TEXTURE_MODE = 1u<<6, + FLAG_JUGEMU = 1u<<7, + FLAG_ORTHO = 1u<<8, + FLAG_CLIP = 1u<<9, + GEN_CUBE = 1u<<10, LOAD_CUBE = 1u<<11, + GEN_SPHERE = 1u<<12, LOAD_SPHERE = 1u<<13, + GEN_KNOT = 1u<<14 +}; + +static unsigned int gflags = FLAG_ASPECT | FLAG_COLOR_MODE | FLAG_JUGEMU | GEN_CUBE; + +#define NDC_SPACE() ((gflags & FLAG_NDC) != 0) +#define REFLECT_Y() ((gflags & FLAG_REFLECT_Y) != 0) +#define ASPECT_CORRECT() ((gflags & FLAG_ASPECT) != 0) +#define PERSPECTIVE_CORRECT() ((gflags & FLAG_PERSPECTIVE_CORRECT) != 0) +#define PAUSED() ((gflags & FLAG_PAUSE) != 0) +#define COLOR_MODE() ((gflags & FLAG_COLOR_MODE) != 0) +#define TEXTURE_MODE() ((gflags & FLAG_TEXTURE_MODE) != 0) +#define JUGEMU_MODE() ((gflags & FLAG_JUGEMU) != 0) +#define ORTHO_MODE() ((gflags & FLAG_ORTHO) != 0) +#define CLIP_MODE() ((gflags & FLAG_CLIP) != 0) +#define TOGGLE(K, F) do { if (IsKeyPressed(K)) { gflags ^= (F); } } while (0) + +static unsigned int targetMesh = 0; +#define NUM_MODELS 5 +#define TARGET_GEN_CUBE() ((gflags & GEN_CUBE) != 0) +#define TARGET_LOAD_CUBE() ((gflags & LOAD_CUBE) != 0) +#define TARGET_GEN_SPHERE() ((gflags & GEN_SPHERE) != 0) +#define TARGET_LOAD_SPHERE() ((gflags & LOAD_SPHERE) != 0) +#define TARGET_GEN_KNOT() ((gflags & GEN_KNOT) != 0) +#define CYCLE_MESH(K, I, F) do { if (IsKeyPressed(K)) { targetMesh = (I); gflags = (gflags & ~(GEN_CUBE|LOAD_CUBE|GEN_SPHERE|LOAD_SPHERE|GEN_KNOT)) | (F); } } while (0) + +static int fontSize = 20; +static float angularVelocity = 1.25f; +static float fovyPerspective = 60.0f; +static float nearPlaneHeightOrthographic = 1.0f; +static float blendScalar = 5.0f; +static Vector3 yAxis = { 0.0f, 1.0f, 0.0f }; +static Vector3 modelPos = { 0.0f, 0.0f, 0.0f }; +static Vector3 modelScale = { 1.0f, 1.0f, 1.0f }; +static Vector3 mainPos = { 0.0f, 0.0f, 2.0f }; +static Vector3 jugemuPos = { 3.0f, 1.0f, 3.0f }; + +static void BasisVector(Camera3D *main, Vector3 *depthOut, Vector3 *rightOut, Vector3 *upOut); +static void WorldToNDCSpace(Camera3D *main, float aspect, float near, float far, Model *world, Model *ndc, float rotation); + +static void DrawModelFilled(Model *model, Texture2D texture, float rotation); +static void DrawModelWiresAndPoints(Model *model, float rotation); +static void DrawNearPlanePoints(Camera3D *main, float aspect, float near, Model *nearPlanePointsModel, Model *displayModel, float rotation); + +static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame); +static void DrawSpatialFrame(Mesh *spatialFrame); + +static void PerspectiveIncorrectCapture(Camera3D *main, float aspect, float near, Model *displayModel, Texture2D meshTexture, float rotation); +#if defined(GRAPHICS_API_OPENGL_33) +static void PerspectiveCorrectCapture(Camera3D *main, Model *model, Texture2D meshTexture, RenderTexture2D *perspectiveCorrectRenderTexture, float rotation); +#else +static void PerspectiveCorrectCapture(Camera3D *main, Model *model, Texture2D meshTexture, Texture2D *perspectiveCorrectTexture, float rotation); +#endif +static void AlphaMaskPunchOut(Image *rgba, Image *mask, unsigned char threshold); +static void FillPlanarTexCoords(Mesh *mesh); +static void FillVertexColors(Mesh *mesh); +static void OrbitSpace(Camera3D *jugemu, float dt); +static Vector3 AspectCorrectAndReflectNearPlane(Vector3 intersect, Vector3 center, Vector3 right, Vector3 up, float xAspect, float yReflect); +static Vector3 TranslateRotateScale(int inverse, Vector3 coordinate, Vector3 pos, Vector3 scale, float rotation); +static Vector3 Intersect(Camera3D *main, float near, Vector3 worldCoord); +static float SpaceBlendFactor(float dt); +static float AspectBlendFactor(float dt); +static float ReflectBlendFactor(float dt); +static float OrthoBlendFactor(float dt); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [core] example - fixed function didactic"); +#if defined(GRAPHICS_API_OPENGL_33) + customShader = LoadShaderFromMemory(vert, frag); + useVertexColorsLoc = GetShaderLocation(customShader, "useVertexColors"); + flipTexcoordYLoc = GetShaderLocation(customShader, "flipTexcoordY"); + RenderTexture2D perspectiveCorrectRenderTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); +#else + Texture2D perspectiveCorrectTexture = (Texture2D){ 0 }; +#endif + float near = 1.0f; + float far = 3.0f; + float aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); + nearPlaneHeightOrthographic = 2.0f*near*tanf(DEG2RAD*fovyPerspective*0.5f); + float meshRotation = 0.0f; + + Camera3D main = { 0 }; + main.position = mainPos; + main.target = modelPos; + main.up = yAxis; + main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; + main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic: fovyPerspective; + + Camera3D jugemu = (Camera3D){ 0 }; + jugemu.position = jugemuPos; + jugemu.target = modelPos; + jugemu.up = yAxis; + jugemu.fovy = fovyPerspective; + jugemu.projection = CAMERA_PERSPECTIVE; + + Model worldModels[NUM_MODELS] = { 0 }; + Model ndcModels[NUM_MODELS] = { 0 }; + Model nearPlanePointsModels[NUM_MODELS] = { 0 }; + Texture2D meshTextures[NUM_MODELS] = { 0 }; + int textureConfig[NUM_MODELS] = { 4, 4, 16, 16, 32 }; + + for (int i = 0; i < NUM_MODELS; i++) + { + if (i == 0) worldModels[0] = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f)); + if (i == 1) worldModels[1] = LoadModel("resources/models/unit_cube.obj"); + if (i == 2) worldModels[2] = LoadModelFromMesh(GenMeshSphere(0.5f, 8, 8)); + if (i == 3) worldModels[3] = LoadModel("resources/models/unit_sphere.obj"); + if (i == 4) worldModels[4] = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 16, 128)); + + Mesh *worldMesh = &worldModels[i].meshes[0]; + if (!worldMesh->indices) + { + worldMesh->indices = RL_CALLOC(worldMesh->vertexCount, sizeof(unsigned short)); + for (int j = 0; j < worldMesh->vertexCount; j++) worldMesh->indices[j] = (unsigned short)j; + worldMesh->triangleCount = worldMesh->vertexCount/3; + } + FillPlanarTexCoords(worldMesh); + FillVertexColors(worldMesh); + + Image textureImage = GenImageChecked(textureConfig[i], textureConfig[i], 1, 1, BLACK, WHITE); + meshTextures[i] = LoadTextureFromImage(textureImage); + UnloadImage(textureImage); + worldModels[i].materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTextures[i]; + + Mesh ndcMesh = (Mesh){ 0 }; + ndcMesh.vertexCount = worldMesh->vertexCount; + ndcMesh.triangleCount = worldMesh->triangleCount; + ndcMesh.vertices = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector3)); + ndcMesh.indices = RL_CALLOC(ndcMesh.triangleCount, sizeof(Triangle)); + memcpy(ndcMesh.indices, worldMesh->indices, ndcMesh.triangleCount*sizeof(Triangle)); + //NOTE: test things by toggling through the LOADED MESHES VS GEN MESHES when removing planar texcoord fill above + if (worldMesh->texcoords) ndcMesh.texcoords = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector2)); + if (worldMesh->texcoords) memcpy(ndcMesh.texcoords, worldMesh->texcoords, ndcMesh.vertexCount*sizeof(Vector2)); + if (worldMesh->colors) ndcMesh.colors = RL_CALLOC(ndcMesh.vertexCount, sizeof(Color)); + if (worldMesh->colors) memcpy(ndcMesh.colors, worldMesh->colors, ndcMesh.vertexCount*sizeof(Color)); + UploadMesh(&ndcMesh, true); //this allows for UpdateMeshBuffer later on, but its rought just to work around genmesh upload being static + ndcModels[i] = LoadModelFromMesh(ndcMesh); + ndcModels[i].materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTextures[i]; + +#if defined(GRAPHICS_API_OPENGL_33) + worldModels[i].materials[0].shader = customShader; + ndcModels[i].materials[0].shader = customShader; +#endif + Mesh nearPlanePoints = (Mesh){ 0 }; + nearPlanePoints.vertexCount = worldMesh->triangleCount*3; + nearPlanePoints.vertices = RL_CALLOC(nearPlanePoints.vertexCount, sizeof(Vector3)); + UploadMesh(&nearPlanePoints, true); + nearPlanePointsModels[i] = LoadModelFromMesh(nearPlanePoints); + } + + Mesh tempCube = GenMeshCube(1.0f, 1.0f, 1.0f); + Mesh spatialFrame = { 0 }; + spatialFrame.vertexCount = tempCube.vertexCount; + spatialFrame.triangleCount = tempCube.triangleCount; + spatialFrame.vertices = RL_MALLOC(spatialFrame.vertexCount * 3 * sizeof(float)); + spatialFrame.normals = RL_MALLOC(spatialFrame.vertexCount * 3 * sizeof(float)); + spatialFrame.texcoords = RL_MALLOC(spatialFrame.vertexCount * 2 * sizeof(float)); + spatialFrame.indices = RL_MALLOC(spatialFrame.triangleCount * 3 * sizeof(unsigned short)); + spatialFrame.colors = RL_CALLOC(spatialFrame.vertexCount, sizeof(Color)); + memcpy(spatialFrame.vertices, tempCube.vertices, spatialFrame.vertexCount * 3 * sizeof(float)); + memcpy(spatialFrame.normals, tempCube.normals, spatialFrame.vertexCount * 3 * sizeof(float)); + memcpy(spatialFrame.texcoords, tempCube.texcoords, spatialFrame.vertexCount * 2 * sizeof(float)); + memcpy(spatialFrame.indices, tempCube.indices, spatialFrame.triangleCount * 3 * sizeof(unsigned short)); + for (int i = 0; i < spatialFrame.vertexCount; i++) ((Color *)spatialFrame.colors)[i] = (Color){ 255, 255, 255, 0 }; + for (int i = 0; i < 4; i++) ((Color *)spatialFrame.colors)[i].a = 255; + UnloadMesh(tempCube); //NOTE: to clean up static mesh -- better would be to allow for gen mesh to have option for dynamic or static + UploadMesh(&spatialFrame, true); + Model spatialFrameModel = LoadModelFromMesh(spatialFrame); +#if defined(GRAPHICS_API_OPENGL_33) + spatialFrameModel.materials[0].shader = customShader; +#endif + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); + TOGGLE(KEY_N, FLAG_NDC); + if (NDC_SPACE()) TOGGLE(KEY_F, FLAG_REFLECT_Y); + TOGGLE(KEY_Q, FLAG_ASPECT); + TOGGLE(KEY_P, FLAG_PERSPECTIVE_CORRECT); + TOGGLE(KEY_SPACE, FLAG_PAUSE); + TOGGLE(KEY_C, FLAG_COLOR_MODE); + TOGGLE(KEY_T, FLAG_TEXTURE_MODE); + TOGGLE(KEY_J, FLAG_JUGEMU); + TOGGLE(KEY_O, FLAG_ORTHO); + TOGGLE(KEY_X, FLAG_CLIP); + CYCLE_MESH(KEY_ONE, 0, GEN_CUBE); + CYCLE_MESH(KEY_TWO, 1, LOAD_CUBE); + CYCLE_MESH(KEY_THREE, 2, GEN_SPHERE); + CYCLE_MESH(KEY_FOUR, 3, LOAD_SPHERE); + CYCLE_MESH(KEY_FIVE, 4, GEN_KNOT); + + float sBlend = SpaceBlendFactor(GetFrameTime()); + AspectBlendFactor(GetFrameTime()); + ReflectBlendFactor(GetFrameTime()); + OrthoBlendFactor(GetFrameTime()); + + if (!PAUSED()) meshRotation -= angularVelocity*GetFrameTime(); + + OrbitSpace(&jugemu, GetFrameTime()); + main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; + main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic : fovyPerspective; + WorldToNDCSpace(&main, aspect, near, far, &worldModels[targetMesh], &ndcModels[targetMesh], meshRotation); + + for (int i = 0; i < ndcModels[targetMesh].meshes[0].vertexCount; i++) + { + Vector3 *worldVertices = (Vector3 *)worldModels[targetMesh].meshes[0].vertices; + Vector3 *ndcVertices = (Vector3 *)ndcModels[targetMesh].meshes[0].vertices; + ndcVertices[i].x = Lerp(worldVertices[i].x, ndcVertices[i].x, sBlend); + ndcVertices[i].y = Lerp(worldVertices[i].y, ndcVertices[i].y, sBlend); + ndcVertices[i].z = Lerp(worldVertices[i].z, ndcVertices[i].z, sBlend); + } + + UpdateMeshBuffer(ndcModels[targetMesh].meshes[0], RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, ndcModels[targetMesh].meshes[0].vertices, ndcModels[targetMesh].meshes[0].vertexCount*sizeof(Vector3), 0); + Model *displayModel = &ndcModels[targetMesh]; + + if (PERSPECTIVE_CORRECT() && TEXTURE_MODE()) + { + #if defined(GRAPHICS_API_OPENGL_33) + PerspectiveCorrectCapture(&main, displayModel, meshTextures[targetMesh], &perspectiveCorrectRenderTexture, meshRotation); + #else + PerspectiveCorrectCapture(&main, displayModel, meshTextures[targetMesh], &perspectiveCorrectTexture, meshRotation); + #endif + } + + UpdateSpatialFrame(&main, aspect, near, far, &spatialFrame); + UpdateMeshBuffer(spatialFrameModel.meshes[0], RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, spatialFrame.vertices, spatialFrame.vertexCount*sizeof(Vector3), 0); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + if (JUGEMU_MODE()) BeginMode3D(jugemu); else BeginMode3D(main); + Vector3 depth, right, up; + BasisVector(&main, &depth, &right, &up); + + DrawLine3D(main.position, Vector3Add(main.position, right), NEON_CARROT); + DrawLine3D(main.position, Vector3Add(main.position, up), LILAC); + DrawLine3D(main.position, Vector3Add(main.position, depth), MARINER); + + if (JUGEMU_MODE()) DrawSpatialFrame(&spatialFrame); + + DrawModelFilled(displayModel, meshTextures[targetMesh], meshRotation); + DrawModelWiresAndPoints(displayModel, meshRotation); + + if (JUGEMU_MODE()) DrawNearPlanePoints(&main, aspect, near, &nearPlanePointsModels[targetMesh], displayModel, meshRotation); + + if (PERSPECTIVE_CORRECT() && TEXTURE_MODE()) + { + #if defined(GRAPHICS_API_OPENGL_33) + spatialFrameModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = perspectiveCorrectRenderTexture.texture; + int useColors = 1; + SetShaderValue(spatialFrameModel.materials[0].shader, useVertexColorsLoc, &useColors, SHADER_UNIFORM_INT); + int flipTexcoordYValue = (NDC_SPACE() && REFLECT_Y())? 1 : 0; + SetShaderValue(spatialFrameModel.materials[0].shader, flipTexcoordYLoc, &flipTexcoordYValue, SHADER_UNIFORM_INT); + + if (JUGEMU_MODE()) DrawModel(spatialFrameModel, modelPos, 1.0f, WHITE); + + flipTexcoordYValue = 0; + SetShaderValue(spatialFrameModel.materials[0].shader, flipTexcoordYLoc, &flipTexcoordYValue, SHADER_UNIFORM_INT); + #else + spatialFrameModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = perspectiveCorrectTexture; + if (JUGEMU_MODE()) DrawModel(spatialFrameModel, modelPos, 1.0f, WHITE); + #endif + } + else + { + if (JUGEMU_MODE()) PerspectiveIncorrectCapture(&main, aspect, near, displayModel, meshTextures[targetMesh], meshRotation); + } + EndMode3D(); + + DrawText("[1-2]: CUBE [3-4]: SPHERE [5]: KNOT", 12, 12, fontSize, NEON_CARROT); + DrawText("ARROWS: MOVE | SPACEBAR: PAUSE", 12, 38, fontSize, NEON_CARROT); + DrawText("W A : ZOOM ", 12, 64, fontSize, NEON_CARROT); + DrawText("CLIP [ X ]:", 12, 94, fontSize, SUNFLOWER); + DrawText((CLIP_MODE())? "ON" : "OFF", 120, 94, fontSize, (CLIP_MODE())? BAHAMA_BLUE : ANAKIWA); + DrawText((targetMesh == 0)? "GEN_CUBE" : (targetMesh == 1)? "LOAD_CUBE" : (targetMesh == 2)? "GEN_SPHERE" : (targetMesh == 3)? "LOAD_SPHERE" : "GEN_KNOT", 12, 205, fontSize, NEON_CARROT); + DrawText("TEXTURE [ T ]:", 570, 12, fontSize, SUNFLOWER); + DrawText((TEXTURE_MODE())? "ON" : "OFF", 740, 12, fontSize, (TEXTURE_MODE())? ANAKIWA : CHESTNUT_ROSE); + DrawText("COLORS [ C ]:", 570, 38, fontSize, SUNFLOWER); + DrawText((COLOR_MODE())? "ON" : "OFF", 740, 38, fontSize, (COLOR_MODE())? ANAKIWA : CHESTNUT_ROSE); + DrawText("ASPECT [ Q ]:", 12, 392, fontSize, SUNFLOWER); + DrawText((ASPECT_CORRECT())? "CORRECT" : "INCORRECT", 230, 392, fontSize, (ASPECT_CORRECT())? ANAKIWA : CHESTNUT_ROSE); + DrawText("PERSPECTIVE [ P ]:", 12, 418, fontSize, SUNFLOWER); + DrawText((PERSPECTIVE_CORRECT())? "CORRECT" : "INCORRECT", 230, 418, fontSize, (PERSPECTIVE_CORRECT())? ANAKIWA : CHESTNUT_ROSE); + DrawText("LENS [ O ]:", 510, 366, fontSize, SUNFLOWER); + DrawText((ORTHO_MODE())? "ORTHOGRAPHIC" : "PERSPECTIVE", 630, 366, fontSize, (ORTHO_MODE())? BAHAMA_BLUE : ANAKIWA); + DrawText("SPACE [ N ]:", 520, 392, fontSize, SUNFLOWER); + DrawText((NDC_SPACE())? "NDC" : "WORLD", 655, 392, fontSize, (NDC_SPACE())? BAHAMA_BLUE : ANAKIWA); + if (NDC_SPACE()) + { + DrawText("REFLECT [ F ]:", 530, 418, fontSize, SUNFLOWER); + DrawText((REFLECT_Y())? "Y_DOWN" : "Y_UP", 695, 418, fontSize, (REFLECT_Y())? ANAKIWA : CHESTNUT_ROSE); + } + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + for (int i = 0; i < NUM_MODELS; i++) + { + UnloadModel(worldModels[i]); + UnloadModel(ndcModels[i]); + UnloadModel(nearPlanePointsModels[i]); + if (meshTextures[i].id) UnloadTexture(meshTextures[i]); + } + UnloadModel(spatialFrameModel); +#if defined(GRAPHICS_API_OPENGL_33) + if (perspectiveCorrectRenderTexture.id) UnloadRenderTexture(perspectiveCorrectRenderTexture); + UnloadShader(customShader); +#endif + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +static void BasisVector(Camera3D *main, Vector3 *depthOut, Vector3 *rightOut, Vector3 *upOut) +{ + Vector3 depth = Vector3Normalize(Vector3Subtract(main->target, main->position)); + Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main->up)); + Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); + *depthOut = depth; + *rightOut = right; + *upOut = up; +} + +static void WorldToNDCSpace(Camera3D *main, float aspect, float near, float far, Model *world, Model *ndc, float rotation) +{ + Vector3 depth, right, up; + BasisVector(main, &depth, &right, &up); + float halfHNear = Lerp(near*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); + float halfWNear = Lerp(halfHNear, halfHNear*aspect, AspectBlendFactor(0.0f)); + float halfDepthNDC = Lerp(halfHNear, 0.5f*(far - near), Lerp(AspectBlendFactor(0.0f), 0.0f, OrthoBlendFactor(0.0f))); + Vector3 centerNearPlane = Vector3Add(main->position, Vector3Scale(depth, near)); + Vector3 centerNDCCube = Vector3Add(centerNearPlane, Vector3Scale(depth, halfDepthNDC)); + + for (int i = 0; i < world->meshes[0].vertexCount; i++) + { + Vector3 worldVertex = TranslateRotateScale(0, ((Vector3 *)world->meshes[0].vertices)[i], modelPos, modelScale, rotation); + float signedDepth = Vector3DotProduct(Vector3Subtract(worldVertex, main->position), depth); + Vector3 intersectionCoord = Intersect(main, near, worldVertex); + Vector3 clipPlaneVector = Vector3Subtract(intersectionCoord, centerNearPlane); + float xNDC = Vector3DotProduct(clipPlaneVector, right)/halfWNear; + float yNDC = Vector3DotProduct(clipPlaneVector, up)/halfHNear; + float zNDC = Lerp((far + near - 2.0f*far*near/signedDepth)/(far - near), 2.0f*(signedDepth - near)/(far - near) - 1.0f, OrthoBlendFactor(0.0f)); + Vector3 scaledRight = Vector3Scale(right, xNDC*halfWNear); + Vector3 scaledUp = Vector3Scale(up, yNDC*halfHNear); + Vector3 scaledDepth = Vector3Scale(depth, zNDC*halfDepthNDC); + Vector3 offset = Vector3Add(Vector3Add(scaledRight, scaledUp), scaledDepth); + Vector3 scaledNDCCoord = Vector3Add(centerNDCCube, offset); + ((Vector3 *)ndc->meshes[0].vertices)[i] = TranslateRotateScale(1, scaledNDCCoord, modelPos, modelScale, rotation); + } +} + +static void DrawModelFilled(Model *model, Texture2D texture, float rotation) +{ + if (!(COLOR_MODE() || TEXTURE_MODE())) return; +#if defined(GRAPHICS_API_OPENGL_33) + int useColors = COLOR_MODE() ? 1 : 0; + SetShaderValue(model->materials[0].shader, useVertexColorsLoc, &useColors, SHADER_UNIFORM_INT); +#else + Color *cacheColors = (Color *)model->meshes[0].colors; + if (TEXTURE_MODE() && !COLOR_MODE()) model->meshes[0].colors = NULL; +#endif + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = (TEXTURE_MODE())? texture.id : rlGetTextureIdDefault(); + DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = rlGetTextureIdDefault(); + +#if defined(GRAPHICS_API_OPENGL_11) + model->meshes[0].colors = (unsigned char *)cacheColors; +#endif +} + +static void DrawModelWiresAndPoints(Model *model, float rotation) +{ +#if defined(GRAPHICS_API_OPENGL_33) + int useColors = (CLIP_MODE())? 1 : 0; + SetShaderValue(model->materials[0].shader, useVertexColorsLoc, &useColors, SHADER_UNIFORM_INT); +#else + Color *cacheColors = (Color *)model->meshes[0].colors; + if (!CLIP_MODE()) model->meshes[0].colors = NULL; +#endif + unsigned int cacheID = model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id; + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = rlGetTextureIdDefault(); + + DrawModelWiresEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, MARINER); + rlSetPointSize(4.0f); + DrawModelPointsEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, LILAC); + + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = cacheID; +#if defined(GRAPHICS_API_OPENGL_11) + model->meshes[0].colors = (unsigned char *)cacheColors; +#endif +} + +static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame) +{ + Vector3 depth, right, up; + BasisVector(main, &depth, &right, &up); + float halfHNear = Lerp(near*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); + float halfWNear = Lerp(halfHNear, halfHNear*aspect, AspectBlendFactor(0.0f)); + float halfHFar = Lerp(far*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); + float halfWFar = Lerp(halfHFar, halfHFar*aspect, AspectBlendFactor(0.0f)); + float halfDepthNdc = Lerp(halfHNear, 0.5f*(far - near), Lerp(AspectBlendFactor(0.0f), 0.0f, OrthoBlendFactor(0.0f))); + float halfDepth = Lerp(0.5f*(far - near), halfDepthNdc, SpaceBlendFactor(0.0f)); + float farHalfW = Lerp(halfWFar, halfWNear, SpaceBlendFactor(0.0f)); + float farHalfH = Lerp(halfHFar, halfHNear, SpaceBlendFactor(0.0f)); + Vector3 centerNear = Vector3Add(main->position, Vector3Scale(depth, near)); + + for (int i = 0; i < spatialFrame->vertexCount; ++i) + { + Vector3 offset = Vector3Subtract(((Vector3 *)spatialFrame->vertices)[i], centerNear); + float xSign = (Vector3DotProduct(offset, right) >= 0.0f)? 1.0f : -1.0f; + float ySign = (Vector3DotProduct(offset, up) >= 0.0f)? 1.0f : -1.0f; + float farMask = (Vector3DotProduct(offset, depth) > halfDepth)? 1.0f : 0.0f; + float finalHalfW = halfWNear + farMask*(farHalfW - halfWNear); + float finalHalfH = halfHNear + farMask*(farHalfH - halfHNear); + Vector3 center = Vector3Add(centerNear, Vector3Scale(depth, farMask*2.0f*halfDepth)); + ((Vector3 *)spatialFrame->vertices)[i] = Vector3Add(center, Vector3Add(Vector3Scale(right, xSign*finalHalfW), Vector3Scale(up, ySign*finalHalfH))); + } +} + +static void DrawSpatialFrame(Mesh *spatialFrame) +{ + static int frontFaces[4][2] = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 } }; + static int backFaces[4][2] = { { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 } }; + static int ribFaces[4][2] = { { 0, 4 }, { 1, 7 }, { 2, 6 }, { 3, 5 } }; + static int (*faces[3])[2] = { frontFaces, backFaces, ribFaces }; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 4; j++) + { + Vector3 startPosition = ((Vector3 *)spatialFrame->vertices)[faces[i][j][0]]; + Vector3 endPosition = ((Vector3 *)spatialFrame->vertices)[faces[i][j][1]]; + DrawLine3D(startPosition, endPosition, (i == 0)? NEON_CARROT : (i == 1)? EGGPLANT : HOPBUSH); + } +} + +static void DrawNearPlanePoints(Camera3D *main, float aspect, float near, Model *nearPlanePointsModel, Model *displayModel, float rotation) +{ + Vector3 depth, right, up; + BasisVector(main, &depth, &right, &up); + Mesh *displayMesh = &displayModel->meshes[0]; + int nearPlaneVertexCount = 0; + int capacity = displayMesh->triangleCount*3; + Mesh *nearPlanePointsMesh = &nearPlanePointsModel->meshes[0]; + Vector3 centerNearPlane = Vector3Add(main->position, Vector3Scale(depth, near)); + float xAspect = Lerp(1.0f/aspect, 1.0f, AspectBlendFactor(0.0f)); + float yReflect = Lerp(1.0f, -1.0f, ReflectBlendFactor(0.0f)); + + for (int i = 0; i < displayMesh->triangleCount; i++) + { + Vector3 *vertices = (Vector3 *)displayMesh->vertices; + Triangle *triangles = (Triangle *)displayMesh->indices; + + Vector3 a = TranslateRotateScale(0, vertices[triangles[i][0]], modelPos, modelScale, rotation); + Vector3 b = TranslateRotateScale(0, vertices[triangles[i][1]], modelPos, modelScale, rotation); + Vector3 c = TranslateRotateScale(0, vertices[triangles[i][2]], modelPos, modelScale, rotation); + + // test if front facing or not (ugly one-liner -- comment out will ~double the rays, which is fine) + if (Vector3DotProduct(Vector3Normalize(Vector3CrossProduct(Vector3Subtract(b, a), Vector3Subtract(c, a))), depth) > 0.0f) continue; + Vector3 intersectionPoints[3] = { Intersect(main, near, a), Intersect(main, near, b), Intersect(main, near, c) }; + + for (int j = 0; j < 3 && nearPlaneVertexCount < capacity; ++j) + { + Vector3 corrected = AspectCorrectAndReflectNearPlane(intersectionPoints[j], centerNearPlane, right, up, xAspect, yReflect); + DrawLine3D((Vector3[]){ a, b, c }[j], corrected, (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); + ((Vector3 *)nearPlanePointsMesh->vertices)[nearPlaneVertexCount] = corrected; + nearPlaneVertexCount++; + } + } + + nearPlanePointsMesh->vertexCount = nearPlaneVertexCount; + UpdateMeshBuffer(*nearPlanePointsMesh, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, nearPlanePointsMesh->vertices, nearPlanePointsMesh->vertexCount*sizeof(Vector3), 0); + rlSetPointSize(3.0f); + DrawModelPoints(*nearPlanePointsModel, modelPos, 1.0f, LILAC); +} + +static void PerspectiveIncorrectCapture(Camera3D *main, float aspect, float near, Model *displayModel, Texture2D meshTexture, float rotation) +{ + Vector3 depth, right, up; + BasisVector(main, &depth, &right, &up); + Mesh *displayMesh = &displayModel->meshes[0]; + Vector3 centerNearPlane = Vector3Add(main->position, Vector3Scale(depth, near)); + float xAspect = Lerp(1.0f/aspect, 1.0f, AspectBlendFactor(0.0f)); + float yReflect = Lerp(1.0f, -1.0f, ReflectBlendFactor(0.0f)); + rlColor4ub(WHITE.r, WHITE.g, WHITE.b, WHITE.a); // just to emphasize raylib Colors are ub 0~255 not floats + if (TEXTURE_MODE() && displayMesh->texcoords) + { + rlSetTexture(meshTexture.id); + rlEnableTexture(meshTexture.id); + } + else + { + rlDisableTexture(); + } + if (!TEXTURE_MODE() && !COLOR_MODE()) + { + rlEnableWireMode(); + rlColor4ub(MARINER.r, MARINER.g, MARINER.b, MARINER.a); + } + rlBegin(RL_TRIANGLES); + + for (int i = 0; i < displayMesh->triangleCount; i++) + { + Triangle *triangles = (Triangle *)displayMesh->indices; + Vector3 *vertices = (Vector3 *)displayMesh->vertices; + Color *colors = (Color *)displayMesh->colors; + Vector2 *texcoords = (Vector2 *)displayMesh->texcoords; + + Vector3 a = TranslateRotateScale(0, vertices[triangles[i][0]], modelPos, modelScale, rotation); + Vector3 b = TranslateRotateScale(0, vertices[triangles[i][1]], modelPos, modelScale, rotation); + Vector3 c = TranslateRotateScale(0, vertices[triangles[i][2]], modelPos, modelScale, rotation); + + a = AspectCorrectAndReflectNearPlane(Intersect(main, near, a), centerNearPlane, right, up, xAspect, yReflect); + b = AspectCorrectAndReflectNearPlane(Intersect(main, near, b), centerNearPlane, right, up, xAspect, yReflect); + c = AspectCorrectAndReflectNearPlane(Intersect(main, near, c), centerNearPlane, right, up, xAspect, yReflect); + + if (COLOR_MODE() && displayMesh->colors) rlColor4ub(colors[triangles[i][0]].r, colors[triangles[i][0]].g, colors[triangles[i][0]].b, colors[triangles[i][0]].a); + if (TEXTURE_MODE() && displayMesh->texcoords) rlTexCoord2f(texcoords[triangles[i][0]].x, texcoords[triangles[i][0]].y); + rlVertex3f(a.x, a.y, a.z); + // vertex winding!! to account for reflection toggle (will draw the inside of the geometry otherwise) + int secondIndex = (NDC_SPACE() && REFLECT_Y())? triangles[i][2] : triangles[i][1]; + Vector3 secondVertex = (NDC_SPACE() && REFLECT_Y())? c : b; + if (COLOR_MODE() && displayMesh->colors) rlColor4ub(colors[secondIndex].r, colors[secondIndex].g, colors[secondIndex].b, colors[secondIndex].a); + if (TEXTURE_MODE() && displayMesh->texcoords) rlTexCoord2f(texcoords[secondIndex].x, texcoords[secondIndex].y); + rlVertex3f(secondVertex.x, secondVertex.y, secondVertex.z); + + int thirdIndex = (NDC_SPACE() && REFLECT_Y())? triangles[i][1] : triangles[i][2]; + Vector3 thirdVertex = (NDC_SPACE() && REFLECT_Y())? b : c; + if (COLOR_MODE() && displayMesh->colors) rlColor4ub(colors[thirdIndex].r, colors[thirdIndex].g, colors[thirdIndex].b, colors[thirdIndex].a); + if (TEXTURE_MODE() && displayMesh->texcoords) rlTexCoord2f(texcoords[thirdIndex].x, texcoords[thirdIndex].y); + rlVertex3f(thirdVertex.x, thirdVertex.y, thirdVertex.z); + } + + rlEnd(); + rlDrawRenderBatchActive(); //NOTE: this is what allows lines in opengl33 + rlSetTexture(rlGetTextureIdDefault()); + rlDisableTexture(); + rlDisableWireMode(); +} + +#if defined(GRAPHICS_API_OPENGL_33) +static void PerspectiveCorrectCapture(Camera3D *main, Model *model, Texture2D meshTexture, RenderTexture2D *perspectiveCorrectRenderTexture, float rotation) +{ + BeginTextureMode(*perspectiveCorrectRenderTexture); + ClearBackground(BLANK); + BeginMode3D(*main); + int useColors = (COLOR_MODE())? 1 : 0; + SetShaderValue(model->materials[0].shader, useVertexColorsLoc, &useColors, SHADER_UNIFORM_INT); + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTexture; + DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); + EndMode3D(); + EndTextureMode(); +} +#endif + +#if defined(GRAPHICS_API_OPENGL_11) +static void PerspectiveCorrectCapture(Camera3D *main, Model *model, Texture2D meshTexture, Texture2D *perspectiveCorrectTexture, float rotation) +{ + unsigned char *cacheColors = model->meshes[0].colors; + if (TEXTURE_MODE() && !COLOR_MODE()) model->meshes[0].colors = NULL; + + ClearBackground(BLACK); + + BeginMode3D(*main); + Texture2D previousTexture = model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture; + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTexture; + DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture = previousTexture; + EndMode3D(); + + Image rgba = LoadImageFromScreen(); + ImageFormat(&rgba, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); + model->meshes[0].colors = cacheColors; + ClearBackground(BLACK); + + BeginMode3D(*main); + Texture2D cacheTexture = model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture; + Color cacheMaterialColor = model->materials[0].maps[MATERIAL_MAP_ALBEDO].color; + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture = (Texture2D){ 0 }; + model->materials[0].maps[MATERIAL_MAP_ALBEDO].color = WHITE; + DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture = cacheTexture; + model->materials[0].maps[MATERIAL_MAP_ALBEDO].color = cacheMaterialColor; + EndMode3D(); + + Image mask = LoadImageFromScreen(); + AlphaMaskPunchOut(&rgba, &mask, 1); + ImageFlipVertical(&rgba); + if ((NDC_SPACE() && REFLECT_Y())) ImageFlipVertical(&rgba); // FLIP AGAIN.. it works visually, but is not clear and feels hacked/ugly + if ((perspectiveCorrectTexture->id != 0)) + UpdateTexture(*perspectiveCorrectTexture, rgba.data); + else + *perspectiveCorrectTexture = LoadTextureFromImage(rgba); + UnloadImage(mask); + UnloadImage(rgba); +} +#endif + +static void OrbitSpace(Camera3D *jugemu, float dt) +{ + float radius = Vector3Length(jugemu->position); + float azimuth = atan2f(jugemu->position.z, jugemu->position.x); + float horizontalRadius = sqrtf(jugemu->position.x*jugemu->position.x + jugemu->position.z*jugemu->position.z); + float elevation = atan2f(jugemu->position.y, horizontalRadius); + if (IsKeyDown(KEY_LEFT)) azimuth += 1.0f*dt; + if (IsKeyDown(KEY_RIGHT)) azimuth -= 1.0f*dt; + if (IsKeyDown(KEY_UP)) elevation += 1.0f*dt; + if (IsKeyDown(KEY_DOWN)) elevation -= 1.0f*dt; + if (IsKeyDown(KEY_W)) radius -= 1.0f*dt; + if (IsKeyDown(KEY_S)) radius += 1.0f*dt; + elevation = Clamp(elevation, -PI/2 + 0.1f, PI/2 - 0.1f); + jugemu->position.x = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*cosf(azimuth); + jugemu->position.y = Clamp(radius, 0.25f, 10.0f)*sinf(elevation); + jugemu->position.z = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*sinf(azimuth); +} + +static void AlphaMaskPunchOut(Image *rgba, Image *mask, unsigned char threshold) +{ + Image maskCopy = ImageCopy(*mask); + ImageFormat(&maskCopy, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); + ImageFormat(rgba, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); + unsigned char *maskGrayScale = maskCopy.data; + Color *colors = rgba->data; + int pixelCount = rgba->width*rgba->height; + for (size_t i = 0; i < pixelCount; ++i) colors[i].a = (maskGrayScale[i] > threshold)? 255 : 0; + UnloadImage(maskCopy); +} + +static void FillPlanarTexCoords(Mesh *mesh) +{ + //NOTE: opengl33, please just always provide texcoords for the obj, opengl11 allows null because its easy and works with ps2 isolation tests, + // but otherwise they are always assumed to exist + if (!mesh->texcoords) + { + mesh->texcoords = RL_CALLOC(mesh->vertexCount, sizeof(Vector2)); + // Demonstrate planar mapping ("reasonable" default): https://en.wikipedia.org/wiki/Planar_projection. + BoundingBox bounds = GetMeshBoundingBox(*mesh); + Vector3 extents = Vector3Subtract(bounds.max, bounds.min); + for (int j = 0; j < mesh->vertexCount; j++) + { + float x = ((Vector3 *)mesh->vertices)[j].x; + float y = ((Vector3 *)mesh->vertices)[j].y; + ((Vector2 *)mesh->texcoords)[j].x = (x - bounds.min.x)/extents.x; + ((Vector2 *)mesh->texcoords)[j].y = (y - bounds.min.y)/extents.y; + } + } +} + +static void FillVertexColors(Mesh *mesh) +{ + if (!mesh->colors) mesh->colors = RL_CALLOC(mesh->vertexCount, sizeof(Color)); + Color *colors = (Color *)mesh->colors; + Vector3 *vertices = (Vector3 *)mesh->vertices; + BoundingBox bounds = GetMeshBoundingBox(*mesh); + + for (int i = 0; i < mesh->vertexCount; ++i) + { + Vector3 vertex = vertices[i]; + float nx = (vertex.x - 0.5f*(bounds.min.x + bounds.max.x))/(0.5f*(bounds.max.x - bounds.min.x)); + float ny = (vertex.y - 0.5f*(bounds.min.y + bounds.max.y))/(0.5f*(bounds.max.y - bounds.min.y)); + float nz = (vertex.z - 0.5f*(bounds.min.z + bounds.max.z))/(0.5f*(bounds.max.z - bounds.min.z)); + float len = sqrtf(nx*nx + ny*ny + nz*nz); + colors[i] = (Color){ lrintf(127.5f*(nx/len + 1.0f)), lrintf(127.5f*(ny/len + 1.0f)), lrintf(127.5f*(nz/len + 1.0f)), 255 }; + } +} + +static Vector3 AspectCorrectAndReflectNearPlane(Vector3 intersect, Vector3 center, Vector3 right, Vector3 up, float xAspect, float yReflect) +{ + Vector3 centerDistance = Vector3Subtract(intersect, center); + float x = Vector3DotProduct(centerDistance, right); + float y = Vector3DotProduct(centerDistance, up); + return Vector3Add(center, Vector3Add(Vector3Scale(right, x*xAspect), Vector3Scale(up, y*yReflect))); +} + +static Vector3 TranslateRotateScale(int inverse, Vector3 coordinate, Vector3 pos, Vector3 scale, float rotation) +{ + Matrix matrix = MatrixMultiply(MatrixMultiply(MatrixScale(scale.x, scale.y, scale.z), MatrixRotateY(rotation)), MatrixTranslate(pos.x, pos.y, pos.z)); + Matrix result = inverse ? MatrixInvert(matrix) : matrix; + return Vector3Transform(coordinate, result); +} + +static Vector3 Intersect(Camera3D *main, float near, Vector3 worldCoord) +{ + Vector3 viewDir = Vector3Normalize(Vector3Subtract(main->target, main->position)); + Vector3 mainCameraToPoint = Vector3Subtract(worldCoord, main->position); + float depthAlongView = Vector3DotProduct(mainCameraToPoint, viewDir); + Vector3 centerNearPlane = Vector3Add(main->position, Vector3Scale(viewDir, near)); + if (depthAlongView <= 0.0f) return centerNearPlane; + float scaleToNear = near/depthAlongView; + Vector3 resultPerspective = Vector3Add(main->position, Vector3Scale(mainCameraToPoint, scaleToNear)); + Vector3 resultOrtho = Vector3Add(worldCoord, Vector3Scale(viewDir, Vector3DotProduct(Vector3Subtract(centerNearPlane, worldCoord), viewDir))); + Vector3 result = Vector3Lerp(resultPerspective,resultOrtho, OrthoBlendFactor(0.0f)); + return result; +} + +static float SpaceBlendFactor(float dt) +{ + static float blend = 0.0f; + if (dt > 0.0f) blend = Clamp(blend + ((NDC_SPACE())? 1.0f : -1.0f)*blendScalar*dt, 0.0f, 1.0f); + return blend; +} + +static float AspectBlendFactor(float dt) +{ + static float blend = 0.0f; + if (dt > 0.0f) blend = Clamp(blend + ((ASPECT_CORRECT())? 1.0f : -1.0f)*blendScalar*dt, 0.0f, 1.0f); + return blend; +} + +static float ReflectBlendFactor(float dt) +{ + static float blend = 0.0f; + if (dt > 0.0f) + { + float target = (NDC_SPACE() && REFLECT_Y())? 1.0f : 0.0f; + float direction = (blend < target)? 1.0f : (blend > target)? -1.0f : 0.0f; + blend = Clamp(blend + direction*blendScalar*dt, 0.0f, 1.0f); + } + return blend; +} + +static float OrthoBlendFactor(float dt) +{ + static float blend = 0.0f; + if (dt > 0.0f) blend = Clamp(blend + ((ORTHO_MODE())? 1.0f : -1.0f)*blendScalar*dt, 0.0f, 1.0f); + return blend; +} \ No newline at end of file