From 03e28d389b3aae51df3c04571513ba533934a4d8 Mon Sep 17 00:00:00 2001 From: Banane_Rotative Date: Mon, 1 Dec 2025 14:00:10 +0100 Subject: [PATCH] Map creation: Front and back face collision --- Assets/Editor/SvgToFlatMeshEditor.cs | 249 +++++++++++++++++++-------- 1 file changed, 179 insertions(+), 70 deletions(-) diff --git a/Assets/Editor/SvgToFlatMeshEditor.cs b/Assets/Editor/SvgToFlatMeshEditor.cs index a09cd9c..b000eb9 100644 --- a/Assets/Editor/SvgToFlatMeshEditor.cs +++ b/Assets/Editor/SvgToFlatMeshEditor.cs @@ -87,14 +87,21 @@ public class SvgToFlatMeshEditor : EditorWindow // Gather shapes by fill color. We'll traverse the scene tree. var shapesByColor = new Dictionary>(new ColorEqualityComparer()); + var wallsByColor = new Dictionary>(new ColorEqualityComparer()); - TraverseAndCollectShapes(sceneInfo.Scene.Root, Matrix2D.identity, shapesByColor); + TraverseAndCollectShapes(sceneInfo.Scene.Root, Matrix2D.identity, shapesByColor, wallsByColor); if (shapesByColor.Count == 0) { EditorUtility.DisplayDialog("Result", "No filled shapes found in the SVG.", "OK"); return; } + if (wallsByColor.Count == 0) + { + EditorUtility.DisplayDialog("Result", "No wall shapes found in the SVG.", "OK"); + return; + } + // Create parent container GameObject container = new GameObject(Path.GetFileNameWithoutExtension(svgFile.name) + "_SVG_Meshes"); if (parentTransform != null) { @@ -105,23 +112,7 @@ public class SvgToFlatMeshEditor : EditorWindow foreach (var kv in shapesByColor) { Color color = kv.Key; List entries = kv.Value; - - // Build a temporary scene that contains all these shapes combined (preserving transforms) - Scene tmpScene = new Scene(); - tmpScene.Root = new SceneNode(); - tmpScene.Root.Children = new List(); - - foreach (var entry in entries) { - // create a shallow copy Node with transform and the original shapes (the shape objects can be reused) - SceneNode copyNode = new SceneNode() { - Transform = entry.Node.Transform, // keep transform - Shapes = new List() { entry.Shape } - }; - tmpScene.Root.Children.Add(copyNode); - } - - // Tessellate the tmpScene - var geoms = VectorUtils.TessellateScene(tmpScene, tessOptions); + List geoms = TesselateIntoGeometries(entries); if (geoms == null || geoms.Count == 0) { Debug.LogWarning($"No geometry generated for color {color} (skipping)."); @@ -132,42 +123,21 @@ public class SvgToFlatMeshEditor : EditorWindow Mesh mesh = BuildMeshFromGeometries(geoms, sceneCenter, meshScale); // Create GameObject for this color region - string colorName = ColorToName(color); - GameObject go = new GameObject($"Region_{colorName}"); - go.transform.SetParent(container.transform, false); + string objectName = BuildObjectName(color, "Floor"); + BuildGameObject(objectName, color, mesh, container); + } - var mf = go.AddComponent(); - mf.sharedMesh = mesh; + foreach (var kv in wallsByColor) + { + Color color = kv.Key; + List entries = kv.Value; - var mr = go.AddComponent(); + // Build Mesh from + Mesh mesh = BuildExtrudedMeshFromBeziers(entries, sceneCenter, meshScale, 5.0f); - if (editorMaterial != null) { - // instantiate a material so each region can have its own color without overwriting the original asset - Material matInstance = new Material(editorMaterial); - matInstance.color = color; - mr.sharedMaterial = matInstance; - } - else { - // Create a quick default material - var mat = new Material(Shader.Find("Standard")); - mat.color = color; - mr.sharedMaterial = mat; - } - - // Generate collider - var mc = go.AddComponent(); - mc.sharedMesh = mesh; - mc.convex = false; // keep non-convex for flat terrain; set to true if needed for rigidbodies - - // Add tag to disable mesh renderer before build - go.tag = "EditorOnlyMeshRenderer"; - - // Automatically assign audio triggers based on color - string folder = colorFolderMap.GetFolder(color); - if (folder != null) - { - // TODO: automatically assign audio triggers - } + // Create GameObject for this wall color region + string objectName = BuildObjectName(color, "Wall"); + BuildGameObject(objectName, color, mesh, container); } // Focus selection on created container @@ -175,8 +145,73 @@ public class SvgToFlatMeshEditor : EditorWindow EditorUtility.DisplayDialog("Done", $"Generated {shapesByColor.Count} region GameObjects under '{container.name}'.", "OK"); } - // Recursively traverse scene nodes and collect filled shapes - void TraverseAndCollectShapes(SceneNode node, Matrix2D parentTransform, Dictionary> shapesByColor) { + // Tesselate a list of SceneNodeShapeEntry into VectorUtils.Geometry list + List TesselateIntoGeometries(List entries) { + // Build a temporary scene that contains all these shapes combined (preserving transforms) + Scene tmpScene = new Scene(); + tmpScene.Root = new SceneNode(); + tmpScene.Root.Children = new List(); + + foreach (var entry in entries) { + // create a shallow copy Node with transform and the original shapes (the shape objects can be reused) + SceneNode copyNode = new SceneNode() { + Transform = entry.Node.Transform, // keep transform + Shapes = new List() { entry.Shape } + }; + tmpScene.Root.Children.Add(copyNode); + } + + // Tessellate the tmpScene + return VectorUtils.TessellateScene(tmpScene, tessOptions); + } + + string BuildObjectName(Color color, string prefix) + { + string colorName = ColorToName(color); + return $"{prefix}_{colorName}"; + } + + void BuildGameObject(string objectName, Color color, Mesh mesh, GameObject container) + { + GameObject go = new GameObject(objectName); + go.transform.SetParent(container.transform, false); + + MeshFilter mf = go.AddComponent(); + mf.sharedMesh = mesh; + + MeshRenderer mr = go.AddComponent(); + + if (editorMaterial != null) { + // instantiate a material so each region can have its own color without overwriting the original asset + Material matInstance = new Material(editorMaterial); + matInstance.color = color; + mr.sharedMaterial = matInstance; + } + else { + // Create a quick default material + Material mat = new Material(Shader.Find("Standard")); + mat.color = color; + mr.sharedMaterial = mat; + } + + // Generate collider + var mc = go.AddComponent(); + mc.sharedMesh = mesh; + mc.convex = false; // keep non-convex for flat terrain; set to true if needed for rigidbodies + + // Add tag to disable mesh renderer before build + go.tag = "EditorOnlyMeshRenderer"; + + // Automatically assign audio triggers based on color + string folder = colorFolderMap.GetFolder(color); + if (folder != null) + { + // TODO: automatically assign audio triggers + } + } + + // Recursively traverse scene nodes and collect filled shapes and walls by color + void TraverseAndCollectShapes(SceneNode node, Matrix2D parentTransform, Dictionary> shapesByColor, Dictionary> wallsByColor) { if (node == null) { return; } @@ -189,7 +224,7 @@ public class SvgToFlatMeshEditor : EditorWindow if (shape == null) { continue; } - // Only treat fills (SolidFill) + // Only treat fills (SolidFill) for floors if (shape.Fill is SolidFill sf) { Color col = sf.Color; // Note: color comes as linear RGBA. Convert to Unity's Color (already same type) @@ -205,43 +240,51 @@ public class SvgToFlatMeshEditor : EditorWindow }; list.Add(new SceneNodeShapeEntry() { Node = fakeNode, Shape = shape }); } + + // Treat contours as walls, and only those with stroke color defined + if (shape.Contours != null && shape.Contours.Length > 0 && shape.PathProps.Stroke != null) + { + Color wallColor = shape.PathProps.Stroke.Color; + if (!wallsByColor.TryGetValue(wallColor, out List wallList)) { + wallList = new List(); + wallsByColor[wallColor] = wallList; + } + + // Add all contours as wall segments + foreach (BezierContour contour in shape.Contours) + { + wallList.Add(contour.Segments); + } + } } } if (node.Children != null && node.Children.Count > 0) { foreach (var c in node.Children) { - TraverseAndCollectShapes(c, currentTransform, shapesByColor); + TraverseAndCollectShapes(c, currentTransform, shapesByColor, wallsByColor); } } } // Build a Mesh from VectorUtils.Geometry list Mesh BuildMeshFromGeometries(List geoms, Vector2 geomsCenter, float globalScale) { - var verts = new List(); - var uvs = new List(); - var indices = new List(); + // Forget about UVs (unnecessary for our use case) + List verts = new List(); + List indices = new List(); int baseIndex = 0; - foreach (var g in geoms) { + foreach (VectorUtils.Geometry g in geoms) { if (g == null || g.Vertices == null || g.Indices == null) { continue; } // Add vertices (VectorUtils uses Vector2 for geometry XY) for (int i = 0; i < g.Vertices.Length; i++) { - var v2 = g.Vertices[i]; + Vector2 v2 = g.Vertices[i]; // Map XY -> XZ plane; Y = 0 Vector3 v3 = new Vector3(v2.x-geomsCenter.x, 0f, -v2.y+geomsCenter.y) * globalScale; verts.Add(v3); - - // UVs: If geometry provides UV, use it; otherwise use XY mapped to UV - if (g.UVs != null && g.UVs.Length == g.Vertices.Length) { - uvs.Add(g.UVs[i]); - } - else { - uvs.Add(new Vector2(v2.x, -v2.y)); - } } // Add indices (triangles) @@ -261,8 +304,6 @@ public class SvgToFlatMeshEditor : EditorWindow mesh.indexFormat = (verts.Count > 65535) ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16; mesh.SetVertices(verts); mesh.SetTriangles(indices, 0); - if (uvs != null && uvs.Count == verts.Count) - mesh.SetUVs(0, uvs); mesh.RecalculateNormals(); mesh.RecalculateBounds(); @@ -270,6 +311,74 @@ public class SvgToFlatMeshEditor : EditorWindow return mesh; } + // Build an extruded Mesh from geometries + Mesh BuildExtrudedMeshFromBeziers(List beziers, Vector2 geomsCenter, float globalScale, float height){ + // Forget about UVs (unnecessary for our use case) + List verts = new List(); + List indices = new List(); + + Vector3 geomsCenter3D = new Vector3(geomsCenter.x, 0f, -geomsCenter.y); + + // Treat each path separately as a closed shape to extrude + foreach (BezierPathSegment[] bezier in beziers) + { + // Add vertices: low and high for each point + for (int i=0; i 65535) ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16; + mesh.SetVertices(verts); + mesh.SetTriangles(indices, 0); + + mesh.RecalculateNormals(); + mesh.RecalculateBounds(); + + return mesh; + } + // Helper to produce a safe string for color names string ColorToName(Color c) { // Try to present RGBA hex