Browse Source

examples_model_decals : Fixed unload crash, added buttons (#5306)

themushroompirates 1 month ago
parent
commit
e244cf297a
2 changed files with 283 additions and 214 deletions
  1. 283 214
      examples/models/models_decals.c
  2. BIN
      examples/models/models_decals.png

+ 283 - 214
examples/models/models_decals.c

@@ -43,8 +43,10 @@ typedef struct MeshBuilder {
 static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]);
 static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]);
 static void FreeMeshBuilder(MeshBuilder *mb);
 static void FreeMeshBuilder(MeshBuilder *mb);
 static Mesh BuildMesh(MeshBuilder *mb);
 static Mesh BuildMesh(MeshBuilder *mb);
-static Mesh GenMeshDecal(Mesh inputMesh, Ray ray);
+static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset);
 static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s);
 static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s);
+#define FreeDecalMeshData() GenMeshDecal((Model){ .meshCount = -1.0f }, (Matrix){ 0 }, 0.0f, 0.0f)
+static bool Button(Rectangle rec, char *label);
 
 
 //------------------------------------------------------------------------------------
 //------------------------------------------------------------------------------------
 // Program main entry point
 // Program main entry point
@@ -105,10 +107,6 @@ int main(void)
     decalMaterial.maps[MATERIAL_MAP_DIFFUSE].texture = decalTexture;
     decalMaterial.maps[MATERIAL_MAP_DIFFUSE].texture = decalTexture;
     decalMaterial.maps[MATERIAL_MAP_DIFFUSE].color = RAYWHITE;
     decalMaterial.maps[MATERIAL_MAP_DIFFUSE].color = RAYWHITE;
     
     
-    // We're going to use these to build up our decal meshes
-    // They'll resize automatically as we go, we'll free them at the end
-    MeshBuilder meshBuilders[2] = { 0 };
-    
     bool showModel = true;
     bool showModel = true;
     Model decalModels[MAX_DECALS] = { 0 };
     Model decalModels[MAX_DECALS] = { 0 };
     int decalCount = 0;
     int decalCount = 0;
@@ -123,8 +121,6 @@ int main(void)
         //----------------------------------------------------------------------------------
         //----------------------------------------------------------------------------------
         if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) UpdateCamera(&camera, CAMERA_THIRD_PERSON);
         if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) UpdateCamera(&camera, CAMERA_THIRD_PERSON);
 
 
-        if (IsKeyPressed(KEY_SPACE)) showModel = !showModel;
-
         // Display information about closest hit
         // Display information about closest hit
         RayCollision collision = { 0 };
         RayCollision collision = { 0 };
         collision.distance = FLT_MAX;
         collision.distance = FLT_MAX;
@@ -165,206 +161,12 @@ int main(void)
             
             
             // Spin the placement around a bit
             // Spin the placement around a bit
             splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD*((float)GetRandomValue(-180, 180))));
             splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD*((float)GetRandomValue(-180, 180))));
