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