From 44226c995a6f33ed77410acc58d1ad7b5224876a Mon Sep 17 00:00:00 2001 From: iann Date: Fri, 31 Oct 2025 04:17:27 +0900 Subject: [PATCH 1/6] cleaner commit on review branch --- examples/Makefile | 1 + .../core/core_3d_fixed_function_didactic.c | 646 ++++++++++++++++++ .../core/core_3d_fixed_function_didactic.png | Bin 0 -> 58096 bytes src/rmodels.c | 13 +- 4 files changed, 652 insertions(+), 8 deletions(-) create mode 100644 examples/core/core_3d_fixed_function_didactic.c create mode 100644 examples/core/core_3d_fixed_function_didactic.png diff --git a/examples/Makefile b/examples/Makefile index b5e96807938d..239c6974ccd1 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -521,6 +521,7 @@ CORE = \ core/core_3d_camera_mode \ core/core_3d_camera_split_screen \ core/core_3d_picking \ + core/core_3d_fixed_function_didactic \ core/core_automation_events \ core/core_basic_screen_manager \ core/core_basic_window \ 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..213fd2c062ba --- /dev/null +++ b/examples/core/core_3d_fixed_function_didactic.c @@ -0,0 +1,646 @@ +/******************************************************************************************* +* +* 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. add proper clipping to the target meshes to show intuition there (e.g. move mesh out of clip planes or 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 +#include + +#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 +}; + +static unsigned int gflags = FLAG_ASPECT | FLAG_COLOR_MODE; + +#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 TOGGLE(K, F) do { if (IsKeyPressed(K)) { gflags ^= (F); } } while (0) + +static int fontSize = 20; +static float angularVelocity = 1.25f; +static float fovy = 60.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, Mesh *mesh, 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, Mesh *mesh, Texture2D meshTexture, float rotation); +static void PerspectiveCorrectCapture(Camera3D *main, Model *model, Texture2D meshTexture, 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 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); + +//------------------------------------------------------------------------------------ +// 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"); + + float aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); + float meshRotation = 0.0f; + + Camera3D main = { 0 }; + main.position = mainPos; + main.target = modelPos; + main.up = yAxis; + main.fovy = fovy; + main.projection = CAMERA_PERSPECTIVE; + + Camera3D jugemu = (Camera3D){ 0 }; + jugemu.position = jugemuPosIso; + jugemu.target = modelPos; + jugemu.up = yAxis; + jugemu.fovy = fovy; + jugemu.projection = CAMERA_PERSPECTIVE; + + // Model worldModel = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f)); + // Image textureImage = GenImageChecked(4, 4, 1, 1, BLACK, WHITE); + + // Model worldModel = LoadModelFromMesh(GenMeshSphere(0.5f, 8, 8)); + // Image textureImage = GenImageChecked(16, 16, 1, 1, BLACK, WHITE); + + // Model worldModel = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 8, 64)); + Model worldModel = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 16, 128)); + Image textureImage = GenImageChecked(32, 32, 1, 1, BLACK, WHITE); + + Texture2D meshTexture = LoadTextureFromImage(textureImage); + UnloadImage(textureImage); + + if (!worldModel.meshes[0].indices) + { + worldModel.meshes[0].indices = RL_CALLOC(worldModel.meshes[0].vertexCount, sizeof(unsigned short)); + for (int i = 0; i < worldModel.meshes[0].vertexCount; i++) worldModel.meshes[0].indices[i] = (unsigned short)i; + worldModel.meshes[0].triangleCount = worldModel.meshes[0].vertexCount/3; + } + FillVertexColors(&worldModel.meshes[0]); + + Mesh ndcMesh = (Mesh){ 0 }; + ndcMesh.vertexCount = worldModel.meshes[0].vertexCount; + ndcMesh.triangleCount = worldModel.meshes[0].triangleCount; + ndcMesh.vertices = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector3)); + ndcMesh.texcoords = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector2)); + ndcMesh.indices = RL_CALLOC(ndcMesh.triangleCount, sizeof(Triangle)); + ndcMesh.colors = RL_CALLOC(ndcMesh.vertexCount, sizeof(Color)); + memcpy(ndcMesh.colors, worldModel.meshes[0].colors, ndcMesh.vertexCount*sizeof(Color)); + memcpy(ndcMesh.texcoords, worldModel.meshes[0].texcoords, ndcMesh.vertexCount*sizeof(Vector2)); + memcpy(ndcMesh.indices, worldModel.meshes[0].indices, ndcMesh.triangleCount*sizeof(Triangle)); + Model ndcModel = LoadModelFromMesh(ndcMesh); + + Mesh nearPlanePoints = (Mesh){ 0 }; + nearPlanePoints.vertexCount = worldModel.meshes[0].triangleCount*3; + nearPlanePoints.vertices = RL_CALLOC(nearPlanePoints.vertexCount, sizeof(Vector3)); + Model nearPlanePointsModel = LoadModelFromMesh(nearPlanePoints); + + worldModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTexture; + ndcModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTexture; + + Texture2D perspectiveCorrectTexture = (Texture2D){ 0 }; + 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()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + float far = 3.0f; + float near = 1.0f; + 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); + + float sBlend = SpaceBlendFactor(GetFrameTime()); + AspectBlendFactor(GetFrameTime()); + ReflectBlendFactor(GetFrameTime()); + + if (!PAUSED()) meshRotation -= angularVelocity*GetFrameTime(); + + OrbitSpace(&jugemu, GetFrameTime()); + + WorldToNDCSpace(&main, aspect, near, far, &worldModel, &ndcModel, meshRotation); + + for (int i = 0; i < ndcModel.meshes[0].vertexCount; i++) + { + Vector3 *worldVertices = (Vector3 *)worldModel.meshes[0].vertices; + Vector3 *ndcVertices = (Vector3 *)ndcModel.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); + } + + Model *displayModel = &ndcModel; + Mesh *displayMesh = &ndcModel.meshes[0]; + + if (PERSPECTIVE_CORRECT() && TEXTURE_MODE()) + { + PerspectiveCorrectCapture(&main, displayModel, meshTexture, &perspectiveCorrectTexture, meshRotation); + } + + UpdateSpatialFrame(&main, aspect, near, far, &spatialFrame); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + + BeginMode3D(jugemu); + 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); + + DrawSpatialFrame(spatialFrame); + + DrawModelFilled(displayModel, meshTexture, meshRotation); + DrawModelWiresAndPoints(displayModel, meshRotation); + + DrawNearPlanePoints(&main, aspect, near, &nearPlanePointsModel, displayMesh, meshRotation); + + if (PERSPECTIVE_CORRECT() && TEXTURE_MODE()) + { + spatialFrameModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = perspectiveCorrectTexture; + DrawModel(spatialFrameModel, modelPos, 1.0f, WHITE); + } + else + { + PerspectiveIncorrectCapture(&main, aspect, near, displayMesh, meshTexture, meshRotation); + } + EndMode3D(); + + DrawText("ARROWS: MOVE | SPACEBAR: PAUSE", 12, 12, fontSize, NEON_CARROT); + DrawText("W A : ZOOM", 12, 38, 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("SPACE [ N ]:", 530, 392, fontSize, SUNFLOWER); + DrawText((NDC_SPACE())? "NDC" : "WORLD", 665, 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 + //-------------------------------------------------------------------------------------- + UnloadModel(worldModel); + UnloadModel(ndcModel); + UnloadModel(nearPlanePointsModel); + UnloadModel(spatialFrameModel); + UnloadTexture(meshTexture); + + 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 = near*tanf(DEG2RAD*main->fovy*0.5f); + float halfWNear = Lerp(halfHNear, halfHNear*aspect, AspectBlendFactor(0.0f)); + float halfDepthNDC = Lerp(halfHNear, 0.5f*(far - near), AspectBlendFactor(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 = (far + near - 2.0f*far*near/signedDepth)/(far - near); + 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; + Color *cacheColors = (Color *)model->meshes[0].colors; + if (TEXTURE_MODE() && !COLOR_MODE()) model->meshes[0].colors = NULL; + + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = (TEXTURE_MODE())? texture.id : 0; + DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = 0; + + model->meshes[0].colors = (unsigned char *)cacheColors; +} + +static void DrawModelWiresAndPoints(Model *model, float rotation) +{ + Color *cacheColors = (Color *)model->meshes[0].colors; + unsigned int cacheID = model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id; + model->meshes[0].colors = NULL; + model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = 0; + + 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; + model->meshes[0].colors = (unsigned char *)cacheColors; +} + +static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame) +{ + Vector3 depth, right, up; + BasisVector(main, &depth, &right, &up); + float halfHNear = near*tanf(DEG2RAD*main->fovy*0.5f); + float halfWNear = Lerp(halfHNear, halfHNear*aspect, AspectBlendFactor(0.0f)); + float halfHFar = far*tanf(DEG2RAD*main->fovy*0.5f); + float halfWFar = Lerp(halfHFar, halfHFar*aspect, AspectBlendFactor(0.0f)); + float halfDepthNdc = Lerp(halfHNear, 0.5f*(far - near), AspectBlendFactor(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, Mesh *mesh, float rotation) +{ + Vector3 depth, right, up; + BasisVector(main, &depth, &right, &up); + int nearPlaneVertexCount = 0; + int capacity = mesh->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 < mesh->triangleCount; i++) + { + Vector3 *vertices = (Vector3 *)mesh->vertices; + Triangle *triangles = (Triangle *)mesh->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; + rlSetPointSize(3.0f); + DrawModelPoints(*nearPlanePointsModel, modelPos, 1.0f, LILAC); +} + +static void PerspectiveIncorrectCapture(Camera3D *main, float aspect, float near, Mesh *mesh, Texture2D meshTexture, float rotation) +{ + Vector3 depth, right, up; + BasisVector(main, &depth, &right, &up); + 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()) + 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 < mesh->triangleCount; i++) + { + Triangle *triangles = (Triangle *)mesh->indices; + Vector3 *vertices = (Vector3 *)mesh->vertices; + Color *colors = (Color *)mesh->colors; + Vector2 *texcoords = (Vector2 *)mesh->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()) rlColor4ub(colors[triangles[i][0]].r, colors[triangles[i][0]].g, colors[triangles[i][0]].b, colors[triangles[i][0]].a); + if (TEXTURE_MODE()) 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()) rlColor4ub(colors[secondIndex].r, colors[secondIndex].g, colors[secondIndex].b, colors[secondIndex].a); + if (TEXTURE_MODE()) 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()) rlColor4ub(colors[thirdIndex].r, colors[thirdIndex].g, colors[thirdIndex].b, colors[thirdIndex].a); + if (TEXTURE_MODE()) rlTexCoord2f(texcoords[thirdIndex].x, texcoords[thirdIndex].y); + rlVertex3f(thirdVertex.x, thirdVertex.y, thirdVertex.z); + } + + rlEnd(); + rlDisableTexture(); + rlDisableWireMode(); +} + +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); +} + +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.5f*dt; + if (IsKeyDown(KEY_RIGHT)) azimuth -= 1.5f*dt; + if (IsKeyDown(KEY_UP)) elevation += 1.0f*dt; + if (IsKeyDown(KEY_DOWN)) elevation -= 1.0f*dt; + if (IsKeyDown(KEY_W)) radius -= 2.0f*dt; + if (IsKeyDown(KEY_S)) radius += 2.0f*dt; + elevation = Clamp(elevation, -M_PI_2 + 0.1f, M_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 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); + if (depthAlongView <= 0.0f) return Vector3Add(main->position, Vector3Scale(viewDir, near)); + float scaleToNear = near/depthAlongView; + return Vector3Add(main->position, Vector3Scale(mainCameraToPoint, scaleToNear)); +} + +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; +} 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 0000000000000000000000000000000000000000..c57d5950b35f0be1f67f27e968a6a07c2696f18b GIT binary patch literal 58096 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
Gn?zr(U_86p3g;N}z z03#Px{YQ&|!N86xv;pc)|G4I9!R3|>_%*P|q1(-k*PSeh4gaV#$O98f8(0XKqDMeJ z*Po#Bzf+VIgnD+oPQFUZW6a%aapO*j*pWwp&bQ(L{@xJg$29kj5Vlp(ejE z7wxw%n%^@hca`<~D0BCxh=g5Kjlq>9+JW=$@@&Z`Q}kowtM&Z@ifSru$+R0fGM)Cz zEG5W^`e1UnW){%#Xs9RGkO{u>)fXH-xJ)DWFQb>U!!$D5`V?0&DwFDkWM9Ee+;56~ zjv>B~FVC5(HQOdu^ag%b5q8K?F6ee-Q~9isDo zcDCrv#r>@Ay}Ji7pWFjD1roqcHm!ns0{a{2``g>jaM?$9*^#xpH~drwa0#qy&N(|K z(OuY<18wV^xEGN(r*(BWT1ZdC+(YMs=gqVJ@vx~B9Dj0SI@kGi)r0~Vi41_Z)Dj%s zZ-;ww`9$Tv_ylUPf=vMDyJI)ZS!NoN+Vn`1#6bb{-8-wwvSSkrO+qBLUOMC0e%@QH zcv1UkwSnLF>j%4yyKgE<*)gR=b^r6>nGqe z&eRPMY4mLe9B>Q&1&R-h3M;y*6y+-J{$uyiZhh-v+{WKqth-Hz`Fnc1w-1CUS7kiL z^`_ovoHwvI;?5CY`3;ENMjXyy%W=(mGVqTA@bUkbz5YkYDcXptcJ8sqf&YR4`o*2S z-4_f^QL0?%Z=G=IoyXx}zyC$QIY>aJ9$se;75m!A=f6H0q6J9ulOI7s~4;(n@- z<6`vj_;>CBE8hXEfvP_TDsy}^2ZGN0SH9Ux2PSA0$54$bB1_k?-NX#^x~2G^Y>l)L z0B(*(!=1zYTGO$&*{0_Tv3iAcu4su2B6KrIkH&;@8O z$JoX^Lg<2&78T$Btc3F;65vH~ukVCmVt1morFK^Q|NXK^RYXjCkzqQ}@ln7t7^MYV z@Q1sWVfgP!VqOZ#268YZD>>K_n7BN3fB$*;5xM}>5m@elW;81~>HqH^nY#hpAB~q0YNdkk~LC^iBCW$ zpa!-di|_wCl6$m}Y-E>({J+UZJBIE$!kHjBCj|IC{y#Js5R(15wG-&ki5)P=Zz$z{ Usl&Zi2>7?n+{)~s2|42b0b`FYBLDyZ literal 0 HcmV?d00001 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)) { From e552a83ee9b497ea767c297bfae28561dd570a47 Mon Sep 17 00:00:00 2001 From: iann Date: Sun, 2 Nov 2025 06:08:57 +0900 Subject: [PATCH 2/6] add orthographic toggle and mesh cycling to prepare for branch to core_3d_camera_view simplification --- .../core/core_3d_fixed_function_didactic.c | 248 +++++++++++------- examples/core/resources/models/unit_cube.obj | 22 ++ .../core/resources/models/unit_sphere.obj | 173 ++++++++++++ 3 files changed, 352 insertions(+), 91 deletions(-) create mode 100644 examples/core/resources/models/unit_cube.obj create mode 100644 examples/core/resources/models/unit_sphere.obj diff --git a/examples/core/core_3d_fixed_function_didactic.c b/examples/core/core_3d_fixed_function_didactic.c index 213fd2c062ba..c25edd8c6151 100644 --- a/examples/core/core_3d_fixed_function_didactic.c +++ b/examples/core/core_3d_fixed_function_didactic.c @@ -21,7 +21,7 @@ * ********************************************************************************************/ // TODO list: -// 1. add proper clipping to the target meshes to show intuition there (e.g. move mesh out of clip planes or allow moving the main camera's target away from the meshes) +// 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) @@ -53,15 +53,19 @@ typedef unsigned short Triangle[3]; enum Flags { FLAG_NDC = 1u<<0, - FLAG_REFLECT_Y = 1u<<1, - FLAG_ASPECT = 1u<<2, + 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_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; +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) @@ -70,11 +74,24 @@ static unsigned int gflags = FLAG_ASPECT | FLAG_COLOR_MODE; #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 fovy = 60.0f; +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 }; @@ -90,7 +107,7 @@ static void DrawModelWiresAndPoints(Model *model, float rotation); static void DrawNearPlanePoints(Camera3D *main, float aspect, float near, Model *nearPlanePointsModel, Mesh *mesh, float rotation); static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame); -static void DrawSpatialFrame(Mesh spatialFrame); +static void DrawSpatialFrame(Mesh *spatialFrame); static void PerspectiveIncorrectCapture(Camera3D *main, float aspect, float near, Mesh *mesh, Texture2D meshTexture, float rotation); static void PerspectiveCorrectCapture(Camera3D *main, Model *model, Texture2D meshTexture, Texture2D *perspectiveCorrectTexture, float rotation); @@ -104,6 +121,7 @@ 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 @@ -116,64 +134,87 @@ int main(void) const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [core] example - fixed function didactic"); - + 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.fovy = fovy; - main.projection = CAMERA_PERSPECTIVE; + 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 = fovy; + jugemu.fovy = fovyPerspective; jugemu.projection = CAMERA_PERSPECTIVE; - // Model worldModel = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f)); - // Image textureImage = GenImageChecked(4, 4, 1, 1, BLACK, WHITE); - - // Model worldModel = LoadModelFromMesh(GenMeshSphere(0.5f, 8, 8)); - // Image textureImage = GenImageChecked(16, 16, 1, 1, BLACK, WHITE); + 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 }; - // Model worldModel = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 8, 64)); - Model worldModel = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 16, 128)); - Image textureImage = GenImageChecked(32, 32, 1, 1, BLACK, WHITE); + 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)); - Texture2D meshTexture = LoadTextureFromImage(textureImage); - UnloadImage(textureImage); + Mesh *worldMesh = &worldModels[i].meshes[0]; - if (!worldModel.meshes[0].indices) - { - worldModel.meshes[0].indices = RL_CALLOC(worldModel.meshes[0].vertexCount, sizeof(unsigned short)); - for (int i = 0; i < worldModel.meshes[0].vertexCount; i++) worldModel.meshes[0].indices[i] = (unsigned short)i; - worldModel.meshes[0].triangleCount = worldModel.meshes[0].vertexCount/3; + 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; + } + if (!worldMesh->texcoords) + { + worldMesh->texcoords = (float *)MemAlloc(sizeof(float)*worldMesh->vertexCount*2); + // Demonstrate planar mapping ("reasonable" default): https://en.wikipedia.org/wiki/Planar_projection. + BoundingBox bounds = GetMeshBoundingBox(*worldMesh); + Vector3 extents = Vector3Subtract(bounds.max, bounds.min); + for (int j = 0; j < worldMesh->vertexCount; j++) + { + float x = ((Vector3 *)worldMesh->vertices)[j].x; + float y = ((Vector3 *)worldMesh->vertices)[j].y; + ((Vector2 *)worldMesh->texcoords)[j].x = (x - bounds.min.x)/extents.x; + ((Vector2 *)worldMesh->texcoords)[j].y = (y - bounds.min.y)/extents.y; + } + } + 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.texcoords = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector2)); + ndcMesh.indices = RL_CALLOC(ndcMesh.triangleCount, sizeof(Triangle)); + ndcMesh.colors = RL_CALLOC(ndcMesh.vertexCount, sizeof(Color)); + memcpy(ndcMesh.colors, worldMesh->colors, ndcMesh.vertexCount*sizeof(Color)); + memcpy(ndcMesh.texcoords, worldMesh->texcoords, ndcMesh.vertexCount*sizeof(Vector2)); + memcpy(ndcMesh.indices, worldMesh->indices, ndcMesh.triangleCount*sizeof(Triangle)); + ndcModels[i] = LoadModelFromMesh(ndcMesh); + ndcModels[i].materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTextures[i]; + + Mesh nearPlanePoints = (Mesh){ 0 }; + nearPlanePoints.vertexCount = worldMesh->triangleCount*3; + nearPlanePoints.vertices = RL_CALLOC(nearPlanePoints.vertexCount, sizeof(Vector3)); + nearPlanePointsModels[i] = LoadModelFromMesh(nearPlanePoints); } - FillVertexColors(&worldModel.meshes[0]); - - Mesh ndcMesh = (Mesh){ 0 }; - ndcMesh.vertexCount = worldModel.meshes[0].vertexCount; - ndcMesh.triangleCount = worldModel.meshes[0].triangleCount; - ndcMesh.vertices = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector3)); - ndcMesh.texcoords = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector2)); - ndcMesh.indices = RL_CALLOC(ndcMesh.triangleCount, sizeof(Triangle)); - ndcMesh.colors = RL_CALLOC(ndcMesh.vertexCount, sizeof(Color)); - memcpy(ndcMesh.colors, worldModel.meshes[0].colors, ndcMesh.vertexCount*sizeof(Color)); - memcpy(ndcMesh.texcoords, worldModel.meshes[0].texcoords, ndcMesh.vertexCount*sizeof(Vector2)); - memcpy(ndcMesh.indices, worldModel.meshes[0].indices, ndcMesh.triangleCount*sizeof(Triangle)); - Model ndcModel = LoadModelFromMesh(ndcMesh); - - Mesh nearPlanePoints = (Mesh){ 0 }; - nearPlanePoints.vertexCount = worldModel.meshes[0].triangleCount*3; - nearPlanePoints.vertices = RL_CALLOC(nearPlanePoints.vertexCount, sizeof(Vector3)); - Model nearPlanePointsModel = LoadModelFromMesh(nearPlanePoints); - - worldModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTexture; - ndcModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTexture; Texture2D perspectiveCorrectTexture = (Texture2D){ 0 }; Mesh spatialFrame = GenMeshCube(1.0f, 1.0f, 1.0f); @@ -189,8 +230,6 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - float far = 3.0f; - float near = 1.0f; aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); TOGGLE(KEY_N, FLAG_NDC); if (NDC_SPACE()) TOGGLE(KEY_F, FLAG_REFLECT_Y); @@ -199,32 +238,42 @@ int main(void) 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); - WorldToNDCSpace(&main, aspect, near, far, &worldModel, &ndcModel, meshRotation); - - for (int i = 0; i < ndcModel.meshes[0].vertexCount; i++) + for (int i = 0; i < ndcModels[targetMesh].meshes[0].vertexCount; i++) { - Vector3 *worldVertices = (Vector3 *)worldModel.meshes[0].vertices; - Vector3 *ndcVertices = (Vector3 *)ndcModel.meshes[0].vertices; + 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); } - Model *displayModel = &ndcModel; - Mesh *displayMesh = &ndcModel.meshes[0]; + Model *displayModel = &ndcModels[targetMesh]; + Mesh *displayMesh = &ndcModels[targetMesh].meshes[0]; if (PERSPECTIVE_CORRECT() && TEXTURE_MODE()) { - PerspectiveCorrectCapture(&main, displayModel, meshTexture, &perspectiveCorrectTexture, meshRotation); + PerspectiveCorrectCapture(&main, displayModel, meshTextures[targetMesh], &perspectiveCorrectTexture, meshRotation); } UpdateSpatialFrame(&main, aspect, near, far, &spatialFrame); @@ -235,8 +284,7 @@ int main(void) BeginDrawing(); ClearBackground(BLACK); - - BeginMode3D(jugemu); + if (JUGEMU_MODE()) BeginMode3D(jugemu); else BeginMode3D(main); Vector3 depth, right, up; BasisVector(&main, &depth, &right, &up); @@ -244,26 +292,30 @@ int main(void) DrawLine3D(main.position, Vector3Add(main.position, up), LILAC); DrawLine3D(main.position, Vector3Add(main.position, depth), MARINER); - DrawSpatialFrame(spatialFrame); + if (JUGEMU_MODE()) DrawSpatialFrame(&spatialFrame); - DrawModelFilled(displayModel, meshTexture, meshRotation); + DrawModelFilled(displayModel, meshTextures[targetMesh], meshRotation); DrawModelWiresAndPoints(displayModel, meshRotation); - DrawNearPlanePoints(&main, aspect, near, &nearPlanePointsModel, displayMesh, meshRotation); + if (JUGEMU_MODE()) DrawNearPlanePoints(&main, aspect, near, &nearPlanePointsModels[targetMesh], displayMesh, meshRotation); if (PERSPECTIVE_CORRECT() && TEXTURE_MODE()) { spatialFrameModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = perspectiveCorrectTexture; - DrawModel(spatialFrameModel, modelPos, 1.0f, WHITE); + if (JUGEMU_MODE()) DrawModel(spatialFrameModel, modelPos, 1.0f, WHITE); } else { - PerspectiveIncorrectCapture(&main, aspect, near, displayMesh, meshTexture, meshRotation); + if (JUGEMU_MODE()) PerspectiveIncorrectCapture(&main, aspect, near, displayMesh, meshTextures[targetMesh], meshRotation); } EndMode3D(); - DrawText("ARROWS: MOVE | SPACEBAR: PAUSE", 12, 12, fontSize, NEON_CARROT); - DrawText("W A : ZOOM", 12, 38, fontSize, NEON_CARROT); + 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); @@ -272,8 +324,10 @@ int main(void) 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("SPACE [ N ]:", 530, 392, fontSize, SUNFLOWER); - DrawText((NDC_SPACE())? "NDC" : "WORLD", 665, 392, fontSize, (NDC_SPACE())? BAHAMA_BLUE : ANAKIWA); + 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); @@ -286,11 +340,14 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - UnloadModel(worldModel); - UnloadModel(ndcModel); - UnloadModel(nearPlanePointsModel); + 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); - UnloadTexture(meshTexture); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- @@ -312,9 +369,9 @@ static void WorldToNDCSpace(Camera3D *main, float aspect, float near, float far, { Vector3 depth, right, up; BasisVector(main, &depth, &right, &up); - float halfHNear = near*tanf(DEG2RAD*main->fovy*0.5f); + 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), 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)); @@ -326,7 +383,7 @@ static void WorldToNDCSpace(Camera3D *main, float aspect, float near, float far, Vector3 clipPlaneVector = Vector3Subtract(intersectionCoord, centerNearPlane); float xNDC = Vector3DotProduct(clipPlaneVector, right)/halfWNear; float yNDC = Vector3DotProduct(clipPlaneVector, up)/halfHNear; - float zNDC = (far + near - 2.0f*far*near/signedDepth)/(far - near); + 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); @@ -341,7 +398,6 @@ static void DrawModelFilled(Model *model, Texture2D texture, float rotation) if (!(COLOR_MODE() || TEXTURE_MODE())) return; Color *cacheColors = (Color *)model->meshes[0].colors; if (TEXTURE_MODE() && !COLOR_MODE()) model->meshes[0].colors = NULL; - model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = (TEXTURE_MODE())? texture.id : 0; DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = 0; @@ -353,9 +409,8 @@ static void DrawModelWiresAndPoints(Model *model, float rotation) { Color *cacheColors = (Color *)model->meshes[0].colors; unsigned int cacheID = model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id; - model->meshes[0].colors = NULL; + if (!CLIP_MODE()) model->meshes[0].colors = NULL; model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = 0; - DrawModelWiresEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, MARINER); rlSetPointSize(4.0f); DrawModelPointsEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, LILAC); @@ -368,11 +423,11 @@ static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float f { Vector3 depth, right, up; BasisVector(main, &depth, &right, &up); - float halfHNear = near*tanf(DEG2RAD*main->fovy*0.5f); + 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 = far*tanf(DEG2RAD*main->fovy*0.5f); + 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), 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)); @@ -391,7 +446,7 @@ static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float f } } -static void DrawSpatialFrame(Mesh spatialFrame) +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 } }; @@ -401,8 +456,8 @@ static void DrawSpatialFrame(Mesh spatialFrame) 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]]; + 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); } } @@ -552,12 +607,12 @@ static void OrbitSpace(Camera3D *jugemu, float dt) 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.5f*dt; - if (IsKeyDown(KEY_RIGHT)) azimuth -= 1.5f*dt; + 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 -= 2.0f*dt; - if (IsKeyDown(KEY_S)) radius += 2.0f*dt; + if (IsKeyDown(KEY_W)) radius -= 1.0f*dt; + if (IsKeyDown(KEY_S)) radius += 1.0f*dt; elevation = Clamp(elevation, -M_PI_2 + 0.1f, M_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); @@ -614,9 +669,13 @@ 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); - if (depthAlongView <= 0.0f) return Vector3Add(main->position, Vector3Scale(viewDir, near)); + Vector3 centerNearPlane = Vector3Add(main->position, Vector3Scale(viewDir, near)); + if (depthAlongView <= 0.0f) return centerNearPlane; float scaleToNear = near/depthAlongView; - return Vector3Add(main->position, Vector3Scale(mainCameraToPoint, scaleToNear)); + 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) @@ -644,3 +703,10 @@ static float ReflectBlendFactor(float dt) } 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..49121401d2bc --- /dev/null +++ b/examples/core/resources/models/unit_cube.obj @@ -0,0 +1,22 @@ +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 +s off +f 1 4 3 +f 1 3 2 +f 5 6 7 +f 5 7 8 +f 1 5 8 +f 1 8 4 +f 2 3 7 +f 2 7 6 +f 4 8 7 +f 4 7 3 +f 1 2 6 +f 1 6 5 diff --git a/examples/core/resources/models/unit_sphere.obj b/examples/core/resources/models/unit_sphere.obj new file mode 100644 index 000000000000..9e96c5083622 --- /dev/null +++ b/examples/core/resources/models/unit_sphere.obj @@ -0,0 +1,173 @@ +o sphere_unit_minimal +o sphere_unit_box_scaled +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 From 892ec64fd445393de92b1780bd7ec54c14d371e7 Mon Sep 17 00:00:00 2001 From: iann Date: Sun, 2 Nov 2025 06:53:53 +0900 Subject: [PATCH 3/6] CI build error recheck --- examples/core/core_3d_fixed_function_didactic.c | 3 +-- examples/core/resources/models/unit_sphere.obj | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/core/core_3d_fixed_function_didactic.c b/examples/core/core_3d_fixed_function_didactic.c index c25edd8c6151..25858636b390 100644 --- a/examples/core/core_3d_fixed_function_didactic.c +++ b/examples/core/core_3d_fixed_function_didactic.c @@ -31,7 +31,6 @@ #include "raylib.h" #include "raymath.h" #include "rlgl.h" -#include #include #include #include @@ -613,7 +612,7 @@ static void OrbitSpace(Camera3D *jugemu, float 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, -M_PI_2 + 0.1f, M_PI_2 - 0.1f); + 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); diff --git a/examples/core/resources/models/unit_sphere.obj b/examples/core/resources/models/unit_sphere.obj index 9e96c5083622..cf84ba070a82 100644 --- a/examples/core/resources/models/unit_sphere.obj +++ b/examples/core/resources/models/unit_sphere.obj @@ -1,5 +1,4 @@ -o sphere_unit_minimal -o sphere_unit_box_scaled +o unit_sphere v 0.0000 0.5000 0.0000 v 0.1913 0.4619 0.0000 v 0.1353 0.4619 0.1353 From 3617ebd97081e2ed14d94e021e438be7bd048277 Mon Sep 17 00:00:00 2001 From: iann Date: Tue, 4 Nov 2025 05:36:19 +0900 Subject: [PATCH 4/6] one last commit before moving to models.c PR --- .../core/core_3d_fixed_function_didactic.c | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/core/core_3d_fixed_function_didactic.c b/examples/core/core_3d_fixed_function_didactic.c index 25858636b390..1aac37ca6ad4 100644 --- a/examples/core/core_3d_fixed_function_didactic.c +++ b/examples/core/core_3d_fixed_function_didactic.c @@ -177,7 +177,7 @@ int main(void) } if (!worldMesh->texcoords) { - worldMesh->texcoords = (float *)MemAlloc(sizeof(float)*worldMesh->vertexCount*2); + worldMesh->texcoords = (float *)RL_CALLOC(worldMesh->vertexCount, sizeof(Vector2)); // Demonstrate planar mapping ("reasonable" default): https://en.wikipedia.org/wiki/Planar_projection. BoundingBox bounds = GetMeshBoundingBox(*worldMesh); Vector3 extents = Vector3Subtract(bounds.max, bounds.min); @@ -200,12 +200,13 @@ int main(void) ndcMesh.vertexCount = worldMesh->vertexCount; ndcMesh.triangleCount = worldMesh->triangleCount; ndcMesh.vertices = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector3)); - ndcMesh.texcoords = RL_CALLOC(ndcMesh.vertexCount, sizeof(Vector2)); ndcMesh.indices = RL_CALLOC(ndcMesh.triangleCount, sizeof(Triangle)); - ndcMesh.colors = RL_CALLOC(ndcMesh.vertexCount, sizeof(Color)); - memcpy(ndcMesh.colors, worldMesh->colors, ndcMesh.vertexCount*sizeof(Color)); - memcpy(ndcMesh.texcoords, worldMesh->texcoords, ndcMesh.vertexCount*sizeof(Vector2)); 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)); ndcModels[i] = LoadModelFromMesh(ndcMesh); ndcModels[i].materials[0].maps[MATERIAL_MAP_ALBEDO].texture = meshTextures[i]; @@ -508,7 +509,7 @@ static void PerspectiveIncorrectCapture(Camera3D *main, float aspect, float near 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()) + if (TEXTURE_MODE() && mesh->texcoords) rlEnableTexture(meshTexture.id); else rlDisableTexture(); @@ -535,20 +536,20 @@ static void PerspectiveIncorrectCapture(Camera3D *main, float aspect, float near 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()) rlColor4ub(colors[triangles[i][0]].r, colors[triangles[i][0]].g, colors[triangles[i][0]].b, colors[triangles[i][0]].a); - if (TEXTURE_MODE()) rlTexCoord2f(texcoords[triangles[i][0]].x, texcoords[triangles[i][0]].y); + if (COLOR_MODE() && mesh->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() && mesh->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()) rlColor4ub(colors[secondIndex].r, colors[secondIndex].g, colors[secondIndex].b, colors[secondIndex].a); - if (TEXTURE_MODE()) rlTexCoord2f(texcoords[secondIndex].x, texcoords[secondIndex].y); + if (COLOR_MODE() && mesh->colors) rlColor4ub(colors[secondIndex].r, colors[secondIndex].g, colors[secondIndex].b, colors[secondIndex].a); + if (TEXTURE_MODE() && mesh->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()) rlColor4ub(colors[thirdIndex].r, colors[thirdIndex].g, colors[thirdIndex].b, colors[thirdIndex].a); - if (TEXTURE_MODE()) rlTexCoord2f(texcoords[thirdIndex].x, texcoords[thirdIndex].y); + if (COLOR_MODE() && mesh->colors) rlColor4ub(colors[thirdIndex].r, colors[thirdIndex].g, colors[thirdIndex].b, colors[thirdIndex].a); + if (TEXTURE_MODE() && mesh->texcoords) rlTexCoord2f(texcoords[thirdIndex].x, texcoords[thirdIndex].y); rlVertex3f(thirdVertex.x, thirdVertex.y, thirdVertex.z); } From 8bc6606ebe18b19a32fb00955df355937bd43045 Mon Sep 17 00:00:00 2001 From: iann Date: Sat, 8 Nov 2025 12:20:35 +0900 Subject: [PATCH 5/6] begin splitting up the example into a single example 200 LOC target --- examples/Makefile | 2 + examples/core/core_3d_camera_view_opengl11.c | 382 ++++++++++++++++ .../core/core_3d_camera_view_opengl11.png | Bin 0 -> 60248 bytes examples/core/core_3d_camera_view_opengl33.c | 407 ++++++++++++++++++ .../core/core_3d_camera_view_opengl33.png | Bin 0 -> 103636 bytes .../core/core_3d_fixed_function_didactic.c | 242 ++++++++--- examples/core/resources/models/unit_cube.obj | 29 +- 7 files changed, 999 insertions(+), 63 deletions(-) create mode 100644 examples/core/core_3d_camera_view_opengl11.c create mode 100644 examples/core/core_3d_camera_view_opengl11.png create mode 100644 examples/core/core_3d_camera_view_opengl33.c create mode 100644 examples/core/core_3d_camera_view_opengl33.png diff --git a/examples/Makefile b/examples/Makefile index 239c6974ccd1..06be40e5e879 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -522,6 +522,8 @@ CORE = \ 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_opengl33 \ core/core_automation_events \ core/core_basic_screen_manager \ core/core_basic_window \ 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 0000000000000000000000000000000000000000..d45126cb7cf6a0bcfc81ea78957ef64c7856f05b GIT binary patch literal 60248 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 aeLO{BpcYw7qwJR)5vzUpGUFKZQNG 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 literal 0 HcmV?d00001 diff --git a/examples/core/core_3d_camera_view_opengl33.c b/examples/core/core_3d_camera_view_opengl33.c new file mode 100644 index 000000000000..7ab5e20db879 --- /dev/null +++ b/examples/core/core_3d_camera_view_opengl33.c @@ -0,0 +1,407 @@ +/******************************************************************************************* +* +* raylib [core] example - Camera View +* Example complexity rating: [★★★★] 4/4 +* +* Example originally created with raylib 6.0? (target) +* +* Example contributed by IANN (@meisei4) and reviewed by Ramon Santamaria (@raysan5) and community +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2025-2025 @meisei4 +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" +#include +#include +#include + +static const char *vert = + "#version 330\n" + "in vec3 vertexPosition;\n" + "in vec2 vertexTexCoord;\n" + "in vec3 vertexNormal;\n" + "in vec4 vertexColor;\n" + "uniform mat4 mvp;\n" + "out vec2 fragTexCoord;\n" + "out vec4 fragColor;\n" + "uniform int useVertexColors;\n" + "void main()\n" + "{\n" + " if (useVertexColors == 1) {\n" + " fragColor = vertexColor;\n" + " } else {\n" + " fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" + " }\n" + " fragTexCoord = vertexTexCoord;\n" + " gl_Position = mvp * vec4(vertexPosition, 1.0);\n" + "}\n"; + +static const char *frag = + "#version 330\n" + "in vec2 fragTexCoord;\n" + "in vec4 fragColor;\n" + "uniform sampler2D texture0;\n" + "uniform vec4 colDiffuse;\n" + "out vec4 finalColor;\n" + "void main()\n" + "{\n" + " vec4 texelColor = texture(texture0, fragTexCoord);\n" + " vec4 outColor = texelColor*fragColor*colDiffuse;\n" + " if (outColor.a <= 0.0) discard; \n" + " finalColor = outColor;\n" + "}\n"; + +static Shader customShader = { 0 }; +static int useVertexColorsLoc = -1; + +#define BAHAMA_BLUE CLITERAL(Color){ 0, 102, 153, 255 } +#define SUNFLOWER CLITERAL(Color){ 255, 204, 153, 255 } +#define ANAKIWA CLITERAL(Color){ 153, 204, 255, 255 } +#define MARINER CLITERAL(Color){ 51, 102, 204, 255 } +#define NEON_CARROT CLITERAL(Color){ 255, 153, 51, 255 } +#define EGGPLANT CLITERAL(Color){ 102, 68, 102, 255 } +#define HOPBUSH CLITERAL(Color){ 204, 102, 153, 255 } +#define LILAC CLITERAL(Color){ 204, 153, 204, 255 } +#define RED_DAMASK CLITERAL(Color){ 221, 102, 68, 255 } +#define CHESTNUT_ROSE CLITERAL(Color){ 204, 102, 102, 255 } + +typedef unsigned short Triangle[3]; + +enum Flags +{ + FLAG_ASPECT = 1u<<0, + FLAG_PAUSE = 1u<<1, + FLAG_JUGEMU = 1u<<2, + FLAG_ORTHO = 1u<<3, + GEN_CUBE = 1u<<4, + GEN_SPHERE = 1u<<5, + GEN_KNOT = 1u<<6 +}; + +static unsigned int gflags = FLAG_ASPECT | FLAG_JUGEMU | GEN_CUBE; + +#define ASPECT_CORRECT() ((gflags & FLAG_ASPECT) != 0) +#define PAUSED() ((gflags & FLAG_PAUSE) != 0) +#define JUGEMU_MODE() ((gflags & FLAG_JUGEMU) != 0) +#define ORTHO_MODE() ((gflags & FLAG_ORTHO) != 0) +#define TOGGLE(K, F) do { if (IsKeyPressed(K)) { gflags ^= (F); } } while (0) + +static unsigned int targetMesh = 0; +#define NUM_MODELS 3 +#define CYCLE_MESH(K, I, F) do { if (IsKeyPressed(K)) { targetMesh = (I); gflags = (gflags & ~(GEN_CUBE|GEN_SPHERE|GEN_KNOT)) | (F); } } while (0) + +static int fontSize = 20; +static float angularVelocity = 1.25f; +static float fovyPerspective = 60.0f; +static float nearPlaneHeightOrthographic = 1.0f; +static float blendScalar = 5.0f; +static Vector3 yAxis = { 0.0f, 1.0f, 0.0f }; +static Vector3 modelPos = { 0.0f, 0.0f, 0.0f }; +static Vector3 modelScale = { 1.0f, 1.0f, 1.0f }; +static Vector3 mainPos = { 0.0f, 0.0f, 2.0f }; +static Vector3 jugemuPosIso = { 3.0f, 1.0f, 3.0f }; + +static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame); +static void DrawSpatialFrame(Mesh *spatialFrame); +static void PerspectiveCorrectCapture(Camera3D *main, Model *model, RenderTexture2D *perspectiveCorrectRenderTexture, float rotation); +static void FillVertexColors(Mesh *mesh); +static void OrbitSpace(Camera3D *jugemu, float dt); +static Vector3 TranslateRotateScale(Vector3 coordinate, Vector3 pos, Vector3 scale, float rotation); +static Vector3 Intersect(Camera3D *main, float near, Vector3 worldCoord); +static float OrthoBlendFactor(float dt); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [core] example - fixed function didactic"); + customShader = LoadShaderFromMemory(vert, frag); + useVertexColorsLoc = GetShaderLocation(customShader, "useVertexColors"); + RenderTexture2D perspectiveCorrectRenderTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); + float near = 1.0f; + float far = 3.0f; + float aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); + nearPlaneHeightOrthographic = 2.0f*near*tanf(DEG2RAD*fovyPerspective*0.5f); + float meshRotation = 0.0f; + + Camera3D main = { 0 }; + main.position = mainPos; + main.target = modelPos; + main.up = yAxis; + main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; + main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic: fovyPerspective; + + Camera3D jugemu = (Camera3D){ 0 }; + jugemu.position = jugemuPosIso; + jugemu.target = modelPos; + jugemu.up = yAxis; + jugemu.fovy = fovyPerspective; + jugemu.projection = CAMERA_PERSPECTIVE; + + Model models[NUM_MODELS] = { 0 }; + models[0] = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f)); + models[1] = LoadModelFromMesh(GenMeshSphere(0.5f, 8, 8)); + models[2] = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 16, 128)); + for (int i = 0; i < NUM_MODELS; i++) + { + Mesh *mesh = &models[i].meshes[0]; + mesh->vaoId = 0; + if (!mesh->indices) + { + mesh->indices = RL_CALLOC(mesh->vertexCount, sizeof(unsigned short)); + for (int j = 0; j < mesh->vertexCount; j++) mesh->indices[j] = (unsigned short)j; + mesh->triangleCount = mesh->vertexCount/3; + } + FillVertexColors(mesh); + UploadMesh(mesh, true); + } + + Mesh tempCube = GenMeshCube(1.0f, 1.0f, 1.0f); + Mesh spatialFrame = { 0 }; + spatialFrame.vertexCount = tempCube.vertexCount; + spatialFrame.triangleCount = tempCube.triangleCount; + spatialFrame.vertices = RL_MALLOC(spatialFrame.vertexCount * 3 * sizeof(float)); + spatialFrame.normals = RL_MALLOC(spatialFrame.vertexCount * 3 * sizeof(float)); + spatialFrame.texcoords = RL_MALLOC(spatialFrame.vertexCount * 2 * sizeof(float)); + spatialFrame.indices = RL_MALLOC(spatialFrame.triangleCount * 3 * sizeof(unsigned short)); + spatialFrame.colors = RL_CALLOC(spatialFrame.vertexCount, sizeof(Color)); + memcpy(spatialFrame.vertices, tempCube.vertices, spatialFrame.vertexCount * 3 * sizeof(float)); + memcpy(spatialFrame.normals, tempCube.normals, spatialFrame.vertexCount * 3 * sizeof(float)); + memcpy(spatialFrame.texcoords, tempCube.texcoords, spatialFrame.vertexCount * 2 * sizeof(float)); + memcpy(spatialFrame.indices, tempCube.indices, spatialFrame.triangleCount * 3 * sizeof(unsigned short)); + for (int i = 0; i < spatialFrame.vertexCount; i++) ((Color *)spatialFrame.colors)[i] = (Color){ 255, 255, 255, 0 }; + for (int i = 0; i < 4; i++) ((Color *)spatialFrame.colors)[i].a = 255; + UnloadMesh(tempCube); + UploadMesh(&spatialFrame, true); + Model spatialFrameModel = LoadModelFromMesh(spatialFrame); + spatialFrameModel.materials[0].shader = customShader; + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + while (!WindowShouldClose()) + { + // Update + //---------------------------------------------------------------------------------- + aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); + TOGGLE(KEY_Q, FLAG_ASPECT); + TOGGLE(KEY_SPACE, FLAG_PAUSE); + TOGGLE(KEY_J, FLAG_JUGEMU); + TOGGLE(KEY_O, FLAG_ORTHO); + CYCLE_MESH(KEY_ONE, 0, GEN_CUBE); + CYCLE_MESH(KEY_TWO, 1, GEN_SPHERE); + CYCLE_MESH(KEY_THREE, 2, GEN_KNOT); + + OrthoBlendFactor(GetFrameTime()); + + if (!PAUSED()) meshRotation -= angularVelocity*GetFrameTime(); + + OrbitSpace(&jugemu, GetFrameTime()); + main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; + main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic : fovyPerspective; + + Model *displayModel = &models[targetMesh]; + + PerspectiveCorrectCapture(&main, displayModel, &perspectiveCorrectRenderTexture, meshRotation); + + UpdateSpatialFrame(&main, aspect, near, far, &spatialFrame); + UpdateMeshBuffer(spatialFrameModel.meshes[0], RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, spatialFrame.vertices, spatialFrame.vertexCount*sizeof(Vector3), 0); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + if (JUGEMU_MODE()) BeginMode3D(jugemu); else BeginMode3D(main); + Vector3 depth = Vector3Normalize(Vector3Subtract(main.target, main.position)); + Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main.up)); + Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); + DrawLine3D(main.position, Vector3Add(main.position, right), NEON_CARROT); + DrawLine3D(main.position, Vector3Add(main.position, up), LILAC); + DrawLine3D(main.position, Vector3Add(main.position, depth), MARINER); + + if (JUGEMU_MODE()) DrawSpatialFrame(&spatialFrame); + + DrawModelEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, WHITE); + + unsigned int cacheID = displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id; + displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = rlGetTextureIdDefault(); + DrawModelWiresEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, MARINER); + rlSetPointSize(4.0f); + DrawModelPointsEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, LILAC); + displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = cacheID; + + if (JUGEMU_MODE()) + { + Vector3 *vertices = (Vector3 *)displayModel->meshes[0].vertices; + Triangle *triangles = (Triangle *)displayModel->meshes[0].indices; + for (int i = 0; i < displayModel->meshes[0].triangleCount; i++) + { + Vector3 a = TranslateRotateScale(vertices[triangles[i][0]], modelPos, modelScale, meshRotation); + Vector3 b = TranslateRotateScale(vertices[triangles[i][1]], modelPos, modelScale, meshRotation); + Vector3 c = TranslateRotateScale(vertices[triangles[i][2]], modelPos, modelScale, meshRotation); + if (Vector3DotProduct(Vector3Normalize(Vector3CrossProduct(Vector3Subtract(b, a), Vector3Subtract(c, a))), depth) > 0.0f) continue; + DrawLine3D(a, Intersect(&main, near, a), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); + DrawLine3D(b, Intersect(&main, near, b), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); + DrawLine3D(c, Intersect(&main, near, c), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); + } + } + spatialFrameModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = perspectiveCorrectRenderTexture.texture; + //NOTE: there is still the alpha threshold issue without custom frag shader... + int useColors = 1; + SetShaderValue(spatialFrameModel.materials[0].shader, useVertexColorsLoc, &useColors, SHADER_UNIFORM_INT); + if (JUGEMU_MODE()) DrawModel(spatialFrameModel, modelPos, 1.0f, WHITE); + + EndMode3D(); + + DrawText("[1]: CUBE [2]: SPHERE [3]: KNOT", 12, 12, fontSize, NEON_CARROT); + DrawText("ARROWS: MOVE | SPACEBAR: PAUSE", 12, 38, fontSize, NEON_CARROT); + DrawText("W S : ZOOM ", 12, 64, fontSize, NEON_CARROT); + DrawText((targetMesh == 0)? "GEN_CUBE" : (targetMesh == 1)? "GEN_SPHERE" : "GEN_KNOT", 12, 205, fontSize, NEON_CARROT); + DrawText("ASPECT [ Q ]:", 12, 392, fontSize, SUNFLOWER); + DrawText((ASPECT_CORRECT())? "CORRECT" : "INCORRECT", 230, 392, fontSize, (ASPECT_CORRECT())? ANAKIWA : CHESTNUT_ROSE); + DrawText("LENS [ O ]:", 510, 366, fontSize, SUNFLOWER); + DrawText((ORTHO_MODE())? "ORTHOGRAPHIC" : "PERSPECTIVE", 630, 366, fontSize, (ORTHO_MODE())? BAHAMA_BLUE : ANAKIWA); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadModel(models[0]); + UnloadModel(models[1]); + UnloadModel(models[2]); + UnloadModel(spatialFrameModel); + if (perspectiveCorrectRenderTexture.id) UnloadRenderTexture(perspectiveCorrectRenderTexture); + UnloadShader(customShader); + CloseWindow(); + //-------------------------------------------------------------------------------------- + + return 0; +} + +static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame) +{ + Vector3 depth = Vector3Normalize(Vector3Subtract(main->target, main->position)); + Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main->up)); + Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); + float halfHNear = Lerp(near*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); + float halfWNear = halfHNear*aspect; + float halfHFar = Lerp(far*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); + float halfWFar = halfHFar*aspect; + float halfDepth = 0.5f*(far - near); + Vector3 centerNear = Vector3Add(main->position, Vector3Scale(depth, near)); + + for (int i = 0; i < spatialFrame->vertexCount; ++i) + { + Vector3 offset = Vector3Subtract(((Vector3 *)spatialFrame->vertices)[i], centerNear); + float xSign = (Vector3DotProduct(offset, right) >= 0.0f)? 1.0f : -1.0f; + float ySign = (Vector3DotProduct(offset, up) >= 0.0f)? 1.0f : -1.0f; + float farMask = (Vector3DotProduct(offset, depth) > halfDepth)? 1.0f : 0.0f; + float finalHalfW = halfWNear + farMask*(halfWFar - halfWNear); + float finalHalfH = halfHNear + farMask*(halfHFar - halfHNear); + Vector3 center = Vector3Add(centerNear, Vector3Scale(depth, farMask*2.0f*halfDepth)); + ((Vector3 *)spatialFrame->vertices)[i] = Vector3Add(center, Vector3Add(Vector3Scale(right, xSign*finalHalfW), Vector3Scale(up, ySign*finalHalfH))); + } +} + +static void DrawSpatialFrame(Mesh *spatialFrame) +{ + static int frontFaces[4][2] = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 } }; + static int backFaces[4][2] = { { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 } }; + static int ribFaces[4][2] = { { 0, 4 }, { 1, 7 }, { 2, 6 }, { 3, 5 } }; + static int (*faces[3])[2] = { frontFaces, backFaces, ribFaces }; + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 4; j++) + { + Vector3 startPosition = ((Vector3 *)spatialFrame->vertices)[faces[i][j][0]]; + Vector3 endPosition = ((Vector3 *)spatialFrame->vertices)[faces[i][j][1]]; + DrawLine3D(startPosition, endPosition, (i == 0)? NEON_CARROT : (i == 1)? EGGPLANT : HOPBUSH); + } +} + +static void PerspectiveCorrectCapture(Camera3D *main, Model *model, RenderTexture2D *perspectiveCorrectRenderTexture, float rotation) +{ + BeginTextureMode(*perspectiveCorrectRenderTexture); + ClearBackground(BLANK); + BeginMode3D(*main); + DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); + DrawModelWiresEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, MARINER); + EndMode3D(); + EndTextureMode(); +} + +static void OrbitSpace(Camera3D *jugemu, float dt) +{ + float radius = Vector3Length(jugemu->position); + float azimuth = atan2f(jugemu->position.z, jugemu->position.x); + float horizontalRadius = sqrtf(jugemu->position.x*jugemu->position.x + jugemu->position.z*jugemu->position.z); + float elevation = atan2f(jugemu->position.y, horizontalRadius); + if (IsKeyDown(KEY_LEFT)) azimuth += 1.0f*dt; + if (IsKeyDown(KEY_RIGHT)) azimuth -= 1.0f*dt; + if (IsKeyDown(KEY_UP)) elevation += 1.0f*dt; + if (IsKeyDown(KEY_DOWN)) elevation -= 1.0f*dt; + if (IsKeyDown(KEY_W)) radius -= 1.0f*dt; + if (IsKeyDown(KEY_S)) radius += 1.0f*dt; + elevation = Clamp(elevation, -PI/2 + 0.1f, PI/2 - 0.1f); + jugemu->position.x = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*cosf(azimuth); + jugemu->position.y = Clamp(radius, 0.25f, 10.0f)*sinf(elevation); + jugemu->position.z = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*sinf(azimuth); +} + +static void FillVertexColors(Mesh *mesh) +{ + if (!mesh->colors) mesh->colors = RL_CALLOC(mesh->vertexCount, sizeof(Color)); + Color *colors = (Color *)mesh->colors; + Vector3 *vertices = (Vector3 *)mesh->vertices; + BoundingBox bounds = GetMeshBoundingBox(*mesh); + + for (int i = 0; i < mesh->vertexCount; ++i) + { + Vector3 vertex = vertices[i]; + float nx = (vertex.x - 0.5f*(bounds.min.x + bounds.max.x))/(0.5f*(bounds.max.x - bounds.min.x)); + float ny = (vertex.y - 0.5f*(bounds.min.y + bounds.max.y))/(0.5f*(bounds.max.y - bounds.min.y)); + float nz = (vertex.z - 0.5f*(bounds.min.z + bounds.max.z))/(0.5f*(bounds.max.z - bounds.min.z)); + float len = sqrtf(nx*nx + ny*ny + nz*nz); + colors[i] = (Color){ lrintf(127.5f*(nx/len + 1.0f)), lrintf(127.5f*(ny/len + 1.0f)), lrintf(127.5f*(nz/len + 1.0f)), 255 }; + } +} + +static Vector3 TranslateRotateScale(Vector3 coordinate, Vector3 pos, Vector3 scale, float rotation) +{ + return Vector3Transform(coordinate, MatrixMultiply(MatrixMultiply(MatrixScale(scale.x, scale.y, scale.z), MatrixRotateY(rotation)), MatrixTranslate(pos.x, pos.y, pos.z))); +} + +static Vector3 Intersect(Camera3D *main, float near, Vector3 worldCoord) +{ + Vector3 viewDir = Vector3Normalize(Vector3Subtract(main->target, main->position)); + Vector3 mainCameraToPoint = Vector3Subtract(worldCoord, main->position); + float depthAlongView = Vector3DotProduct(mainCameraToPoint, viewDir); + Vector3 centerNearPlane = Vector3Add(main->position, Vector3Scale(viewDir, near)); + if (depthAlongView <= 0.0f) return centerNearPlane; + float scaleToNear = near/depthAlongView; + Vector3 resultPerspective = Vector3Add(main->position, Vector3Scale(mainCameraToPoint, scaleToNear)); + Vector3 resultOrtho = Vector3Add(worldCoord, Vector3Scale(viewDir, Vector3DotProduct(Vector3Subtract(centerNearPlane, worldCoord), viewDir))); + return Vector3Lerp(resultPerspective, resultOrtho, OrthoBlendFactor(0.0f)); +} + +static float OrthoBlendFactor(float dt) +{ + static float blend = 0.0f; + if (dt > 0.0f) blend = Clamp(blend + ((ORTHO_MODE())? 1.0f : -1.0f)*blendScalar*dt, 0.0f, 1.0f); + return blend; +} \ No newline at end of file diff --git a/examples/core/core_3d_camera_view_opengl33.png b/examples/core/core_3d_camera_view_opengl33.png new file mode 100644 index 0000000000000000000000000000000000000000..cdac66493e0fcfce95189973e7dafa8a0de39076 GIT binary patch literal 103636 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 #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 } @@ -103,15 +146,19 @@ static void WorldToNDCSpace(Camera3D *main, float aspect, float near, float far, 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, Mesh *mesh, 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, Mesh *mesh, Texture2D meshTexture, float rotation); +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); @@ -133,6 +180,14 @@ int main(void) 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(); @@ -168,27 +223,13 @@ int main(void) 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; } - if (!worldMesh->texcoords) - { - worldMesh->texcoords = (float *)RL_CALLOC(worldMesh->vertexCount, sizeof(Vector2)); - // Demonstrate planar mapping ("reasonable" default): https://en.wikipedia.org/wiki/Planar_projection. - BoundingBox bounds = GetMeshBoundingBox(*worldMesh); - Vector3 extents = Vector3Subtract(bounds.max, bounds.min); - for (int j = 0; j < worldMesh->vertexCount; j++) - { - float x = ((Vector3 *)worldMesh->vertices)[j].x; - float y = ((Vector3 *)worldMesh->vertices)[j].y; - ((Vector2 *)worldMesh->texcoords)[j].x = (x - bounds.min.x)/extents.x; - ((Vector2 *)worldMesh->texcoords)[j].y = (y - bounds.min.y)/extents.y; - } - } + FillPlanarTexCoords(worldMesh); FillVertexColors(worldMesh); Image textureImage = GenImageChecked(textureConfig[i], textureConfig[i], 1, 1, BLACK, WHITE); @@ -207,22 +248,42 @@ int main(void) 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); } - Texture2D perspectiveCorrectTexture = (Texture2D){ 0 }; - Mesh spatialFrame = GenMeshCube(1.0f, 1.0f, 1.0f); + 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); //-------------------------------------------------------------------------------------- @@ -268,15 +329,20 @@ int main(void) 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]; - Mesh *displayMesh = &ndcModels[targetMesh].meshes[0]; 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 @@ -297,16 +363,29 @@ int main(void) DrawModelFilled(displayModel, meshTextures[targetMesh], meshRotation); DrawModelWiresAndPoints(displayModel, meshRotation); - if (JUGEMU_MODE()) DrawNearPlanePoints(&main, aspect, near, &nearPlanePointsModels[targetMesh], displayMesh, 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, displayMesh, meshTextures[targetMesh], meshRotation); + if (JUGEMU_MODE()) PerspectiveIncorrectCapture(&main, aspect, near, displayModel, meshTextures[targetMesh], meshRotation); } EndMode3D(); @@ -348,7 +427,10 @@ int main(void) 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 //-------------------------------------------------------------------------------------- @@ -396,27 +478,42 @@ static void WorldToNDCSpace(Camera3D *main, float aspect, float near, float far, 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; - model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = (TEXTURE_MODE())? texture.id : 0; +#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 = 0; + 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; - unsigned int cacheID = model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id; if (!CLIP_MODE()) model->meshes[0].colors = NULL; - model->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = 0; +#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) @@ -462,21 +559,22 @@ static void DrawSpatialFrame(Mesh *spatialFrame) } } -static void DrawNearPlanePoints(Camera3D *main, float aspect, float near, Model *nearPlanePointsModel, Mesh *mesh, float rotation) +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 = mesh->triangleCount*3; + 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 < mesh->triangleCount; i++) + for (int i = 0; i < displayMesh->triangleCount; i++) { - Vector3 *vertices = (Vector3 *)mesh->vertices; - Triangle *triangles = (Triangle *)mesh->indices; + 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); @@ -496,24 +594,29 @@ static void DrawNearPlanePoints(Camera3D *main, float aspect, float near, Model } 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, Mesh *mesh, Texture2D meshTexture, float rotation) +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() && mesh->texcoords) + if (TEXTURE_MODE() && displayMesh->texcoords) + { + rlSetTexture(meshTexture.id); rlEnableTexture(meshTexture.id); + } else + { rlDisableTexture(); - + } if (!TEXTURE_MODE() && !COLOR_MODE()) { rlEnableWireMode(); @@ -521,12 +624,12 @@ static void PerspectiveIncorrectCapture(Camera3D *main, float aspect, float near } rlBegin(RL_TRIANGLES); - for (int i = 0; i < mesh->triangleCount; i++) + for (int i = 0; i < displayMesh->triangleCount; i++) { - Triangle *triangles = (Triangle *)mesh->indices; - Vector3 *vertices = (Vector3 *)mesh->vertices; - Color *colors = (Color *)mesh->colors; - Vector2 *texcoords = (Vector2 *)mesh->texcoords; + 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); @@ -536,28 +639,46 @@ static void PerspectiveIncorrectCapture(Camera3D *main, float aspect, float near 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() && mesh->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() && mesh->texcoords) rlTexCoord2f(texcoords[triangles[i][0]].x, texcoords[triangles[i][0]].y); + 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() && mesh->colors) rlColor4ub(colors[secondIndex].r, colors[secondIndex].g, colors[secondIndex].b, colors[secondIndex].a); - if (TEXTURE_MODE() && mesh->texcoords) rlTexCoord2f(texcoords[secondIndex].x, texcoords[secondIndex].y); + 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() && mesh->colors) rlColor4ub(colors[thirdIndex].r, colors[thirdIndex].g, colors[thirdIndex].b, colors[thirdIndex].a); - if (TEXTURE_MODE() && mesh->texcoords) rlTexCoord2f(texcoords[thirdIndex].x, texcoords[thirdIndex].y); + 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; @@ -575,7 +696,6 @@ static void PerspectiveCorrectCapture(Camera3D *main, Model *model, Texture2D me Image rgba = LoadImageFromScreen(); ImageFormat(&rgba, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); model->meshes[0].colors = cacheColors; - ClearBackground(BLACK); BeginMode3D(*main); @@ -596,10 +716,10 @@ static void PerspectiveCorrectCapture(Camera3D *main, Model *model, Texture2D me UpdateTexture(*perspectiveCorrectTexture, rgba.data); else *perspectiveCorrectTexture = LoadTextureFromImage(rgba); - UnloadImage(mask); UnloadImage(rgba); } +#endif static void OrbitSpace(Camera3D *jugemu, float dt) { @@ -631,6 +751,26 @@ static void AlphaMaskPunchOut(Image *rgba, Image *mask, unsigned char threshold) 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)); diff --git a/examples/core/resources/models/unit_cube.obj b/examples/core/resources/models/unit_cube.obj index 49121401d2bc..215fed62eae9 100644 --- a/examples/core/resources/models/unit_cube.obj +++ b/examples/core/resources/models/unit_cube.obj @@ -7,16 +7,21 @@ 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 4 3 -f 1 3 2 -f 5 6 7 -f 5 7 8 -f 1 5 8 -f 1 8 4 -f 2 3 7 -f 2 7 6 -f 4 8 7 -f 4 7 3 -f 1 2 6 -f 1 6 5 +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 From 41c9975118c7e1065ff9311f62904bc557898188 Mon Sep 17 00:00:00 2001 From: iann Date: Tue, 11 Nov 2025 03:48:20 +0900 Subject: [PATCH 6/6] reduce LOC --- examples/Makefile | 2 +- examples/core/core_3d_camera_view.c | 265 ++++++ examples/core/core_3d_camera_view_opengl33.c | 407 --------- .../core/core_3d_camera_view_opengl33.png | Bin 103636 -> 0 bytes examples/core/core_3d_that_one_example.c | 852 ++++++++++++++++++ 5 files changed, 1118 insertions(+), 408 deletions(-) create mode 100644 examples/core/core_3d_camera_view.c delete mode 100644 examples/core/core_3d_camera_view_opengl33.c delete mode 100644 examples/core/core_3d_camera_view_opengl33.png create mode 100644 examples/core/core_3d_that_one_example.c diff --git a/examples/Makefile b/examples/Makefile index 06be40e5e879..b41de1944d01 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -523,7 +523,7 @@ CORE = \ core/core_3d_picking \ core/core_3d_fixed_function_didactic \ core/core_3d_camera_view_opengl11 \ - core/core_3d_camera_view_opengl33 \ + core/core_3d_camera_view \ core/core_automation_events \ core/core_basic_screen_manager \ core/core_basic_window \ diff --git a/examples/core/core_3d_camera_view.c b/examples/core/core_3d_camera_view.c new file mode 100644 index 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_opengl33.c b/examples/core/core_3d_camera_view_opengl33.c deleted file mode 100644 index 7ab5e20db879..000000000000 --- a/examples/core/core_3d_camera_view_opengl33.c +++ /dev/null @@ -1,407 +0,0 @@ -/******************************************************************************************* -* -* raylib [core] example - Camera View -* Example complexity rating: [★★★★] 4/4 -* -* Example originally created with raylib 6.0? (target) -* -* Example contributed by IANN (@meisei4) and reviewed by Ramon Santamaria (@raysan5) and community -* -* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, -* BSD-like license that allows static linking with closed source software -* -* Copyright (c) 2025-2025 @meisei4 -* -********************************************************************************************/ - -#include "raylib.h" -#include "raymath.h" -#include "rlgl.h" -#include -#include -#include - -static const char *vert = - "#version 330\n" - "in vec3 vertexPosition;\n" - "in vec2 vertexTexCoord;\n" - "in vec3 vertexNormal;\n" - "in vec4 vertexColor;\n" - "uniform mat4 mvp;\n" - "out vec2 fragTexCoord;\n" - "out vec4 fragColor;\n" - "uniform int useVertexColors;\n" - "void main()\n" - "{\n" - " if (useVertexColors == 1) {\n" - " fragColor = vertexColor;\n" - " } else {\n" - " fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" - " }\n" - " fragTexCoord = vertexTexCoord;\n" - " gl_Position = mvp * vec4(vertexPosition, 1.0);\n" - "}\n"; - -static const char *frag = - "#version 330\n" - "in vec2 fragTexCoord;\n" - "in vec4 fragColor;\n" - "uniform sampler2D texture0;\n" - "uniform vec4 colDiffuse;\n" - "out vec4 finalColor;\n" - "void main()\n" - "{\n" - " vec4 texelColor = texture(texture0, fragTexCoord);\n" - " vec4 outColor = texelColor*fragColor*colDiffuse;\n" - " if (outColor.a <= 0.0) discard; \n" - " finalColor = outColor;\n" - "}\n"; - -static Shader customShader = { 0 }; -static int useVertexColorsLoc = -1; - -#define BAHAMA_BLUE CLITERAL(Color){ 0, 102, 153, 255 } -#define SUNFLOWER CLITERAL(Color){ 255, 204, 153, 255 } -#define ANAKIWA CLITERAL(Color){ 153, 204, 255, 255 } -#define MARINER CLITERAL(Color){ 51, 102, 204, 255 } -#define NEON_CARROT CLITERAL(Color){ 255, 153, 51, 255 } -#define EGGPLANT CLITERAL(Color){ 102, 68, 102, 255 } -#define HOPBUSH CLITERAL(Color){ 204, 102, 153, 255 } -#define LILAC CLITERAL(Color){ 204, 153, 204, 255 } -#define RED_DAMASK CLITERAL(Color){ 221, 102, 68, 255 } -#define CHESTNUT_ROSE CLITERAL(Color){ 204, 102, 102, 255 } - -typedef unsigned short Triangle[3]; - -enum Flags -{ - FLAG_ASPECT = 1u<<0, - FLAG_PAUSE = 1u<<1, - FLAG_JUGEMU = 1u<<2, - FLAG_ORTHO = 1u<<3, - GEN_CUBE = 1u<<4, - GEN_SPHERE = 1u<<5, - GEN_KNOT = 1u<<6 -}; - -static unsigned int gflags = FLAG_ASPECT | FLAG_JUGEMU | GEN_CUBE; - -#define ASPECT_CORRECT() ((gflags & FLAG_ASPECT) != 0) -#define PAUSED() ((gflags & FLAG_PAUSE) != 0) -#define JUGEMU_MODE() ((gflags & FLAG_JUGEMU) != 0) -#define ORTHO_MODE() ((gflags & FLAG_ORTHO) != 0) -#define TOGGLE(K, F) do { if (IsKeyPressed(K)) { gflags ^= (F); } } while (0) - -static unsigned int targetMesh = 0; -#define NUM_MODELS 3 -#define CYCLE_MESH(K, I, F) do { if (IsKeyPressed(K)) { targetMesh = (I); gflags = (gflags & ~(GEN_CUBE|GEN_SPHERE|GEN_KNOT)) | (F); } } while (0) - -static int fontSize = 20; -static float angularVelocity = 1.25f; -static float fovyPerspective = 60.0f; -static float nearPlaneHeightOrthographic = 1.0f; -static float blendScalar = 5.0f; -static Vector3 yAxis = { 0.0f, 1.0f, 0.0f }; -static Vector3 modelPos = { 0.0f, 0.0f, 0.0f }; -static Vector3 modelScale = { 1.0f, 1.0f, 1.0f }; -static Vector3 mainPos = { 0.0f, 0.0f, 2.0f }; -static Vector3 jugemuPosIso = { 3.0f, 1.0f, 3.0f }; - -static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame); -static void DrawSpatialFrame(Mesh *spatialFrame); -static void PerspectiveCorrectCapture(Camera3D *main, Model *model, RenderTexture2D *perspectiveCorrectRenderTexture, float rotation); -static void FillVertexColors(Mesh *mesh); -static void OrbitSpace(Camera3D *jugemu, float dt); -static Vector3 TranslateRotateScale(Vector3 coordinate, Vector3 pos, Vector3 scale, float rotation); -static Vector3 Intersect(Camera3D *main, float near, Vector3 worldCoord); -static float OrthoBlendFactor(float dt); - -//------------------------------------------------------------------------------------ -// Program main entry point -//------------------------------------------------------------------------------------ -int main(void) -{ - // Initialization - //-------------------------------------------------------------------------------------- - const int screenWidth = 800; - const int screenHeight = 450; - - InitWindow(screenWidth, screenHeight, "raylib [core] example - fixed function didactic"); - customShader = LoadShaderFromMemory(vert, frag); - useVertexColorsLoc = GetShaderLocation(customShader, "useVertexColors"); - RenderTexture2D perspectiveCorrectRenderTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); - float near = 1.0f; - float far = 3.0f; - float aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); - nearPlaneHeightOrthographic = 2.0f*near*tanf(DEG2RAD*fovyPerspective*0.5f); - float meshRotation = 0.0f; - - Camera3D main = { 0 }; - main.position = mainPos; - main.target = modelPos; - main.up = yAxis; - main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; - main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic: fovyPerspective; - - Camera3D jugemu = (Camera3D){ 0 }; - jugemu.position = jugemuPosIso; - jugemu.target = modelPos; - jugemu.up = yAxis; - jugemu.fovy = fovyPerspective; - jugemu.projection = CAMERA_PERSPECTIVE; - - Model models[NUM_MODELS] = { 0 }; - models[0] = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f)); - models[1] = LoadModelFromMesh(GenMeshSphere(0.5f, 8, 8)); - models[2] = LoadModelFromMesh(GenMeshKnot(1.0f, 1.0f, 16, 128)); - for (int i = 0; i < NUM_MODELS; i++) - { - Mesh *mesh = &models[i].meshes[0]; - mesh->vaoId = 0; - if (!mesh->indices) - { - mesh->indices = RL_CALLOC(mesh->vertexCount, sizeof(unsigned short)); - for (int j = 0; j < mesh->vertexCount; j++) mesh->indices[j] = (unsigned short)j; - mesh->triangleCount = mesh->vertexCount/3; - } - FillVertexColors(mesh); - UploadMesh(mesh, true); - } - - Mesh tempCube = GenMeshCube(1.0f, 1.0f, 1.0f); - Mesh spatialFrame = { 0 }; - spatialFrame.vertexCount = tempCube.vertexCount; - spatialFrame.triangleCount = tempCube.triangleCount; - spatialFrame.vertices = RL_MALLOC(spatialFrame.vertexCount * 3 * sizeof(float)); - spatialFrame.normals = RL_MALLOC(spatialFrame.vertexCount * 3 * sizeof(float)); - spatialFrame.texcoords = RL_MALLOC(spatialFrame.vertexCount * 2 * sizeof(float)); - spatialFrame.indices = RL_MALLOC(spatialFrame.triangleCount * 3 * sizeof(unsigned short)); - spatialFrame.colors = RL_CALLOC(spatialFrame.vertexCount, sizeof(Color)); - memcpy(spatialFrame.vertices, tempCube.vertices, spatialFrame.vertexCount * 3 * sizeof(float)); - memcpy(spatialFrame.normals, tempCube.normals, spatialFrame.vertexCount * 3 * sizeof(float)); - memcpy(spatialFrame.texcoords, tempCube.texcoords, spatialFrame.vertexCount * 2 * sizeof(float)); - memcpy(spatialFrame.indices, tempCube.indices, spatialFrame.triangleCount * 3 * sizeof(unsigned short)); - for (int i = 0; i < spatialFrame.vertexCount; i++) ((Color *)spatialFrame.colors)[i] = (Color){ 255, 255, 255, 0 }; - for (int i = 0; i < 4; i++) ((Color *)spatialFrame.colors)[i].a = 255; - UnloadMesh(tempCube); - UploadMesh(&spatialFrame, true); - Model spatialFrameModel = LoadModelFromMesh(spatialFrame); - spatialFrameModel.materials[0].shader = customShader; - SetTargetFPS(60); - //-------------------------------------------------------------------------------------- - - while (!WindowShouldClose()) - { - // Update - //---------------------------------------------------------------------------------- - aspect = (float)GetScreenWidth()/(float)GetScreenHeight(); - TOGGLE(KEY_Q, FLAG_ASPECT); - TOGGLE(KEY_SPACE, FLAG_PAUSE); - TOGGLE(KEY_J, FLAG_JUGEMU); - TOGGLE(KEY_O, FLAG_ORTHO); - CYCLE_MESH(KEY_ONE, 0, GEN_CUBE); - CYCLE_MESH(KEY_TWO, 1, GEN_SPHERE); - CYCLE_MESH(KEY_THREE, 2, GEN_KNOT); - - OrthoBlendFactor(GetFrameTime()); - - if (!PAUSED()) meshRotation -= angularVelocity*GetFrameTime(); - - OrbitSpace(&jugemu, GetFrameTime()); - main.projection = (ORTHO_MODE())? CAMERA_ORTHOGRAPHIC : CAMERA_PERSPECTIVE; - main.fovy = (ORTHO_MODE())? nearPlaneHeightOrthographic : fovyPerspective; - - Model *displayModel = &models[targetMesh]; - - PerspectiveCorrectCapture(&main, displayModel, &perspectiveCorrectRenderTexture, meshRotation); - - UpdateSpatialFrame(&main, aspect, near, far, &spatialFrame); - UpdateMeshBuffer(spatialFrameModel.meshes[0], RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, spatialFrame.vertices, spatialFrame.vertexCount*sizeof(Vector3), 0); - //---------------------------------------------------------------------------------- - - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); - - ClearBackground(BLACK); - if (JUGEMU_MODE()) BeginMode3D(jugemu); else BeginMode3D(main); - Vector3 depth = Vector3Normalize(Vector3Subtract(main.target, main.position)); - Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main.up)); - Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); - DrawLine3D(main.position, Vector3Add(main.position, right), NEON_CARROT); - DrawLine3D(main.position, Vector3Add(main.position, up), LILAC); - DrawLine3D(main.position, Vector3Add(main.position, depth), MARINER); - - if (JUGEMU_MODE()) DrawSpatialFrame(&spatialFrame); - - DrawModelEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, WHITE); - - unsigned int cacheID = displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id; - displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = rlGetTextureIdDefault(); - DrawModelWiresEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, MARINER); - rlSetPointSize(4.0f); - DrawModelPointsEx(*displayModel, modelPos, yAxis, RAD2DEG*meshRotation, modelScale, LILAC); - displayModel->materials[0].maps[MATERIAL_MAP_ALBEDO].texture.id = cacheID; - - if (JUGEMU_MODE()) - { - Vector3 *vertices = (Vector3 *)displayModel->meshes[0].vertices; - Triangle *triangles = (Triangle *)displayModel->meshes[0].indices; - for (int i = 0; i < displayModel->meshes[0].triangleCount; i++) - { - Vector3 a = TranslateRotateScale(vertices[triangles[i][0]], modelPos, modelScale, meshRotation); - Vector3 b = TranslateRotateScale(vertices[triangles[i][1]], modelPos, modelScale, meshRotation); - Vector3 c = TranslateRotateScale(vertices[triangles[i][2]], modelPos, modelScale, meshRotation); - if (Vector3DotProduct(Vector3Normalize(Vector3CrossProduct(Vector3Subtract(b, a), Vector3Subtract(c, a))), depth) > 0.0f) continue; - DrawLine3D(a, Intersect(&main, near, a), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); - DrawLine3D(b, Intersect(&main, near, b), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); - DrawLine3D(c, Intersect(&main, near, c), (Color){ RED_DAMASK.r, RED_DAMASK.g, RED_DAMASK.b, 20 }); - } - } - spatialFrameModel.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = perspectiveCorrectRenderTexture.texture; - //NOTE: there is still the alpha threshold issue without custom frag shader... - int useColors = 1; - SetShaderValue(spatialFrameModel.materials[0].shader, useVertexColorsLoc, &useColors, SHADER_UNIFORM_INT); - if (JUGEMU_MODE()) DrawModel(spatialFrameModel, modelPos, 1.0f, WHITE); - - EndMode3D(); - - DrawText("[1]: CUBE [2]: SPHERE [3]: KNOT", 12, 12, fontSize, NEON_CARROT); - DrawText("ARROWS: MOVE | SPACEBAR: PAUSE", 12, 38, fontSize, NEON_CARROT); - DrawText("W S : ZOOM ", 12, 64, fontSize, NEON_CARROT); - DrawText((targetMesh == 0)? "GEN_CUBE" : (targetMesh == 1)? "GEN_SPHERE" : "GEN_KNOT", 12, 205, fontSize, NEON_CARROT); - DrawText("ASPECT [ Q ]:", 12, 392, fontSize, SUNFLOWER); - DrawText((ASPECT_CORRECT())? "CORRECT" : "INCORRECT", 230, 392, fontSize, (ASPECT_CORRECT())? ANAKIWA : CHESTNUT_ROSE); - DrawText("LENS [ O ]:", 510, 366, fontSize, SUNFLOWER); - DrawText((ORTHO_MODE())? "ORTHOGRAPHIC" : "PERSPECTIVE", 630, 366, fontSize, (ORTHO_MODE())? BAHAMA_BLUE : ANAKIWA); - - EndDrawing(); - //---------------------------------------------------------------------------------- - } - - // De-Initialization - //-------------------------------------------------------------------------------------- - UnloadModel(models[0]); - UnloadModel(models[1]); - UnloadModel(models[2]); - UnloadModel(spatialFrameModel); - if (perspectiveCorrectRenderTexture.id) UnloadRenderTexture(perspectiveCorrectRenderTexture); - UnloadShader(customShader); - CloseWindow(); - //-------------------------------------------------------------------------------------- - - return 0; -} - -static void UpdateSpatialFrame(Camera3D *main, float aspect, float near, float far, Mesh *spatialFrame) -{ - Vector3 depth = Vector3Normalize(Vector3Subtract(main->target, main->position)); - Vector3 right = Vector3Normalize(Vector3CrossProduct(depth, main->up)); - Vector3 up = Vector3Normalize(Vector3CrossProduct(right, depth)); - float halfHNear = Lerp(near*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); - float halfWNear = halfHNear*aspect; - float halfHFar = Lerp(far*tanf(DEG2RAD*fovyPerspective*0.5f), 0.5f*nearPlaneHeightOrthographic, OrthoBlendFactor(0.0f)); - float halfWFar = halfHFar*aspect; - float halfDepth = 0.5f*(far - near); - Vector3 centerNear = Vector3Add(main->position, Vector3Scale(depth, near)); - - for (int i = 0; i < spatialFrame->vertexCount; ++i) - { - Vector3 offset = Vector3Subtract(((Vector3 *)spatialFrame->vertices)[i], centerNear); - float xSign = (Vector3DotProduct(offset, right) >= 0.0f)? 1.0f : -1.0f; - float ySign = (Vector3DotProduct(offset, up) >= 0.0f)? 1.0f : -1.0f; - float farMask = (Vector3DotProduct(offset, depth) > halfDepth)? 1.0f : 0.0f; - float finalHalfW = halfWNear + farMask*(halfWFar - halfWNear); - float finalHalfH = halfHNear + farMask*(halfHFar - halfHNear); - Vector3 center = Vector3Add(centerNear, Vector3Scale(depth, farMask*2.0f*halfDepth)); - ((Vector3 *)spatialFrame->vertices)[i] = Vector3Add(center, Vector3Add(Vector3Scale(right, xSign*finalHalfW), Vector3Scale(up, ySign*finalHalfH))); - } -} - -static void DrawSpatialFrame(Mesh *spatialFrame) -{ - static int frontFaces[4][2] = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 } }; - static int backFaces[4][2] = { { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 } }; - static int ribFaces[4][2] = { { 0, 4 }, { 1, 7 }, { 2, 6 }, { 3, 5 } }; - static int (*faces[3])[2] = { frontFaces, backFaces, ribFaces }; - - for (int i = 0; i < 3; i++) - for (int j = 0; j < 4; j++) - { - Vector3 startPosition = ((Vector3 *)spatialFrame->vertices)[faces[i][j][0]]; - Vector3 endPosition = ((Vector3 *)spatialFrame->vertices)[faces[i][j][1]]; - DrawLine3D(startPosition, endPosition, (i == 0)? NEON_CARROT : (i == 1)? EGGPLANT : HOPBUSH); - } -} - -static void PerspectiveCorrectCapture(Camera3D *main, Model *model, RenderTexture2D *perspectiveCorrectRenderTexture, float rotation) -{ - BeginTextureMode(*perspectiveCorrectRenderTexture); - ClearBackground(BLANK); - BeginMode3D(*main); - DrawModelEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, WHITE); - DrawModelWiresEx(*model, modelPos, yAxis, RAD2DEG*rotation, modelScale, MARINER); - EndMode3D(); - EndTextureMode(); -} - -static void OrbitSpace(Camera3D *jugemu, float dt) -{ - float radius = Vector3Length(jugemu->position); - float azimuth = atan2f(jugemu->position.z, jugemu->position.x); - float horizontalRadius = sqrtf(jugemu->position.x*jugemu->position.x + jugemu->position.z*jugemu->position.z); - float elevation = atan2f(jugemu->position.y, horizontalRadius); - if (IsKeyDown(KEY_LEFT)) azimuth += 1.0f*dt; - if (IsKeyDown(KEY_RIGHT)) azimuth -= 1.0f*dt; - if (IsKeyDown(KEY_UP)) elevation += 1.0f*dt; - if (IsKeyDown(KEY_DOWN)) elevation -= 1.0f*dt; - if (IsKeyDown(KEY_W)) radius -= 1.0f*dt; - if (IsKeyDown(KEY_S)) radius += 1.0f*dt; - elevation = Clamp(elevation, -PI/2 + 0.1f, PI/2 - 0.1f); - jugemu->position.x = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*cosf(azimuth); - jugemu->position.y = Clamp(radius, 0.25f, 10.0f)*sinf(elevation); - jugemu->position.z = Clamp(radius, 0.25f, 10.0f)*cosf(elevation)*sinf(azimuth); -} - -static void FillVertexColors(Mesh *mesh) -{ - if (!mesh->colors) mesh->colors = RL_CALLOC(mesh->vertexCount, sizeof(Color)); - Color *colors = (Color *)mesh->colors; - Vector3 *vertices = (Vector3 *)mesh->vertices; - BoundingBox bounds = GetMeshBoundingBox(*mesh); - - for (int i = 0; i < mesh->vertexCount; ++i) - { - Vector3 vertex = vertices[i]; - float nx = (vertex.x - 0.5f*(bounds.min.x + bounds.max.x))/(0.5f*(bounds.max.x - bounds.min.x)); - float ny = (vertex.y - 0.5f*(bounds.min.y + bounds.max.y))/(0.5f*(bounds.max.y - bounds.min.y)); - float nz = (vertex.z - 0.5f*(bounds.min.z + bounds.max.z))/(0.5f*(bounds.max.z - bounds.min.z)); - float len = sqrtf(nx*nx + ny*ny + nz*nz); - colors[i] = (Color){ lrintf(127.5f*(nx/len + 1.0f)), lrintf(127.5f*(ny/len + 1.0f)), lrintf(127.5f*(nz/len + 1.0f)), 255 }; - } -} - -static Vector3 TranslateRotateScale(Vector3 coordinate, Vector3 pos, Vector3 scale, float rotation) -{ - return Vector3Transform(coordinate, MatrixMultiply(MatrixMultiply(MatrixScale(scale.x, scale.y, scale.z), MatrixRotateY(rotation)), MatrixTranslate(pos.x, pos.y, pos.z))); -} - -static Vector3 Intersect(Camera3D *main, float near, Vector3 worldCoord) -{ - Vector3 viewDir = Vector3Normalize(Vector3Subtract(main->target, main->position)); - Vector3 mainCameraToPoint = Vector3Subtract(worldCoord, main->position); - float depthAlongView = Vector3DotProduct(mainCameraToPoint, viewDir); - Vector3 centerNearPlane = Vector3Add(main->position, Vector3Scale(viewDir, near)); - if (depthAlongView <= 0.0f) return centerNearPlane; - float scaleToNear = near/depthAlongView; - Vector3 resultPerspective = Vector3Add(main->position, Vector3Scale(mainCameraToPoint, scaleToNear)); - Vector3 resultOrtho = Vector3Add(worldCoord, Vector3Scale(viewDir, Vector3DotProduct(Vector3Subtract(centerNearPlane, worldCoord), viewDir))); - return Vector3Lerp(resultPerspective, resultOrtho, OrthoBlendFactor(0.0f)); -} - -static float OrthoBlendFactor(float dt) -{ - static float blend = 0.0f; - if (dt > 0.0f) blend = Clamp(blend + ((ORTHO_MODE())? 1.0f : -1.0f)*blendScalar*dt, 0.0f, 1.0f); - return blend; -} \ No newline at end of file diff --git a/examples/core/core_3d_camera_view_opengl33.png b/examples/core/core_3d_camera_view_opengl33.png deleted file mode 100644 index cdac66493e0fcfce95189973e7dafa8a0de39076..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103636 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 +#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