// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "CSGMesh.h" #if COMPILE_WITH_CSG_BUILDER #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Rectangle.h" #include "Engine/Core/Collections/Dictionary.h" #include "CSGData.h" bool CSG::Mesh::Triangulate(RawData& data, Array& cacheVB) const { // Reject empty meshes int32 verticesCount = _vertices.Count(); if (verticesCount == 0) return true; // Mesh triangles data cacheVB.Clear(); cacheVB.EnsureCapacity(_polygons.Count() * 3); Array surfaceCacheVB(32); // Cache submeshes by material to lay them down // key- brush index, value- direcotry for surfaces (key: surface index, value: list with start vertex per triangle) Dictionary>> polygonsPerBrush(64); // Build index buffer int32 firstI, secondI, thirdI; int32 triangleIndices[3]; Vector2 uvs[3]; RawModelVertex rawVertex; for (int32 i = 0; i < _polygons.Count(); i++) { const Polygon& polygon = _polygons[i]; if (!polygon.Visible || polygon.FirstEdgeIndex == INVALID_INDEX) continue; HalfEdge iterator = _edges[polygon.FirstEdgeIndex]; firstI = iterator.VertexIndex; iterator = _edges[iterator.NextIndex]; secondI = iterator.VertexIndex; int32 lastIndex = -1; const HalfEdge& polygonFirst = _edges[polygon.FirstEdgeIndex]; while (iterator != polygonFirst) { if (lastIndex == iterator.NextIndex) break; lastIndex = iterator.NextIndex; iterator = _edges.At(lastIndex); thirdI = iterator.VertexIndex; // Validate triangle if (firstI != secondI && firstI != thirdI && secondI != thirdI) { // Cache triangle parent surface info const Surface& surface = _surfaces[polygon.SurfaceIndex]; Vector3 uvPos, centerPos; Matrix trans, transRotation, finalTrans; // Build triangle indices if (polygon.Inverted) { triangleIndices[0] = thirdI; triangleIndices[1] = secondI; triangleIndices[2] = firstI; } else { triangleIndices[0] = firstI; triangleIndices[1] = secondI; triangleIndices[2] = thirdI; } Vector3 surfaceUp = surface.Normal; Vector3 surfaceForward = -Vector3::Cross(surfaceUp, Vector3::Right); if (surfaceForward.IsZero()) surfaceForward = Vector3::Forward; Matrix::CreateWorld(Vector3::Zero, surfaceForward, surfaceUp, trans); Matrix::RotationAxis(surfaceUp, surface.TexCoordRotation * DegreesToRadians, transRotation); Matrix::Multiply(transRotation, trans, finalTrans); Vector3::Transform(Vector3::Zero, finalTrans, centerPos); // Calculate normal vector Vector3 v0 = _vertices[triangleIndices[0]]; Vector3 v1 = _vertices[triangleIndices[1]]; Vector3 v2 = _vertices[triangleIndices[2]]; Float3 vd0 = v1 - v0; Float3 vd1 = v2 - v0; Float3 normal = Float3::Normalize(vd0 ^ vd1); // Calculate texture uvs based on vertex position for (int32 q = 0; q < 3; q++) { Vector3 pos = _vertices[triangleIndices[q]]; Vector3::Transform(pos, finalTrans, uvPos); Float2 texCoord = Vector2(uvPos.X - centerPos.X, uvPos.Z - centerPos.Z); // Apply surface uvs transformation uvs[q] = (texCoord * (surface.TexCoordScale * CSG_MESH_UV_SCALE)) + surface.TexCoordOffset; } // Calculate tangent (it needs uvs) Float2 uvd0 = uvs[1] - uvs[0]; Float2 uvd1 = uvs[2] - uvs[0]; float dR = (uvd0.X * uvd1.Y - uvd1.X * uvd0.Y); if (Math::IsZero(dR)) dR = 1.0f; float r = 1.0f / dR; Float3 tangent = (vd0 * uvd1.Y - vd1 * uvd0.Y) * r; Float3 bitangent = (vd1 * uvd0.X - vd0 * uvd1.X) * r; tangent.Normalize(); // Gram-Schmidt orthogonalize Float3 newTangentUnnormalized = tangent - normal * Float3::Dot(normal, tangent); const float length = newTangentUnnormalized.Length(); // Workaround to handle degenerated case if (Math::IsZero(length)) { tangent = Float3::Cross(normal, Float3::UnitX); if (Math::IsZero(tangent.Length())) tangent = Float3::Cross(normal, Float3::UnitY); tangent.Normalize(); bitangent = Float3::Cross(normal, tangent); } else { tangent = newTangentUnnormalized / length; bitangent.Normalize(); } // Build triangle int32 vertexIndex = cacheVB.Count(); for (int32 q = 0; q < 3; q++) { rawVertex.Position = _vertices[triangleIndices[q]]; rawVertex.TexCoord = uvs[q]; rawVertex.Normal = normal; rawVertex.Tangent = tangent; rawVertex.Bitangent = bitangent; rawVertex.LightmapUVs = Vector2::Zero; rawVertex.Color = Color::Black; cacheVB.Add(rawVertex); } // Find brush that produced that surface bool isValid = false; for (int32 brushIndex = 0; brushIndex < _brushesMeta.Count(); brushIndex++) { auto& brushMeta = _brushesMeta[brushIndex]; if (Math::IsInRange(polygon.SurfaceIndex, brushMeta.StartSurfaceIndex, brushMeta.StartSurfaceIndex + brushMeta.SurfacesCount - 1)) { // Cache triangle polygonsPerBrush[brushIndex][polygon.SurfaceIndex].Add(vertexIndex); isValid = true; break; } } ASSERT_LOW_LAYER(isValid); } secondI = thirdI; } } // Check if mesh has no triangles if (cacheVB.IsEmpty()) return true; // TODO: preallocate data material slots and buffers so data.AddTriangle will be simple copy op // Setup result mesh data for (auto iPerBrush = polygonsPerBrush.Begin(); iPerBrush != polygonsPerBrush.End(); ++iPerBrush) { auto& brushMeta = _brushesMeta[iPerBrush->Key]; for (auto iPerSurface = iPerBrush->Value.Begin(); iPerSurface != iPerBrush->Value.End(); ++iPerSurface) { int32 surfaceIndex = iPerSurface->Key; int32 brushSurfaceIndex = surfaceIndex - brushMeta.StartSurfaceIndex; int32 triangleCount = iPerSurface->Value.Count(); int32 vertexCount = triangleCount * 3; Float2 lightmapUVsMin = Float2::Maximum; Float2 lightmapUVsMax = Float2::Minimum; const Surface& surface = _surfaces[surfaceIndex]; // Generate lightmap uvs per brush surface { // We just assume that brush surface is now some kind of rectangle (after triangulation) // So we can project vertices locations into texture space uvs [0;1] based on minimum and maximum vertex location Vector3 surfaceNormal = surface.Normal; // Calculate bounds Vector2 min = Vector2::Maximum, max = Vector2::Minimum; Matrix projection, view, vp; Vector3 up = Vector3::Up; if (Vector3::NearEqual(surfaceNormal, Vector3::Up)) up = Vector3::Right; else if (Vector3::NearEqual(surfaceNormal, Vector3::Down)) up = Vector3::Forward; Matrix::LookAt(Vector3::Zero, surfaceNormal, up, view); Matrix::Ortho(1000, 1000, 0.00001f, 100000.0f, projection); Matrix::Multiply(view, projection, vp); Array pointsCache(vertexCount); for (int32 triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) { int32 triangleStartVertex = iPerSurface->Value[triangleIndex]; for (int32 k = 0; k < 3; k++) { auto& vertex = cacheVB[triangleStartVertex + k]; Vector3 projected = Vector3::Project(vertex.Position, 0, 0, 1000, 1000, 0, 1, vp); Vector2 projectedXY = Vector2(projected); min = Vector2::Min(projectedXY, min); max = Vector2::Max(projectedXY, max); pointsCache.Add(projectedXY); } } // Normalize projected positions to get lightmap uvs Real projectedSize = (max - min).MaxValue(); for (int32 triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) { int32 triangleStartVertex = iPerSurface->Value[triangleIndex]; for (int32 k = 0; k < 3; k++) { auto& vertex = cacheVB[triangleStartVertex + k]; vertex.LightmapUVs = (pointsCache[triangleIndex * 3 + k] - min) / projectedSize; lightmapUVsMin = Float2::Min(lightmapUVsMin, vertex.LightmapUVs); lightmapUVsMax = Float2::Max(lightmapUVsMax, vertex.LightmapUVs); } } } // Write triangles surfaceCacheVB.Clear(); for (int32 triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++) { int32 triangleStartVertex = iPerSurface->Value[triangleIndex]; surfaceCacheVB.Add(cacheVB[triangleStartVertex + 0]); surfaceCacheVB.Add(cacheVB[triangleStartVertex + 1]); surfaceCacheVB.Add(cacheVB[triangleStartVertex + 2]); } Rectangle lightmapUVsBox(lightmapUVsMin, lightmapUVsMax - lightmapUVsMin); data.AddSurface(brushMeta.Parent, brushSurfaceIndex, surface.Material, surface.ScaleInLightmap, lightmapUVsBox, surfaceCacheVB.Get(), surfaceCacheVB.Count()); } } return false; } #endif