-            Matrix splatInv = MatrixInvert(splat);
-            
-            // Reset the mesh builders
-            meshBuilders[0].vertexCount = 0;
-            meshBuilders[1].vertexCount = 0;
-            
-            // We'll be flip-flopping between the two mesh builders
-            // Reading from one and writing to the other, then swapping
-            int mbIndex = 0;
-            
-            // First pass, just get any triangle inside the bounding box (for each mesh of the model)
-            for (int meshIndex = 0; meshIndex < model.meshCount; meshIndex++)
-            {
-                Mesh mesh = model.meshes[meshIndex];
-                for (int tri = 0; tri < mesh.triangleCount; tri++)
-                {
-                    Vector3 vertices[3] = { 0 };
-                    
-                    // The way we calculate the vertices of the mesh triangle
-                    // depend on whether the mesh vertices are indexed or not
-                    if (mesh.indices == 0)
-                    {
-                        for (int v = 0; v < 3; v++)
-                        {
-                            vertices[v] = (Vector3){
-                                mesh.vertices[3*3*tri + 3*v + 0],
-                                mesh.vertices[3*3*tri + 3*v + 1],
-                                mesh.vertices[3*3*tri + 3*v + 2]
-                            };
-                        }
-                    }
-                    else
-                    {
-                        for (int v = 0; v < 3; v++)
-                        {
-                            vertices[v] = (Vector3){
-                                mesh.vertices[ 3*mesh.indices[3*tri+0] + v],
-                                mesh.vertices[ 3*mesh.indices[3*tri+1] + v],
-                                mesh.vertices[ 3*mesh.indices[3*tri+2] + v]
-                            };
-                        }
-                    }
-                    
-                    // Transform all 3 vertices of the triangle
-                    // and check if they are inside our decal box
-                    int insideCount = 0;
-                    for (int i = 0; i < 3; i++)
-                    {
-                        // To splat space
-                        Vector3 v = Vector3Transform(vertices[i], splat);
-                        
-                        if ((fabsf(v.x) < decalSize) || (fabsf(v.y) <= decalSize) || (fabsf(v.z) <= decalSize)) insideCount++;
-                        
-                        // We need to keep the transformed vertex
-                        vertices[i] = v;
-                    }
-                    
-                    // If any of them are inside, we add the triangle - we'll clip it later
-                    if (insideCount > 0) AddTriangleToMeshBuilder(&meshBuilders[mbIndex], vertices);
-                }
-            }
-            
-            // Clipping time! We need to clip against all 6 directions
-            Vector3 planes[6] = {
-               {  1,  0,  0 },
-               { -1,  0,  0 }, 
-               {  0,  1,  0 }, 
-               {  0, -1,  0 }, 
-               {  0,  0,  1 }, 
-               {  0,  0, -1 }
-            };
-            
-            for (int face = 0; face < 6; face++)
-            {
-                // Swap current model builder (so we read from the one we just wrote to)
-                mbIndex = 1 - mbIndex;
-                
-                MeshBuilder *inMesh = &meshBuilders[1 - mbIndex];
-                MeshBuilder *outMesh = &meshBuilders[mbIndex];
-                
-                // Reset write builder
-                outMesh->vertexCount = 0;
-                
-                float s = 0.5f*decalSize;
-                
-                for (int i = 0; i < inMesh->vertexCount; i += 3)
-                {
-                    Vector3 nV1, nV2, nV3, nV4;
-
-                    float d1 = Vector3DotProduct(inMesh->vertices[ i + 0 ], planes[face] ) - s;
-                    float d2 = Vector3DotProduct(inMesh->vertices[ i + 1 ], planes[face] ) - s;
-                    float d3 = Vector3DotProduct(inMesh->vertices[ i + 2 ], planes[face] ) - s;
-
-                    int v1Out = (d1 > 0);
-                    int v2Out = (d2 > 0);
-                    int v3Out = (d3 > 0);
-
-                    // Calculate, how many vertices of the face lie outside of the clipping plane
-                    int total = v1Out + v2Out + v3Out;
-
-                    switch (total)
-                    {
-                        case 0:
-                        {
-                            // The entire face lies inside of the plane, no clipping needed
-                            AddTriangleToMeshBuilder(outMesh, (Vector3[3]){inMesh->vertices[i], inMesh->vertices[i+1], inMesh->vertices[i+2]});
-                        } break;
-                        case 1:
-                        {
-                            // One vertex lies outside of the plane, perform clipping
-                            if (v1Out)
-                            {
-                                nV1 = inMesh->vertices[i + 1];
-                                nV2 = inMesh->vertices[i + 2];
-                                nV3 = ClipSegment(inMesh->vertices[i], nV1, planes[face], s);
-                                nV4 = ClipSegment(inMesh->vertices[i], nV2, planes[face], s);
-                            }
-
-                            if (v2Out) 
-                            {
-                                nV1 = inMesh->vertices[i];
-                                nV2 = inMesh->vertices[i + 2];
-                                nV3 = ClipSegment(inMesh->vertices[i + 1], nV1, planes[face], s);
-                                nV4 = ClipSegment(inMesh->vertices[i + 1], nV2, planes[face], s);
-
-                                AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV3, nV2, nV1});
-                                AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV2, nV3, nV4});
-                                break;
-                            }
-
-                            if (v3Out) 
-                            {
-                                nV1 = inMesh->vertices[i];
-                                nV2 = inMesh->vertices[i + 1];
-                                nV3 = ClipSegment(inMesh->vertices[i + 2], nV1, planes[face], s);
-                                nV4 = ClipSegment(inMesh->vertices[i + 2], nV2, planes[face], s);
-                            }
-                            
-                            AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3});
-                            AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2});
-                        } break;
-                        case 2: 
-                        {
-                            // Two vertices lies outside of the plane, perform clipping
-                            if (!v1Out)
-                            {
-                                nV1 = inMesh->vertices[i];
-                                nV2 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s);
-                                nV3 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s);
-                                AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3});
-                            }
-                            
-                            if (!v2Out)
-                            {
-                                nV1 = inMesh->vertices[i + 1];
-                                nV2 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s);
-                                nV3 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s);
-                                AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3});
-                            }
-                            
-                            if (!v3Out)
-                            {
-                                nV1 = inMesh->vertices[i + 2];
-                                nV2 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s);
-                                nV3 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s);
-                                AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3});
-                            }
-                        } break;
-                        case 3: // The entire face lies outside of the plane, so let's discard the corresponding vertices
-                        default: break;
-                    }
-                }
-            }
             
             
-            // Now we just need to re-transform the vertices
-            MeshBuilder *theMesh = &meshBuilders[mbIndex];
-            
-            // Allocate room for UVs
-            if (theMesh->vertexCount > 0)
-            {
-                theMesh->uvs = (Vector2 *)MemAlloc(sizeof(Vector2)*theMesh->vertexCount);
-                
-                for (int i = 0; i < theMesh->vertexCount; i++)
-                {
-                    // Calculate the UVs based on the projected coords
-                    // They are clipped to (-decalSize .. decalSize) and we want them (0..1)
-                    theMesh->uvs[i].x = (theMesh->vertices[i].x/decalSize + 0.5f);
-                    theMesh->uvs[i].y = (theMesh->vertices[i].y/decalSize + 0.5f);
-                    
-                    // From splat space to world space
-                    theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], splatInv);
-                    
-                    // Tiny nudge in the normal direction so it renders properly over the mesh
-                    theMesh->vertices[i] = Vector3Add(theMesh->vertices[i], Vector3Scale(collision.normal, decalOffset));
-                }
-                
-                // Decal model data ready, create it and add it
+            Mesh decalMesh = GenMeshDecal(model, splat, decalSize, decalOffset);
+            if (decalMesh.vertexCount > 0) {
                 int decalIndex = decalCount++;
                 int decalIndex = decalCount++;
-                decalModels[decalIndex] = LoadModelFromMesh(BuildMesh(theMesh));
-                decalModels[decalIndex].materials[0] = decalMaterial;
+                decalModels[decalIndex] = LoadModelFromMesh(decalMesh);
+                decalModels[decalIndex].materials[0].maps[0] = decalMaterial.maps[0];
             }
             }
         }
         }
         //----------------------------------------------------------------------------------
         //----------------------------------------------------------------------------------
