diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index af1ecfc6d..2786168f6 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -43,8 +43,10 @@ typedef struct MeshBuilder { static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]); static void FreeMeshBuilder(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); +#define FreeDecalMeshData() GenMeshDecal((Model){ .meshCount = -1.0f }, (Matrix){ 0 }, 0.0f, 0.0f) +static bool Button(Rectangle rec, char *label); //------------------------------------------------------------------------------------ // Program main entry point @@ -105,10 +107,6 @@ int main(void) decalMaterial.maps[MATERIAL_MAP_DIFFUSE].texture = decalTexture; 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; Model decalModels[MAX_DECALS] = { 0 }; int decalCount = 0; @@ -123,8 +121,6 @@ int main(void) //---------------------------------------------------------------------------------- if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) UpdateCamera(&camera, CAMERA_THIRD_PERSON); - if (IsKeyPressed(KEY_SPACE)) showModel = !showModel; - // Display information about closest hit RayCollision collision = { 0 }; collision.distance = FLT_MAX; @@ -165,206 +161,12 @@ int main(void) // Spin the placement around a bit 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++; - 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 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) { Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); @@ -418,13 +220,20 @@ int main(void) 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; triangleCount += decalModels[i].meshes[0].triangleCount; - yPos += 15; } DrawText("TOTAL", x0, yPos, 10, LIME); @@ -434,6 +243,23 @@ int main(void) 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); + + 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); @@ -446,13 +272,14 @@ int main(void) UnloadModel(model); 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); - FreeMeshBuilder(&meshBuilders[0]); - FreeMeshBuilder(&meshBuilders[1]); + // Free the data for decal generation + FreeDecalMeshData(); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- @@ -536,3 +363,245 @@ static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) 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; +} \ No newline at end of file diff --git a/examples/models/models_decals.png b/examples/models/models_decals.png index 48e027fe2..6bdbaf17c 100644 Binary files a/examples/models/models_decals.png and b/examples/models/models_decals.png differ