diff --git a/examples/Makefile b/examples/Makefile index b5e96807938d..b41de1944d01 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -521,6 +521,9 @@ CORE = \ core/core_3d_camera_mode \ core/core_3d_camera_split_screen \ core/core_3d_picking \ + core/core_3d_fixed_function_didactic \ + core/core_3d_camera_view_opengl11 \ + 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 000000000000..12537f14aee3 --- /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_opengl11.c b/examples/core/core_3d_camera_view_opengl11.c new file mode 100644 index 000000000000..064f3ec99040 --- /dev/null +++ b/examples/core/core_3d_camera_view_opengl11.c @@ -0,0 +1,382 @@ +/******************************************************************************************* +* +* 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 + +#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 modelScale = { 1.0f, 1.0f, 1.0f }; +static Vector3 modelPos = { 0.0f, 0.0f, 0.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, Texture2D *perspectiveCorrectTexture, float rotation); +static void AlphaMaskPunchOut(Image *rgba, Image *mask, unsigned char threshold); +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"); + Texture2D perspectiveCorrectTexture = (Texture2D){ 0 }; + 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 = (Vector3){ 0.0f, 0.0f, 2.0f }; + main.target = modelPos; + main.up = (Vector3){ 0.0f, 1.0f, 0.0f }; + main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; + main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic: fovyPerspective; + + Camera3D jugemu = (Camera3D){ 0 }; + jugemu.position = (Vector3){ 3.0f, 1.0f, 3.0f }; + jugemu.target = modelPos; + jugemu.up = (Vector3){ 0.0f, 1.0f, 0.0f }; + 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]; + 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); + } + + Mesh spatialFrame = GenMeshCube(1.0f, 1.0f, 1.0f); + spatialFrame.colors = RL_CALLOC(spatialFrame.vertexCount, sizeof(Color)); + 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; + Model spatialFrameModel = LoadModelFromMesh(spatialFrame); + + 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, &perspectiveCorrectTexture, meshRotation); + + UpdateSpatialFrame(&main, aspect, near, far, &spatialFrame); + //---------------------------------------------------------------------------------- + + // 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, (Vector3){ 0.0f, 1.0f, 0.0f }, RAD2DEG*meshRotation, modelScale, WHITE); + + Color *cacheColors = (Color *)displayModel->meshes[0].colors; + displayModel->meshes[0].colors = NULL; + 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, (Vector3){ 0.0f, 1.0f, 0.0f }, RAD2DEG*meshRotation, modelScale, MARINER); + rlSetPointSize(4.0f); + DrawModelPointsEx(*displayModel, modelPos, (Vector3){ 0.0f, 1.0f, 0.0f }, RAD2DEG*meshRotation, modelScale, LILAC); + displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = cacheID; + displayModel->meshes[0].colors = (unsigned char *)cacheColors; + + 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 = perspectiveCorrectTexture; + 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); + 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, Texture2D *perspectiveCorrectTexture, float rotation) +{ + ClearBackground(BLACK); + BeginMode3D(*main); + DrawModelEx(*model, modelPos, (Vector3){ 0.0f, 1.0f, 0.0f }, RAD2DEG*rotation, modelScale, WHITE); + Color *cacheColors = (Color *)model->meshes[0].colors; + model->meshes[0].colors = NULL; + DrawModelWiresEx(*model, modelPos, (Vector3){ 0.0f, 1.0f, 0.0f }, RAD2DEG*rotation, modelScale, MARINER); + model->meshes[0].colors = (unsigned char *)cacheColors; + EndMode3D(); + + Image rgba = LoadImageFromScreen(); + ImageFormat(&rgba, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); + + 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, (Vector3){ 0.0f, 1.0f, 0.0f }, 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 ((perspectiveCorrectTexture->id != 0)) + UpdateTexture(*perspectiveCorrectTexture, rgba.data); + else + *perspectiveCorrectTexture = LoadTextureFromImage(rgba); + UnloadImage(mask); + UnloadImage(rgba); +} + +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 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_opengl11.png b/examples/core/core_3d_camera_view_opengl11.png new file mode 100644 index 000000000000..d45126cb7cf6 Binary files /dev/null and b/examples/core/core_3d_camera_view_opengl11.png differ diff --git a/examples/core/core_3d_fixed_function_didactic.c b/examples/core/core_3d_fixed_function_didactic.c new file mode 100644 index 000000000000..1ac2cd7af75d --- /dev/null +++ b/examples/core/core_3d_fixed_function_didactic.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 jugemuPosIso = { 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 = jugemuPosIso; + 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 diff --git a/examples/core/core_3d_fixed_function_didactic.png b/examples/core/core_3d_fixed_function_didactic.png new file mode 100644 index 000000000000..c57d5950b35f Binary files /dev/null and b/examples/core/core_3d_fixed_function_didactic.png differ 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 000000000000..c849cededdc2 --- /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 diff --git a/examples/core/resources/models/unit_cube.obj b/examples/core/resources/models/unit_cube.obj new file mode 100644 index 000000000000..215fed62eae9 --- /dev/null +++ b/examples/core/resources/models/unit_cube.obj @@ -0,0 +1,27 @@ +o unit_cube +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 0.5 -0.5 +v -0.5 0.5 -0.5 +v -0.5 -0.5 0.5 +v 0.5 -0.5 0.5 +v 0.5 0.5 0.5 +v -0.5 0.5 0.5 +#comment these out to compare opengl11 planar auto fill with just using this and opengl33 +vt 0.0 0.0 +vt 1.0 0.0 +vt 1.0 1.0 +vt 0.0 1.0 +s off +f 1/1 4/4 3/3 +f 1/1 3/3 2/2 +f 5/1 6/2 7/3 +f 5/1 7/3 8/4 +f 1/1 5/1 8/4 +f 1/1 8/4 4/4 +f 2/2 3/3 7/3 +f 2/2 7/3 6/2 +f 4/4 8/4 7/3 +f 4/4 7/3 3/3 +f 1/1 2/2 6/2 +f 1/1 6/2 5/1 diff --git a/examples/core/resources/models/unit_sphere.obj b/examples/core/resources/models/unit_sphere.obj new file mode 100644 index 000000000000..cf84ba070a82 --- /dev/null +++ b/examples/core/resources/models/unit_sphere.obj @@ -0,0 +1,172 @@ +o unit_sphere +v 0.0000 0.5000 0.0000 +v 0.1913 0.4619 0.0000 +v 0.1353 0.4619 0.1353 +v 0.0000 0.4619 0.1913 +v -0.1353 0.4619 0.1353 +v -0.1913 0.4619 0.0000 +v -0.1353 0.4619 -0.1353 +v -0.0000 0.4619 -0.1913 +v 0.1353 0.4619 -0.1353 +v 0.3536 0.3536 0.0000 +v 0.2500 0.3536 0.2500 +v 0.0000 0.3536 0.3536 +v -0.2500 0.3536 0.2500 +v -0.3536 0.3536 0.0000 +v -0.2500 0.3536 -0.2500 +v -0.0000 0.3536 -0.3536 +v 0.2500 0.3536 -0.2500 +v 0.4619 0.1913 0.0000 +v 0.3266 0.1913 0.3266 +v 0.0000 0.1913 0.4619 +v -0.3266 0.1913 0.3266 +v -0.4619 0.1913 0.0000 +v -0.3266 0.1913 -0.3266 +v -0.0000 0.1913 -0.4619 +v 0.3266 0.1913 -0.3266 +v 0.5000 0.0000 0.0000 +v 0.3536 0.0000 0.3536 +v 0.0000 0.0000 0.5000 +v -0.3536 0.0000 0.3536 +v -0.5000 0.0000 0.0000 +v -0.3536 0.0000 -0.3536 +v -0.0000 0.0000 -0.5000 +v 0.3536 0.0000 -0.3536 +v 0.4619 -0.1913 0.0000 +v 0.3266 -0.1913 0.3266 +v 0.0000 -0.1913 0.4619 +v -0.3266 -0.1913 0.3266 +v -0.4619 -0.1913 0.0000 +v -0.3266 -0.1913 -0.3266 +v -0.0000 -0.1913 -0.4619 +v 0.3266 -0.1913 -0.3266 +v 0.3536 -0.3536 0.0000 +v 0.2500 -0.3536 0.2500 +v 0.0000 -0.3536 0.3536 +v -0.2500 -0.3536 0.2500 +v -0.3536 -0.3536 0.0000 +v -0.2500 -0.3536 -0.2500 +v -0.0000 -0.3536 -0.3536 +v 0.2500 -0.3536 -0.2500 +v 0.1913 -0.4619 0.0000 +v 0.1353 -0.4619 0.1353 +v 0.0000 -0.4619 0.1913 +v -0.1353 -0.4619 0.1353 +v -0.1913 -0.4619 0.0000 +v -0.1353 -0.4619 -0.1353 +v -0.0000 -0.4619 -0.1913 +v 0.1353 -0.4619 -0.1353 +v 0.0000 -0.5000 0.0000 +s off +f 1 3 2 +f 1 4 3 +f 1 5 4 +f 1 6 5 +f 1 7 6 +f 1 8 7 +f 1 9 8 +f 1 2 9 +f 2 3 10 +f 3 11 10 +f 3 4 11 +f 4 12 11 +f 4 5 12 +f 5 13 12 +f 5 6 13 +f 6 14 13 +f 6 7 14 +f 7 15 14 +f 7 8 15 +f 8 16 15 +f 8 9 16 +f 9 17 16 +f 9 2 17 +f 2 10 17 +f 10 11 18 +f 11 19 18 +f 11 12 19 +f 12 20 19 +f 12 13 20 +f 13 21 20 +f 13 14 21 +f 14 22 21 +f 14 15 22 +f 15 23 22 +f 15 16 23 +f 16 24 23 +f 16 17 24 +f 17 25 24 +f 17 10 25 +f 10 18 25 +f 18 19 26 +f 19 27 26 +f 19 20 27 +f 20 28 27 +f 20 21 28 +f 21 29 28 +f 21 22 29 +f 22 30 29 +f 22 23 30 +f 23 31 30 +f 23 24 31 +f 24 32 31 +f 24 25 32 +f 25 33 32 +f 25 18 33 +f 18 26 33 +f 26 27 34 +f 27 35 34 +f 27 28 35 +f 28 36 35 +f 28 29 36 +f 29 37 36 +f 29 30 37 +f 30 38 37 +f 30 31 38 +f 31 39 38 +f 31 32 39 +f 32 40 39 +f 32 33 40 +f 33 41 40 +f 33 26 41 +f 26 34 41 +f 34 35 42 +f 35 43 42 +f 35 36 43 +f 36 44 43 +f 36 37 44 +f 37 45 44 +f 37 38 45 +f 38 46 45 +f 38 39 46 +f 39 47 46 +f 39 40 47 +f 40 48 47 +f 40 41 48 +f 41 49 48 +f 41 34 49 +f 34 42 49 +f 42 43 50 +f 43 51 50 +f 43 44 51 +f 44 52 51 +f 44 45 52 +f 45 53 52 +f 45 46 53 +f 46 54 53 +f 46 47 54 +f 47 55 54 +f 47 48 55 +f 48 56 55 +f 48 49 56 +f 49 57 56 +f 49 42 57 +f 42 50 57 +f 58 50 51 +f 58 51 52 +f 58 52 53 +f 58 53 54 +f 58 54 55 +f 58 55 56 +f 58 56 57 +f 58 57 50 diff --git a/src/rmodels.c b/src/rmodels.c index 3c904c396a91..35b086972323 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1435,7 +1435,7 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) #define GL_COLOR_ARRAY 0x8076 #define GL_TEXTURE_COORD_ARRAY 0x8078 - rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id); + if (mesh.texcoords && material.maps[MATERIAL_MAP_DIFFUSE].texture.id > 0) rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id); if (mesh.animVertices) rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.animVertices); else rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices); @@ -4434,10 +4434,12 @@ static Model LoadOBJ(const char *fileName) model.meshes[i].vertices = (float *)MemAlloc(sizeof(float)*vertexCount*3); model.meshes[i].normals = (float *)MemAlloc(sizeof(float)*vertexCount*3); - model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2); #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2); model.meshes[i].colors = (unsigned char *)MemAlloc(sizeof(unsigned char)*vertexCount*4); #else + if (objAttributes.texcoords != NULL && objAttributes.num_texcoords > 0) model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2); + else model.meshes[i].texcoords = NULL; model.meshes[i].colors = NULL; #endif } @@ -4493,16 +4495,11 @@ static Model LoadOBJ(const char *fileName) for (int i = 0; i < 3; i++) model.meshes[meshIndex].vertices[localMeshVertexCount*3 + i] = objAttributes.vertices[vertIndex*3 + i]; - if ((objAttributes.texcoords != NULL) && (texcordIndex != TINYOBJ_INVALID_INDEX) && (texcordIndex >= 0)) + if ((objAttributes.texcoords != NULL) && (texcordIndex != TINYOBJ_INVALID_INDEX) && (texcordIndex >= 0) && (model.meshes[meshIndex].texcoords)) { for (int i = 0; i < 2; i++) model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + i] = objAttributes.texcoords[texcordIndex*2 + i]; model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1]; } - else - { - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 0] = 0.0f; - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 0.0f; - } if ((objAttributes.normals != NULL) && (normalIndex != TINYOBJ_INVALID_INDEX) && (normalIndex >= 0)) {