@@ -381,7 +183,7 @@ int main(void)
                 // Draw the decal models
                 // Draw the decal models
                 for (int i = 0; i < decalCount; i++) DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE);
                 for (int i = 0; i < decalCount; i++) DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE);
 
 
-                // If we hit the mesh, draw the box for the decal 
+                // If we hit the mesh, draw the box for the decal
                 if (collision.hit)
                 if (collision.hit)
                 {
                 {
                     Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f));
                     Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f));
@@ -418,13 +220,20 @@ int main(void)
             
             
             for (int i = 0; i < decalCount; i++)
             for (int i = 0; i < decalCount; i++)
             {
             {
-                DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME);
-                DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME);
-                DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME);
+                if (i == 20) {
+                    DrawText("...", x0, yPos, 10, LIME);
+                    yPos += 15;
+                }
+                
+                if (i < 20) {
+                    DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME);
+                    DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME);
+                    DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME);
+                    yPos += 15;
+                }
                 
                 
                 vertexCount += decalModels[i].meshes[0].vertexCount;
                 vertexCount += decalModels[i].meshes[0].vertexCount;
                 triangleCount += decalModels[i].meshes[0].triangleCount;
                 triangleCount += decalModels[i].meshes[0].triangleCount;
-                yPos += 15;
             }
             }
             
             
             DrawText("TOTAL", x0, yPos, 10, LIME);
             DrawText("TOTAL", x0, yPos, 10, LIME);
