Skip to content

Commit 0ef9f9a

Browse files
committed
Implemented better face grouping algorithm.
1 parent b415c68 commit 0ef9f9a

File tree

4 files changed

+116
-59
lines changed

4 files changed

+116
-59
lines changed

Geometry/MeshBuilder.cs

Lines changed: 105 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
using Source2Roblox.Textures;
1414

1515
using RobloxFiles.DataTypes;
16+
using System.Threading.Tasks;
17+
using System.Collections.Concurrent;
18+
using System.Threading;
19+
using RobloxFiles;
1620

1721
namespace Source2Roblox.Geometry
1822
{
@@ -167,40 +171,6 @@ public static void BakeBSP(BSPFile bsp, string exportDir, GameMount game = null)
167171
vertIndices.Add(edge);
168172
}
169173

170-
var windingToBrushSide = new Dictionary<Winding, BrushSide>();
171-
var brushSideToBrush = new Dictionary<BrushSide, Brush>();
172-
var windingOctree = new Octree<Winding>();
173-
174-
var brushSides = bsp.BrushSides;
175-
var brushes = bsp.Brushes;
176-
int solved = 0;
177-
178-
foreach (var brush in brushes)
179-
{
180-
var sides = new List<BrushSide>();
181-
var firstSide = brush.FirstSide;
182-
var numSides = brush.NumSides;
183-
184-
for (int i = 0; i < numSides; i++)
185-
{
186-
var side = brushSides[firstSide + i];
187-
brushSideToBrush[side] = brush;
188-
sides.Add(side);
189-
}
190-
191-
Console.WriteLine($"Solving Brush {solved++}/{brushes.Count}");
192-
var windings = bsp.SolveFaces(brush);
193-
194-
for (int i = 0; i < windings.Count; i++)
195-
{
196-
var winding = windings[i];
197-
var side = sides[i];
198-
199-
Debugger.Break();
200-
}
201-
}
202-
203-
204174
var materialSets = new Dictionary<string, ValveMaterial>();
205175
var vertOffsets = new Dictionary<int, Vector3>();
206176

@@ -309,6 +279,7 @@ public static void BakeBSP(BSPFile bsp, string exportDir, GameMount game = null)
309279
}
310280

311281
var faces = bsp.Faces.OrderBy(face => face.Material);
282+
var faceOctree = new Octree<Face>();
312283
numNorms = 0;
313284

314285
foreach (Face face in faces)
@@ -492,8 +463,45 @@ public static void BakeBSP(BSPFile bsp, string exportDir, GameMount game = null)
492463
numUVs += numEdges;
493464
numNorms += numEdges;
494465
numVerts += numEdges;
466+
467+
faceOctree.CreateNode(center, face);
468+
}
469+
470+
// Cluster nearby faces by material.
471+
var facesLeft = faces
472+
.Where(face => !face.Skip)
473+
.ToHashSet();
474+
475+
var clusters = new List<HashSet<Face>>();
476+
477+
while (facesLeft.Any())
478+
{
479+
var face = facesLeft.First();
480+
facesLeft.Remove(face);
481+
482+
var cluster = new HashSet<Face>();
483+
cluster.Add(face);
484+
485+
var area = face.Area;
486+
var sqrtArea = (float)Math.Sqrt(area);
487+
488+
var nearby = faceOctree
489+
.RadiusSearch(face.Center, sqrtArea * 5)
490+
.Where(other => other.Material == face.Material)
491+
.Where(facesLeft.Contains);
492+
493+
foreach (var other in nearby)
494+
{
495+
facesLeft.Remove(other);
496+
cluster.Add(other);
497+
}
498+
499+
clusters.Add(cluster);
495500
}
496501

502+
// Write OBJ files.
503+
Console.WriteLine("Writing OBJ files...");
504+
497505
string objPath = Path.Combine(exportDir, $"{mapName}.obj");
498506
string mtlPath = Path.Combine(exportDir, $"{mapName}.mtl");
499507

@@ -509,25 +517,26 @@ public static void BakeBSP(BSPFile bsp, string exportDir, GameMount game = null)
509517
foreach (var uv in uvs)
510518
objWriter.AppendLine($"vt {uv.X} {1f - uv.Y}");
511519

