Files
FlaxEngine/Source/Engine/CSG/CSGMesh.Triangulate.cpp

262 lines
11 KiB
C++

// Copyright (c) 2012-2024 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<MeshVertex>& 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<MeshVertex> 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<int32, Dictionary<int32, Array<int32>>> polygonsPerBrush(64);
// Build index buffer
int32 firstI, secondI, thirdI;
int32 triangleIndices[3];
Vector2 uvs[3];
MeshVertex 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;
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<Vector2> 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