@@ -434,6 +243,23 @@ int main(void)
 
 
             DrawText("Hold RMB to move camera", 10, 430, 10, GRAY);
             DrawText("Hold RMB to move camera", 10, 430, 10, GRAY);
             DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY);
             DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY);
+            
+            Rectangle rect = (Rectangle){ 10, screenHeight - 100, 100, 60 };
+            
+            if (Button(rect, showModel ? "Hide Model" : "Show Model")) {
+                showModel = !showModel;
+            }
+            
+            rect.x += rect.width + 10;
+            
+            if (Button(rect, "Clear Decals")) {
+                for (int i = 0; i < decalCount; i++)
+                {
+                    UnloadModel(decalModels[i]);
+                }
+                decalCount = 0;
+            }
+            
 
 
             DrawFPS(10, 10);
             DrawFPS(10, 10);
 
 
@@ -446,13 +272,14 @@ int main(void)
     UnloadModel(model);
     UnloadModel(model);
     UnloadTexture(modelTexture);
     UnloadTexture(modelTexture);
     
     
-    // TODO: WARNING: This line crashes program on closing
-    //for (int i = 0; i < decalCount; i++) UnloadModel(decalModels[i]);
+    for (int i = 0; i < decalCount; i++) {
+        UnloadModel(decalModels[i]);
+    }
 
 
     UnloadTexture(decalTexture);
     UnloadTexture(decalTexture);
     
     
-    FreeMeshBuilder(&meshBuilders[0]);
-    FreeMeshBuilder(&meshBuilders[1]);
+    // Free the data for decal generation
+    FreeDecalMeshData();
 
 
     CloseWindow();              // Close window and OpenGL context
     CloseWindow();              // Close window and OpenGL context
     //--------------------------------------------------------------------------------------
     //--------------------------------------------------------------------------------------
@@ -536,3 +363,245 @@ static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s)
 
 
     return position;
     return position;
 }
 }