512-
var facesByMaterial = faces
513-
.GroupBy(face => face.Material)
514-
.OrderBy(group => group.Key);
515-
516-
int faceCount = 0;
517-
518-
foreach (var group in facesByMaterial)
520+
for (int i = 0; i < clusters.Count; i++)
519521
{
520-
objWriter.AppendLine($"usemtl {group.Key}");
521-
522-
foreach (var face in group)
522+
bool first = true;
523+
var cluster = clusters[i];
524+
objWriter.AppendLine($"o cluster_{i}");
525+
526+
foreach (var face in cluster)
523527
{
524528
int dispInfo = face.DispInfo,
525529
numEdges = face.NumEdges,
526530
firstVert = face.FirstVert,
527531
firstNorm = face.FirstNorm,
528532
firstUV = face.FirstUV;
529533

530-
objWriter.AppendLine($" o face_{faceCount++}");
534+
if (first)
535+
{
536+
string material = face.Material;
537+
objWriter.AppendLine($" usemtl {material}");
538+
first = false;
539+
}
531540

532541
if (dispInfo >= 0)
533542
{
@@ -556,19 +565,18 @@ public static void BakeBSP(BSPFile bsp, string exportDir, GameMount game = null)
556565
}
557566
else
558567
{
559-
var material = face.Material;
560568
var center = face.Center;
561569

562570
if (face.Skip)
563571
continue;
564572

565573
objWriter.Append(" f");
566574

567-
for (int i = 0; i < numEdges; i++)
575+
for (int j = 0; j < numEdges; j++)
568576
{
569-
var normIndex = 1 + firstNorm + i;
570-
var vertIndex = 1 + firstVert + i;
571-
var uvIndex = 1 + firstUV + i;
577+
var normIndex = 1 + firstNorm + j;
578+
var vertIndex = 1 + firstVert + j;
579+
var uvIndex = 1 + firstUV + j;
572580

573581
objWriter.Append($" {vertIndex}/{uvIndex}/{normIndex}");
574582
}
@@ -583,6 +591,53 @@ public static void BakeBSP(BSPFile bsp, string exportDir, GameMount game = null)
583591

584592
string mtl = mtlWriter.ToString();
585593
File.WriteAllText(mtlPath, mtl);
594+
595+
// Write Roblox Files...
596+
Console.WriteLine("Writing Roblox files...");
597+
598+
string gameName = GameMount.GetGameName(game);
599+
string localAppData = Environment.GetEnvironmentVariable("localappdata");
600+
601+
string sourceDir = Path.Combine(localAppData, "Roblox Studio", "content", "source", gameName);
602+
string mapsDir = Path.Combine(sourceDir, "maps");
603+
string mapDir = Path.Combine(mapsDir, bsp.Name);
604+
605+
Directory.CreateDirectory(sourceDir);
606+
Directory.CreateDirectory(mapsDir);
607+
Directory.CreateDirectory(mapDir);
608+
609+
string savePath = Path.Combine(mapsDir, bsp.Name + ".rbxm");
610+
var map = new BinaryRobloxFile();
611+
612+
var level = new Model()
613+
{
614+
Name = bsp.Name,
615+
Parent = map
616+
};
617+
618+
foreach (var cluster in clusters)
619+
{
620+
var mesh = new RobloxMesh();
621+
622+
foreach (var face in cluster)
623+
{
624+
var numEdges = face.NumEdges;
625+
626+
int firstNorm = face.FirstNorm,
627+
firstVert = face.FirstVert,
628+
firstUV = face.FirstUV;
629+
630+
for (int i = 0; i < numEdges; i++)
631+
{
632+
var normIndex = 1 + firstNorm + i;
633+
var vertIndex = 1 + firstVert + i;
634+
var uvIndex = 1 + firstUV + i;
635+
636+
}
637+
}
638+
}
639+
640+
map.Save(savePath);
586641
}
587642
}
588643
}

Octree/OctreeRegion.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public OctreeRegion(Vector3 pos, Vector3 size)
5252

5353
public OctreeRegion(Vector3 pos, Vector3 size, OctreeRegion<T> parent, int parentIndex) : this(pos, size)
5454
{
55-
Debug.Assert(parentIndex < 0 && parentIndex > 7);
55+
Debug.Assert(parentIndex > -1 && parentIndex < 8);
5656
Debug.Assert(parent != null);
5757

5858
ParentIndex = parentIndex;
@@ -155,9 +155,8 @@ public OctreeRegion<T> GetOrCreateSubRegionAtDepth(Vector3 pos, int maxDepth)
155155
for (int i = Depth; i < maxDepth; i++)
156156
{
157157
int index = current.GetSubRegionIndex(pos);
158-
var next = current.SubRegions[index];
159-
160-
if (next == null)
158+
159+
if (!current.SubRegions.TryGetValue(index, out var next))
161160
{
162161
next = current.CreateSubRegion(index);
163162
current.SubRegions[index] = next;

World/BSPFile.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ private static float RoundCoord(float value)
279279
return value;
280280
}
281281

282-
public List<Winding> SolveFaces(Brush brush)
282+
public Dictionary<int, Winding> SolveFaces(Brush brush)
283283
{
284284
int numSides = brush.NumSides,
285285
firstSide = brush.FirstSide;
@@ -314,7 +314,7 @@ public List<Winding> SolveFaces(Brush brush)
314314
var f2 = planeCheck.Normal;
315315

316316
// Check for duplicate plane within some tolerance.
317-
if (f1.Dot(f2) > 0.99)
317+
if (f1.Dot(f2) > 0.999f)
318318
{
319319
var d1 = plane.Dist;
320320
var d2 = planeCheck.Dist;
@@ -331,7 +331,7 @@ public List<Winding> SolveFaces(Brush brush)
331331
// Now we have a set of planes, indicated by `true` values in the 'usePlanes' array,
332332
// from which we will build a solid.
333333

334-
var faces = new List<Winding>();
334+
var faces = new Dictionary<int, Winding>();
335335

336336
for (int i = 0; i < numSides; i++)
337337
{
@@ -381,7 +381,7 @@ public List<Winding> SolveFaces(Brush brush)
381381
}
382382

383383
winding.RemoveDuplicates(MIN_EDGE_LENGTH_EPSILON);
384-
faces.Add(winding);
384+
faces[i] = winding;
385385
}
386386
}
387387

World/Winding.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ public Winding(Plane plane)
6464
up = new Vector3(1, 0, 0);
6565

6666
var scale = -up.Dot(normal);
67-
up = (up + normal * scale).Unit;
67+
up += scale * normal;
68+
69+
var length = up.Magnitude;
70+
up /= length + 1e-10f;
6871

6972
var origin = normal * plane.Dist;
7073
var right = up.Cross(normal);

0 commit comments

Comments
 (0)