mirror of
https://github.com/appen-isen/jeu-sans-image.git
synced 2026-03-18 21:50:42 +01:00
Add: Polygon simplification
Polygons are simplified before tesselation
This commit is contained in:
@@ -15,7 +15,7 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
Material editorMaterial;
|
Material editorMaterial;
|
||||||
float pixelsPerUnit = 100.0f;
|
float pixelsPerUnit = 100.0f;
|
||||||
Transform parentTransform;
|
Transform parentTransform;
|
||||||
float meshScale = 1f;
|
float meshScale = 0.1f;
|
||||||
VectorUtils.TessellationOptions tessOptions = new VectorUtils.TessellationOptions() {
|
VectorUtils.TessellationOptions tessOptions = new VectorUtils.TessellationOptions() {
|
||||||
StepDistance = 1.0f,
|
StepDistance = 1.0f,
|
||||||
MaxCordDeviation = 0.5f,
|
MaxCordDeviation = 0.5f,
|
||||||
@@ -24,6 +24,9 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
};
|
};
|
||||||
Quaternion meshRotation = Quaternion.identity;
|
Quaternion meshRotation = Quaternion.identity;
|
||||||
|
|
||||||
|
float polygonSimplificationTolerance = 0.5f;
|
||||||
|
bool enablePolygonSimplification = true;
|
||||||
|
|
||||||
[SerializeField] private ColorFolderMap colorFolderMap;
|
[SerializeField] private ColorFolderMap colorFolderMap;
|
||||||
|
|
||||||
[MenuItem("Tools/SVG → Flat Mesh Regions")]
|
[MenuItem("Tools/SVG → Flat Mesh Regions")]
|
||||||
@@ -49,6 +52,14 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
tessOptions.MaxTanAngleDeviation = EditorGUILayout.FloatField(new GUIContent("Max Tan Angle Deviation", "From manual: The maximum angle (in degrees) between the curve tangent and the next point after which more tessellation will be generated"), tessOptions.MaxTanAngleDeviation);
|
tessOptions.MaxTanAngleDeviation = EditorGUILayout.FloatField(new GUIContent("Max Tan Angle Deviation", "From manual: The maximum angle (in degrees) between the curve tangent and the next point after which more tessellation will be generated"), tessOptions.MaxTanAngleDeviation);
|
||||||
tessOptions.SamplingStepSize = EditorGUILayout.FloatField(new GUIContent("Sampling Step Size", "From manual: The number of samples used internally to evaluate the curves. More samples = higher quality. Should be between 0 and 1 (inclusive)"), tessOptions.SamplingStepSize);
|
tessOptions.SamplingStepSize = EditorGUILayout.FloatField(new GUIContent("Sampling Step Size", "From manual: The number of samples used internally to evaluate the curves. More samples = higher quality. Should be between 0 and 1 (inclusive)"), tessOptions.SamplingStepSize);
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
EditorGUILayout.LabelField("Polygon Simplification", EditorStyles.boldLabel);
|
||||||
|
enablePolygonSimplification = EditorGUILayout.Toggle(new GUIContent("Enable Polygon Simplification", "Reduce points in polygons before tessellation for minimal triangles."), enablePolygonSimplification);
|
||||||
|
if (enablePolygonSimplification)
|
||||||
|
{
|
||||||
|
polygonSimplificationTolerance = EditorGUILayout.FloatField(new GUIContent("Simplification Tolerance", "Higher = fewer points, lower = more detail. Typical range: 0.1 - 2.0"), polygonSimplificationTolerance);
|
||||||
|
}
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
EditorGUILayout.LabelField("Map rotation options", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Map rotation options", EditorStyles.boldLabel);
|
||||||
meshRotation = Quaternion.Euler(EditorGUILayout.Vector3Field(new GUIContent("Mesh Rotation (degrees)", "Rotation to apply to the generated meshes"), meshRotation.eulerAngles));
|
meshRotation = Quaternion.Euler(EditorGUILayout.Vector3Field(new GUIContent("Mesh Rotation (degrees)", "Rotation to apply to the generated meshes"), meshRotation.eulerAngles));
|
||||||
@@ -151,22 +162,103 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tesselate a list of SceneNodeShapeEntry into VectorUtils.Geometry list
|
// Tesselate a list of SceneNodeShapeEntry into VectorUtils.Geometry list
|
||||||
List<VectorUtils.Geometry> TesselateIntoGeometries(List<SceneNodeShapeEntry> entries) {
|
// Simplify polygon points using Ramer–Douglas–Peucker algorithm
|
||||||
// Build a temporary scene that contains all these shapes combined (preserving transforms)
|
List<Vector2> SimplifyPolygon(List<Vector2> points, float tolerance)
|
||||||
|
{
|
||||||
|
if (points == null || points.Count < 3)
|
||||||
|
return points;
|
||||||
|
|
||||||
|
bool[] keep = new bool[points.Count];
|
||||||
|
keep[0] = true;
|
||||||
|
keep[points.Count - 1] = true;
|
||||||
|
|
||||||
|
void Simplify(int first, int last)
|
||||||
|
{
|
||||||
|
float maxDist = 0f;
|
||||||
|
int index = 0;
|
||||||
|
Vector2 a = points[first];
|
||||||
|
Vector2 b = points[last];
|
||||||
|
for (int i = first + 1; i < last; i++)
|
||||||
|
{
|
||||||
|
float dist = DistanceToSegment(points[i], a, b);
|
||||||
|
if (dist > maxDist)
|
||||||
|
{
|
||||||
|
maxDist = dist;
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (maxDist > tolerance)
|
||||||
|
{
|
||||||
|
keep[index] = true;
|
||||||
|
Simplify(first, index);
|
||||||
|
Simplify(index, last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Simplify(0, points.Count - 1);
|
||||||
|
|
||||||
|
List<Vector2> result = new List<Vector2>();
|
||||||
|
for (int i = 0; i < points.Count; i++)
|
||||||
|
if (keep[i]) result.Add(points[i]);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance from point p to segment ab
|
||||||
|
float DistanceToSegment(Vector2 p, Vector2 a, Vector2 b)
|
||||||
|
{
|
||||||
|
float l2 = (b - a).sqrMagnitude;
|
||||||
|
if (l2 == 0f) return (p - a).magnitude;
|
||||||
|
float t = Mathf.Clamp01(Vector2.Dot(p - a, b - a) / l2);
|
||||||
|
Vector2 proj = a + t * (b - a);
|
||||||
|
return (p - proj).magnitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<VectorUtils.Geometry> TesselateIntoGeometries(List<SceneNodeShapeEntry> entries)
|
||||||
|
{
|
||||||
Scene tmpScene = new Scene();
|
Scene tmpScene = new Scene();
|
||||||
tmpScene.Root = new SceneNode();
|
tmpScene.Root = new SceneNode();
|
||||||
tmpScene.Root.Children = new List<SceneNode>();
|
tmpScene.Root.Children = new List<SceneNode>();
|
||||||
|
|
||||||
foreach (var entry in entries) {
|
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() {
|
Shape shape = entry.Shape;
|
||||||
Transform = entry.Node.Transform, // keep transform
|
// Only simplify polygons (ignore curves for now)
|
||||||
Shapes = new List<Shape>() { entry.Shape }
|
if (shape.Contours != null && shape.Contours.Length == 1 && shape.Contours[0].Segments.Length > 2)
|
||||||
|
{
|
||||||
|
// Extract points from Bezier segments (P0)
|
||||||
|
List<Vector2> pts = new List<Vector2>();
|
||||||
|
foreach (var seg in shape.Contours[0].Segments)
|
||||||
|
pts.Add(seg.P0);
|
||||||
|
// If the shape is almost a polygon (all segments are lines or nearly lines)
|
||||||
|
bool almostPolygon = true;
|
||||||
|
foreach (var seg in shape.Contours[0].Segments)
|
||||||
|
{
|
||||||
|
if ((seg.P0 - seg.P1).magnitude > 0.01f || (seg.P0 - seg.P2).magnitude > 0.01f)
|
||||||
|
{
|
||||||
|
almostPolygon = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (almostPolygon && enablePolygonSimplification)
|
||||||
|
{
|
||||||
|
// Simplify polygon
|
||||||
|
pts = SimplifyPolygon(pts, polygonSimplificationTolerance);
|
||||||
|
// Rebuild contour with simplified points
|
||||||
|
BezierPathSegment[] newSegs = new BezierPathSegment[pts.Count];
|
||||||
|
for (int i = 0; i < pts.Count; i++)
|
||||||
|
{
|
||||||
|
newSegs[i] = new BezierPathSegment { P0 = pts[i], P1 = pts[i], P2 = pts[i] };
|
||||||
|
}
|
||||||
|
shape.Contours[0].Segments = newSegs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SceneNode copyNode = new SceneNode()
|
||||||
|
{
|
||||||
|
Transform = entry.Node.Transform,
|
||||||
|
Shapes = new List<Shape>() { shape }
|
||||||
};
|
};
|
||||||
tmpScene.Root.Children.Add(copyNode);
|
tmpScene.Root.Children.Add(copyNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tessellate the tmpScene
|
|
||||||
return VectorUtils.TessellateScene(tmpScene, tessOptions);
|
return VectorUtils.TessellateScene(tmpScene, tessOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,9 +387,9 @@ public class SvgToFlatMeshEditor : EditorWindow
|
|||||||
|
|
||||||
// Add indices (triangles)
|
// Add indices (triangles)
|
||||||
for (int i = 0; i < g.Indices.Length; i += 3) {
|
for (int i = 0; i < g.Indices.Length; i += 3) {
|
||||||
int i1 = baseIndex + g.Indices[i];
|
int i1 = (baseIndex + g.Indices[i]) % g.Vertices.Length;
|
||||||
int i2 = baseIndex + g.Indices[i + 1];
|
int i2 = (baseIndex + g.Indices[(i + 1)% g.Indices.Length]) % g.Vertices.Length;
|
||||||
int i3 = baseIndex + g.Indices[i + 2];
|
int i3 = (baseIndex + g.Indices[(i + 2)% g.Indices.Length]) % g.Vertices.Length;
|
||||||
if (!IsClockwise( g.Vertices[i1], g.Vertices[i2], g.Vertices[i3] ))
|
if (!IsClockwise( g.Vertices[i1], g.Vertices[i2], g.Vertices[i3] ))
|
||||||
{
|
{
|
||||||
// Add triangle with reversed winding
|
// Add triangle with reversed winding
|
||||||
|
|||||||
Reference in New Issue
Block a user