+
+static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset)
+{
+    // We're going to use these to build up our decal meshes
+    // They'll resize automatically as we go, we'll free them at the end
+    static MeshBuilder meshBuilders[2] = { 0 };
+    
+    // Ugly way of telling us to free the static MeshBuilder data
+    if (inputModel.meshCount == -1) {
+        FreeMeshBuilder(&meshBuilders[0]);
+        FreeMeshBuilder(&meshBuilders[1]);
+        return (Mesh){0};
+    }
+    
+    // We're going to need the inverse matrix
+    Matrix invProj = MatrixInvert(projection);
+            
+    // Reset the mesh builders
+    meshBuilders[0].vertexCount = 0;
+    meshBuilders[1].vertexCount = 0;
+
+    // We'll be flip-flopping between the two mesh builders
+    // Reading from one and writing to the other, then swapping
+    int mbIndex = 0;
+
+    // First pass, just get any triangle inside the bounding box (for each mesh of the model)
+    for (int meshIndex = 0; meshIndex < inputModel.meshCount; meshIndex++)
+    {
+        Mesh mesh = inputModel.meshes[meshIndex];
+        for (int tri = 0; tri < mesh.triangleCount; tri++)
+        {
+            Vector3 vertices[3] = { 0 };
+            
+            // The way we calculate the vertices of the mesh triangle
+            // depend on whether the mesh vertices are indexed or not
+            if (mesh.indices == 0)
+            {
+                for (int v = 0; v < 3; v++)
+                {
+                    vertices[v] = (Vector3){
+                        mesh.vertices[3*3*tri + 3*v + 0],
+                        mesh.vertices[3*3*tri + 3*v + 1],
+                        mesh.vertices[3*3*tri + 3*v + 2]
+                    };
+                }
+            }
+            else
+            {
+                for (int v = 0; v < 3; v++)
+                {
+                    vertices[v] = (Vector3){
+                        mesh.vertices[ 3*mesh.indices[3*tri+0] + v],
+                        mesh.vertices[ 3*mesh.indices[3*tri+1] + v],
+                        mesh.vertices[ 3*mesh.indices[3*tri+2] + v]
+                    };
+                }
+            }
+            
+            // Transform all 3 vertices of the triangle
+            // and check if they are inside our decal box
+            int insideCount = 0;
+            for (int i = 0; i < 3; i++)
+            {
+                // To projection space
+                Vector3 v = Vector3Transform(vertices[i], projection);
+                
+                if ((fabsf(v.x) < decalSize) || (fabsf(v.y) <= decalSize) || (fabsf(v.z) <= decalSize)) insideCount++;
+                
+                // We need to keep the transformed vertex
+                vertices[i] = v;
+            }
+            
+            // If any of them are inside, we add the triangle - we'll clip it later
+            if (insideCount > 0) AddTriangleToMeshBuilder(&meshBuilders[mbIndex], vertices);
+        }
+    }
+
+    // Clipping time! We need to clip against all 6 directions
+    Vector3 planes[6] = {
+       {  1,  0,  0 },
+       { -1,  0,  0 },
+       {  0,  1,  0 },
+       {  0, -1,  0 },
+       {  0,  0,  1 },
+       {  0,  0, -1 }
+    };
+
+    for (int face = 0; face < 6; face++)
+    {
+        // Swap current model builder (so we read from the one we just wrote to)
+        mbIndex = 1 - mbIndex;
+        
+        MeshBuilder *inMesh = &meshBuilders[1 - mbIndex];
+        MeshBuilder *outMesh = &meshBuilders[mbIndex];
+        
+        // Reset write builder
+        outMesh->vertexCount = 0;
+        
+        float s = 0.5f*decalSize;
+        
+        for (int i = 0; i < inMesh->vertexCount; i += 3)
+        {
+            Vector3 nV1, nV2, nV3, nV4;
+
+            float d1 = Vector3DotProduct(inMesh->vertices[ i + 0 ], planes[face] ) - s;
+            float d2 = Vector3DotProduct(inMesh->vertices[ i + 1 ], planes[face] ) - s;
+            float d3 = Vector3DotProduct(inMesh->vertices[ i + 2 ], planes[face] ) - s;
+
+            int v1Out = (d1 > 0);
+            int v2Out = (d2 > 0);
+            int v3Out = (d3 > 0);
+
+            // Calculate, how many vertices of the face lie outside of the clipping plane
+            int total = v1Out + v2Out + v3Out;
+
+            switch (total)
+            {
+                case 0:
+                {
+                    // The entire face lies inside of the plane, no clipping needed
+                    AddTriangleToMeshBuilder(outMesh, (Vector3[3]){inMesh->vertices[i], inMesh->vertices[i+1], inMesh->vertices[i+2]});
+                } break;
+                case 1:
+                {
+                    // One vertex lies outside of the plane, perform clipping
+                    if (v1Out)
+                    {
+                        nV1 = inMesh->vertices[i + 1];
+                        nV2 = inMesh->vertices[i + 2];
+                        nV3 = ClipSegment(inMesh->vertices[i], nV1, planes[face], s);
+                        nV4 = ClipSegment(inMesh->vertices[i], nV2, planes[face], s);
+                    }
+
+                    if (v2Out)
+                    {
+                        nV1 = inMesh->vertices[i];
+                        nV2 = inMesh->vertices[i + 2];
+                        nV3 = ClipSegment(inMesh->vertices[i + 1], nV1, planes[face], s);
+                        nV4 = ClipSegment(inMesh->vertices[i + 1], nV2, planes[face], s);
+
+                        AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV3, nV2, nV1});
+                        AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV2, nV3, nV4});
+                        break;
+                    }
+
+                    if (v3Out)
+                    {
+                        nV1 = inMesh->vertices[i];
+                        nV2 = inMesh->vertices[i + 1];
+                        nV3 = ClipSegment(inMesh->vertices[i + 2], nV1, planes[face], s);
+                        nV4 = ClipSegment(inMesh->vertices[i + 2], nV2, planes[face], s);
+                    }
+                    
+                    AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3});
+                    AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2});
+                } break;
+                case 2:
+                {
+                    // Two vertices lies outside of the plane, perform clipping
+                    if (!v1Out)
+                    {
+                        nV1 = inMesh->vertices[i];
+                        nV2 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s);
+                        nV3 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s);
+                        AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3});
+                    }
+                    
+                    if (!v2Out)
+                    {
+                        nV1 = inMesh->vertices[i + 1];
+                        nV2 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s);
+                        nV3 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s);
+                        AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3});
+                    }
+                    
+                    if (!v3Out)
+                    {
+                        nV1 = inMesh->vertices[i + 2];
+                        nV2 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s);
+                        nV3 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s);
+                        AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3});
+                    }
+                } break;
+                case 3: // The entire face lies outside of the plane, so let's discard the corresponding vertices
+                default: break;
+            }
+        }
+    }
+
+    // Now we just need to re-transform the vertices
+    MeshBuilder *theMesh = &meshBuilders[mbIndex];
+
+    // Allocate room for UVs
+    if (theMesh->vertexCount > 0)
+    {
+        theMesh->uvs = (Vector2 *)MemAlloc(sizeof(Vector2)*theMesh->vertexCount);
+        
+        for (int i = 0; i < theMesh->vertexCount; i++)
+        {
+            // Calculate the UVs based on the projected coords
+            // They are clipped to (-decalSize .. decalSize) and we want them (0..1)
+            theMesh->uvs[i].x = (theMesh->vertices[i].x/decalSize + 0.5f);
+            theMesh->uvs[i].y = (theMesh->vertices[i].y/decalSize + 0.5f);
+            
+            // Tiny nudge in the normal direction so it renders properly over the mesh
+            theMesh->vertices[i].z -= decalOffset;
+            
+            // From projection space to world space
+            theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], invProj);
+        }
+        
+        // Decal model data ready, create the mesh and return it
+        return BuildMesh(theMesh);
+    }
+    else
+    {
+        // Return a blank mesh as there's nothing to add
+        return (Mesh){ 0 };
+    }
+}
+
+static bool Button(Rectangle rec, char *label)
+{
+    Color bgColor = GRAY;
+    bool pressed = false;
+    if (CheckCollisionPointRec(GetMousePosition(), rec)) {
+        bgColor = LIGHTGRAY;
+        if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
+            pressed = true;
+        }
+    }
+    
+    DrawRectangleRec(rec, bgColor);
+    DrawRectangleLinesEx(rec, 2.0f, DARKGRAY);
+    
+    float fontSize = 10.0f;
+    float textWidth = MeasureText(label, fontSize);
+    
+    DrawText(label, (int)(rec.x + rec.width*0.5f - textWidth*0.5f), (int)(rec.y + rec.height*0.5f - fontSize*0.5f), fontSize, DARKGRAY);
+    
+    return pressed;
+}

BIN
examples/models/models_decals.png