mirror of
https://github.com/appen-isen/jeu-sans-image.git
synced 2026-03-18 21:50:42 +01:00
Map creation: Front and back face collision
This commit is contained in:
@@ -87,14 +87,21 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
|
|
||||||
// Gather shapes by fill color. We'll traverse the scene tree.
|
// Gather shapes by fill color. We'll traverse the scene tree.
|
||||||
var shapesByColor = new Dictionary<Color, List<SceneNodeShapeEntry>>(new ColorEqualityComparer());
|
var shapesByColor = new Dictionary<Color, List<SceneNodeShapeEntry>>(new ColorEqualityComparer());
|
||||||
|
var wallsByColor = new Dictionary<Color, List<BezierPathSegment[]>>(new ColorEqualityComparer());
|
||||||
|
|
||||||
TraverseAndCollectShapes(sceneInfo.Scene.Root, Matrix2D.identity, shapesByColor);
|
TraverseAndCollectShapes(sceneInfo.Scene.Root, Matrix2D.identity, shapesByColor, wallsByColor);
|
||||||
|
|
||||||
if (shapesByColor.Count == 0) {
|
if (shapesByColor.Count == 0) {
|
||||||
EditorUtility.DisplayDialog("Result", "No filled shapes found in the SVG.", "OK");
|
EditorUtility.DisplayDialog("Result", "No filled shapes found in the SVG.", "OK");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wallsByColor.Count == 0)
|
||||||
|
{
|
||||||
|
EditorUtility.DisplayDialog("Result", "No wall shapes found in the SVG.", "OK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create parent container
|
// Create parent container
|
||||||
GameObject container = new GameObject(Path.GetFileNameWithoutExtension(svgFile.name) + "_SVG_Meshes");
|
GameObject container = new GameObject(Path.GetFileNameWithoutExtension(svgFile.name) + "_SVG_Meshes");
|
||||||
if (parentTransform != null) {
|
if (parentTransform != null) {
|
||||||
@@ -105,7 +112,41 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
foreach (var kv in shapesByColor) {
|
foreach (var kv in shapesByColor) {
|
||||||
Color color = kv.Key;
|
Color color = kv.Key;
|
||||||
List<SceneNodeShapeEntry> entries = kv.Value;
|
List<SceneNodeShapeEntry> entries = kv.Value;
|
||||||
|
List<VectorUtils.Geometry> geoms = TesselateIntoGeometries(entries);
|
||||||
|
|
||||||
|
if (geoms == null || geoms.Count == 0) {
|
||||||
|
Debug.LogWarning($"No geometry generated for color {color} (skipping).");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Mesh from geoms
|
||||||
|
Mesh mesh = BuildMeshFromGeometries(geoms, sceneCenter, meshScale);
|
||||||
|
|
||||||
|
// Create GameObject for this color region
|
||||||
|
string objectName = BuildObjectName(color, "Floor");
|
||||||
|
BuildGameObject(objectName, color, mesh, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kv in wallsByColor)
|
||||||
|
{
|
||||||
|
Color color = kv.Key;
|
||||||
|
List<BezierPathSegment[]> entries = kv.Value;
|
||||||
|
|
||||||
|
// Build Mesh from
|
||||||
|
Mesh mesh = BuildExtrudedMeshFromBeziers(entries, sceneCenter, meshScale, 5.0f);
|
||||||
|
|
||||||
|
// Create GameObject for this wall color region
|
||||||
|
string objectName = BuildObjectName(color, "Wall");
|
||||||
|
BuildGameObject(objectName, color, mesh, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus selection on created container
|
||||||
|
Selection.activeGameObject = container;
|
||||||
|
EditorUtility.DisplayDialog("Done", $"Generated {shapesByColor.Count} region GameObjects under '{container.name}'.", "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tesselate a list of SceneNodeShapeEntry into VectorUtils.Geometry list
|
||||||
|
List<VectorUtils.Geometry> TesselateIntoGeometries(List<SceneNodeShapeEntry> entries) {
|
||||||
// Build a temporary scene that contains all these shapes combined (preserving transforms)
|
// Build a temporary scene that contains all these shapes combined (preserving transforms)
|
||||||
Scene tmpScene = new Scene();
|
Scene tmpScene = new Scene();
|
||||||
tmpScene.Root = new SceneNode();
|
tmpScene.Root = new SceneNode();
|
||||||
@@ -121,25 +162,24 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tessellate the tmpScene
|
// Tessellate the tmpScene
|
||||||
var geoms = VectorUtils.TessellateScene(tmpScene, tessOptions);
|
return VectorUtils.TessellateScene(tmpScene, tessOptions);
|
||||||
|
|
||||||
if (geoms == null || geoms.Count == 0) {
|
|
||||||
Debug.LogWarning($"No geometry generated for color {color} (skipping).");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build Mesh from geoms
|
string BuildObjectName(Color color, string prefix)
|
||||||
Mesh mesh = BuildMeshFromGeometries(geoms, sceneCenter, meshScale);
|
{
|
||||||
|
|
||||||
// Create GameObject for this color region
|
|
||||||
string colorName = ColorToName(color);
|
string colorName = ColorToName(color);
|
||||||
GameObject go = new GameObject($"Region_{colorName}");
|
return $"{prefix}_{colorName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuildGameObject(string objectName, Color color, Mesh mesh, GameObject container)
|
||||||
|
{
|
||||||
|
GameObject go = new GameObject(objectName);
|
||||||
go.transform.SetParent(container.transform, false);
|
go.transform.SetParent(container.transform, false);
|
||||||
|
|
||||||
var mf = go.AddComponent<MeshFilter>();
|
MeshFilter mf = go.AddComponent<MeshFilter>();
|
||||||
mf.sharedMesh = mesh;
|
mf.sharedMesh = mesh;
|
||||||
|
|
||||||
var mr = go.AddComponent<MeshRenderer>();
|
MeshRenderer mr = go.AddComponent<MeshRenderer>();
|
||||||
|
|
||||||
if (editorMaterial != null) {
|
if (editorMaterial != null) {
|
||||||
// instantiate a material so each region can have its own color without overwriting the original asset
|
// instantiate a material so each region can have its own color without overwriting the original asset
|
||||||
@@ -149,7 +189,7 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Create a quick default material
|
// Create a quick default material
|
||||||
var mat = new Material(Shader.Find("Standard"));
|
Material mat = new Material(Shader.Find("Standard"));
|
||||||
mat.color = color;
|
mat.color = color;
|
||||||
mr.sharedMaterial = mat;
|
mr.sharedMaterial = mat;
|
||||||
}
|
}
|
||||||
@@ -170,13 +210,8 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus selection on created container
|
// Recursively traverse scene nodes and collect filled shapes and walls by color
|
||||||
Selection.activeGameObject = container;
|
void TraverseAndCollectShapes(SceneNode node, Matrix2D parentTransform, Dictionary<Color, List<SceneNodeShapeEntry>> shapesByColor, Dictionary<Color, List<BezierPathSegment[]>> wallsByColor) {
|
||||||
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<Color, List<SceneNodeShapeEntry>> shapesByColor) {
|
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -189,7 +224,7 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
if (shape == null) {
|
if (shape == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Only treat fills (SolidFill)
|
// Only treat fills (SolidFill) for floors
|
||||||
if (shape.Fill is SolidFill sf) {
|
if (shape.Fill is SolidFill sf) {
|
||||||
Color col = sf.Color;
|
Color col = sf.Color;
|
||||||
// Note: color comes as linear RGBA. Convert to Unity's Color (already same type)
|
// 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 });
|
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<BezierPathSegment[]> wallList)) {
|
||||||
|
wallList = new List<BezierPathSegment[]>();
|
||||||
|
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) {
|
if (node.Children != null && node.Children.Count > 0) {
|
||||||
foreach (var c in node.Children) {
|
foreach (var c in node.Children) {
|
||||||
TraverseAndCollectShapes(c, currentTransform, shapesByColor);
|
TraverseAndCollectShapes(c, currentTransform, shapesByColor, wallsByColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a Mesh from VectorUtils.Geometry list
|
// Build a Mesh from VectorUtils.Geometry list
|
||||||
Mesh BuildMeshFromGeometries(List<VectorUtils.Geometry> geoms, Vector2 geomsCenter, float globalScale) {
|
Mesh BuildMeshFromGeometries(List<VectorUtils.Geometry> geoms, Vector2 geomsCenter, float globalScale) {
|
||||||
var verts = new List<Vector3>();
|
// Forget about UVs (unnecessary for our use case)
|
||||||
var uvs = new List<Vector2>();
|
List<Vector3> verts = new List<Vector3>();
|
||||||
var indices = new List<int>();
|
List<int> indices = new List<int>();
|
||||||
|
|
||||||
int baseIndex = 0;
|
int baseIndex = 0;
|
||||||
foreach (var g in geoms) {
|
foreach (VectorUtils.Geometry g in geoms) {
|
||||||
if (g == null || g.Vertices == null || g.Indices == null) {
|
if (g == null || g.Vertices == null || g.Indices == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add vertices (VectorUtils uses Vector2 for geometry XY)
|
// Add vertices (VectorUtils uses Vector2 for geometry XY)
|
||||||
for (int i = 0; i < g.Vertices.Length; i++) {
|
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
|
// Map XY -> XZ plane; Y = 0
|
||||||
Vector3 v3 = new Vector3(v2.x-geomsCenter.x, 0f, -v2.y+geomsCenter.y) * globalScale;
|
Vector3 v3 = new Vector3(v2.x-geomsCenter.x, 0f, -v2.y+geomsCenter.y) * globalScale;
|
||||||
verts.Add(v3);
|
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)
|
// Add indices (triangles)
|
||||||
@@ -261,8 +304,74 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
mesh.indexFormat = (verts.Count > 65535) ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
|
mesh.indexFormat = (verts.Count > 65535) ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
|
||||||
mesh.SetVertices(verts);
|
mesh.SetVertices(verts);
|
||||||
mesh.SetTriangles(indices, 0);
|
mesh.SetTriangles(indices, 0);
|
||||||
if (uvs != null && uvs.Count == verts.Count)
|
|
||||||
mesh.SetUVs(0, uvs);
|
mesh.RecalculateNormals();
|
||||||
|
mesh.RecalculateBounds();
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build an extruded Mesh from geometries
|
||||||
|
Mesh BuildExtrudedMeshFromBeziers(List<BezierPathSegment[]> beziers, Vector2 geomsCenter, float globalScale, float height){
|
||||||
|
// Forget about UVs (unnecessary for our use case)
|
||||||
|
List<Vector3> verts = new List<Vector3>();
|
||||||
|
List<int> indices = new List<int>();
|
||||||
|
|
||||||
|
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<bezier.Length; i++)
|
||||||
|
{
|
||||||
|
Vector2 v2 = bezier[i].P0;
|
||||||
|
Vector3 v3_low = (new Vector3(v2.x, 0f, -v2.y) - geomsCenter3D) * globalScale;
|
||||||
|
Vector3 v3_high = (new Vector3(v2.x, height, -v2.y) - geomsCenter3D) * globalScale;
|
||||||
|
verts.Add(v3_low);
|
||||||
|
verts.Add(v3_low); // Back face duplicate
|
||||||
|
verts.Add(v3_high);
|
||||||
|
verts.Add(v3_high); // Back face duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add indices for triangles
|
||||||
|
for (int i = 0; i < bezier.Length; i++)
|
||||||
|
{
|
||||||
|
int next_i = (i + 1) % bezier.Length;
|
||||||
|
|
||||||
|
// Each quad between points i and nextI is made of two triangles, double sided
|
||||||
|
int low0 = i*4;
|
||||||
|
int high0 = i*4 +2;
|
||||||
|
int low1 = next_i*4;
|
||||||
|
int high1 = next_i*4 +2;
|
||||||
|
|
||||||
|
// Triangle 1
|
||||||
|
indices.Add(low0);
|
||||||
|
indices.Add(high0);
|
||||||
|
indices.Add(low1);
|
||||||
|
|
||||||
|
// Triangle 2
|
||||||
|
indices.Add(high0);
|
||||||
|
indices.Add(high1);
|
||||||
|
indices.Add(low1);
|
||||||
|
|
||||||
|
// Triangle 1 (back face)
|
||||||
|
indices.Add(low1 +1);
|
||||||
|
indices.Add(high0 +1);
|
||||||
|
indices.Add(low0 +1);
|
||||||
|
|
||||||
|
// Triangle 2 (back face)
|
||||||
|
indices.Add(low1 +1);
|
||||||
|
indices.Add(high1 +1);
|
||||||
|
indices.Add(high0 +1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh mesh = new Mesh();
|
||||||
|
mesh.name = "SVG_ExtrudedMesh";
|
||||||
|
mesh.indexFormat = (verts.Count > 65535) ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
|
||||||
|
mesh.SetVertices(verts);
|
||||||
|
mesh.SetTriangles(indices, 0);
|
||||||
|
|
||||||
mesh.RecalculateNormals();
|
mesh.RecalculateNormals();
|
||||||
mesh.RecalculateBounds();
|
mesh.RecalculateBounds();
|
||||||
|
|||||||
Reference in New Issue
Block a user