From 97e74be0eb3150bc16f3dfcac09a8aefa74523ae Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Wed, 15 Jun 2022 20:53:28 +0300 Subject: [PATCH] static batching for level geometry --- Source/Game/Level/BrushScript.cs | 10 + Source/Game/Level/Q3MapImporter.cs | 284 +++++++++++++++++++++++------ 2 files changed, 236 insertions(+), 58 deletions(-) create mode 100644 Source/Game/Level/BrushScript.cs diff --git a/Source/Game/Level/BrushScript.cs b/Source/Game/Level/BrushScript.cs new file mode 100644 index 0000000..808cffc --- /dev/null +++ b/Source/Game/Level/BrushScript.cs @@ -0,0 +1,10 @@ +using System; +using FlaxEngine; + +namespace Game +{ + [ExecuteInEditMode] + public class BrushScript : Script + { + } +} \ No newline at end of file diff --git a/Source/Game/Level/Q3MapImporter.cs b/Source/Game/Level/Q3MapImporter.cs index 6ffd356..a769b0e 100644 --- a/Source/Game/Level/Q3MapImporter.cs +++ b/Source/Game/Level/Q3MapImporter.cs @@ -333,10 +333,10 @@ namespace Game private void LoadMap(bool forceLoad) { worldSpawnActor = Actor.FindActor("WorldSpawn"); + LevelScript levelScript = worldSpawnActor?.GetScript(); if (worldSpawnActor != null) { #if FLAX_EDITOR - LevelScript levelScript = worldSpawnActor.GetScript(); DateTime timestamp = File.GetLastWriteTime(mapPath); if (timestamp != levelScript.MapTimestamp) { @@ -375,6 +375,7 @@ namespace Game //Console.Print("Map parsing time: " + sw.Elapsed.TotalMilliseconds + "ms"); bool oneMesh = false; + bool useStaticBatching = true; bool convexMesh = true; if (worldSpawnActor == null) @@ -382,7 +383,7 @@ namespace Game worldSpawnActor = Actor.AddChild(); worldSpawnActor.Name = "WorldSpawn"; - LevelScript levelScript = worldSpawnActor.AddScript(); + levelScript = worldSpawnActor.AddScript(); #if FLAX_EDITOR levelScript.MapTimestamp = File.GetLastWriteTime(mapPath); #endif @@ -390,6 +391,8 @@ namespace Game //worldSpawnActor.HideFlags |= HideFlags.DontSave; //worldSpawnActor.HideFlags |= HideFlags.DontSelect; } + else + levelScript = worldSpawnActor.GetScript(); if (!oneMesh) { @@ -605,73 +608,238 @@ namespace Game // pass 3: static models & collision sw.Restart(); - brushIndex = 0; - foreach (BrushGeometry geom in brushGeometries) + + if (!useStaticBatching) { - StaticModel childModel = worldSpawnActor.AddChild(); - childModel.Name = "Brush_" + brushIndex; - childModel.Model = geom.model; - childModel.Position = geom.offset; - - for (int i = 0; i < geom.meshes.Length; i++) - childModel.SetMaterial(i, geom.meshes[i].material); - - uint[] indices = new uint[geom.vertices.Length]; - for (uint i = 0; i < indices.Length; i++) - indices[i] = i; - - bool isClipMaterial = false; - bool isMissingMaterial = false; - if (geom.meshes.Length == 1) + brushIndex = 0; + foreach (BrushGeometry geom in brushGeometries) { - MaterialParameter info = geom.meshes[0].material.GetParameter("IsClipMaterial"); - if (info != null && (bool)info.Value) + StaticModel childModel = worldSpawnActor.AddChild(); + childModel.Name = "Brush_" + brushIndex; + childModel.Model = geom.model; + childModel.Position = geom.offset; + + for (int i = 0; i < geom.meshes.Length; i++) + childModel.SetMaterial(i, geom.meshes[i].material); + + BrushScript brushScript = childModel.AddScript(); + + uint[] indices = new uint[geom.vertices.Length]; + for (uint i = 0; i < indices.Length; i++) + indices[i] = i; + + bool isClipMaterial = false; + bool isMissingMaterial = false; + if (geom.meshes.Length == 1) { + MaterialParameter info = geom.meshes[0].material.GetParameter("IsClipMaterial"); + if (info != null && (bool)info.Value) + { + var entries = childModel.Entries; + entries[0].Visible = false; + entries[0].ShadowsMode = ShadowsCastingMode.None; + entries[0].ReceiveDecals = false; + childModel.Entries = entries; + isClipMaterial = true; + } + + if (geom.meshes[0].material == missingMaterial) + isMissingMaterial = true; + } + + /*{ var entries = childModel.Entries; - entries[0].Visible = false; - entries[0].ShadowsMode = ShadowsCastingMode.None; - entries[0].ReceiveDecals = false; + for (int i=0; i < entries.Length; i++) + entries[i].Visible = false; childModel.Entries = entries; - isClipMaterial = true; - } + }*/ - if (geom.meshes[0].material == missingMaterial) - isMissingMaterial = true; - } + if (!isClipMaterial && !isMissingMaterial) + sdfModels.Add(geom.model); - /*{ - var entries = childModel.Entries; - for (int i=0; i < entries.Length; i++) - entries[i].Visible = false; - childModel.Entries = entries; - }*/ - - if (!isClipMaterial && !isMissingMaterial) - sdfModels.Add(geom.model); - - CollisionData collisionData = Content.CreateVirtualAsset(); - if (collisionData.CookCollision( - convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, geom.vertices, - indices)) - { - bool failed = true; - if (convexMesh) - { - // fallback to triangle mesh - failed = collisionData.CookCollision(CollisionDataType.TriangleMesh, + CollisionData collisionData = Content.CreateVirtualAsset(); + if (collisionData.CookCollision( + convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, geom.vertices, - indices); - if (!failed) - Console.PrintWarning("Hull brush " + brushIndex + " is not convex"); + indices)) + { + bool failed = true; + if (convexMesh) + { + // fallback to triangle mesh + failed = collisionData.CookCollision(CollisionDataType.TriangleMesh, + geom.vertices, + indices); + if (!failed) + Console.PrintWarning("Hull brush " + brushIndex + " is not convex"); + } + + if (failed) + throw new Exception("failed to cook final collision"); } - if (failed) - throw new Exception("failed to cook final collision"); + MeshCollider meshCollider = childModel.AddChild(); + meshCollider.CollisionData = collisionData; + brushIndex++; + } + } + else + { + // create brush holder actors and collision + brushIndex = 0; + foreach (BrushGeometry geom in brushGeometries) + { + Actor childModel = worldSpawnActor.AddChild(); + childModel.Name = "Brush_" + brushIndex; + //childModel.Model = geom.model; + childModel.Position = geom.offset; + + //for (int i = 0; i < geom.meshes.Length; i++) + // childModel.SetMaterial(i, geom.meshes[i].material); + + BrushScript brushScript = childModel.AddScript(); + + uint[] indices = new uint[geom.vertices.Length]; + for (uint i = 0; i < indices.Length; i++) + indices[i] = i; + + bool isClipMaterial = false; + bool isMissingMaterial = false; + /*if (geom.meshes.Length == 1) + { + MaterialParameter info = geom.meshes[0].material.GetParameter("IsClipMaterial"); + if (info != null && (bool)info.Value) + { + var entries = childModel.Entries; + entries[0].Visible = false; + entries[0].ShadowsMode = ShadowsCastingMode.None; + entries[0].ReceiveDecals = false; + childModel.Entries = entries; + isClipMaterial = true; + } + + if (geom.meshes[0].material == missingMaterial) + isMissingMaterial = true; + }*/ + + /*{ + var entries = childModel.Entries; + for (int i=0; i < entries.Length; i++) + entries[i].Visible = false; + childModel.Entries = entries; + }*/ + + //if (!isClipMaterial && !isMissingMaterial) + // sdfModels.Add(geom.model); + + CollisionData collisionData = Content.CreateVirtualAsset(); + if (collisionData.CookCollision( + convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, + geom.vertices, + indices)) + { + bool failed = true; + if (convexMesh) + { + // fallback to triangle mesh + failed = collisionData.CookCollision(CollisionDataType.TriangleMesh, + geom.vertices, + indices); + if (!failed) + Console.PrintWarning("Hull brush " + brushIndex + " is not convex"); + } + + if (failed) + throw new Exception("failed to cook final collision"); + } + + MeshCollider meshCollider = childModel.AddChild(); + meshCollider.CollisionData = collisionData; + brushIndex++; } - MeshCollider meshCollider = childModel.AddChild(); - meshCollider.CollisionData = collisionData; - brushIndex++; + // collect batches + brushIndex = 0; + Dictionary> batches = new Dictionary>(); + foreach (BrushGeometry geom in brushGeometries) + { + bool isClipMaterial = false; + bool isMissingMaterial = false; + if (geom.meshes.Length == 1) + { + MaterialParameter info = geom.meshes[0].material.GetParameter("IsClipMaterial"); + if (info != null && (bool)info.Value) + isClipMaterial = true; + + if (geom.meshes[0].material == missingMaterial) + isMissingMaterial = true; + } + + var brushMaterial = geom.meshes[0].material; + + if (!isClipMaterial) + { + if (!batches.TryGetValue(brushMaterial, out var batchGeometries)) + { + batchGeometries = new List(); + batches.Add(brushMaterial, batchGeometries); + } + + batchGeometries.Add(geom); + } + + /*{ + var entries = childModel.Entries; + for (int i=0; i < entries.Length; i++) + entries[i].Visible = false; + childModel.Entries = entries; + }*/ + + //if (!isClipMaterial && !isMissingMaterial) + // sdfModels.Add(geom.model); + + brushIndex++; + } + + foreach (var kvp in batches) + { + List normals = new List(); + List uvs = new List(); + List vertices = new List(); + List indices = new List(); + + uint indicesOffset = 0; + foreach (BrushGeometry geom in kvp.Value) + { + for (int i=0; i(); + batchModel.SetupLODs(new[] { 1 }); + batchModel.SetupMaterialSlots(1); + + batchModel.LODs[0].Meshes[0].UpdateMesh(vertices, indices, normals, + null, uvs); + batchModel.LODs[0].Meshes[0].MaterialSlotIndex = 0; + batchModel.MaterialSlots[0].Material = kvp.Key; + + StaticModel childModel = worldSpawnActor.AddChild(); + childModel.Name = "Batch_" + kvp.Key.Path; + childModel.Model = batchModel; + //childModel.Position = geom.offset; + } } sw.Stop(); @@ -862,7 +1030,7 @@ namespace Game Stopwatch sw = Stopwatch.StartNew(); Console.Print($"Generating level SDF ({sdfModels.Count} models)..."); - Parallel.ForEach(sdfModels, new ParallelOptions() { MaxDegreeOfParallelism = 1 }, model => + Parallel.ForEach(sdfModels/*, new ParallelOptions() { MaxDegreeOfParallelism = 1 }*/, model => { if (model.WaitForLoaded()) throw new Exception("model was not loaded");