Henry00IS / ShapeEditor

2D Shape Editor for Unity Editor to create complex 3D meshes out of 2D shapes with RealtimeCSG support.
MIT License
98 stars 9 forks source link

[50$ Bounty] Create RealtimeCSG Brush from PolygonMesh #3

Open Henry00IS opened 1 year ago

Henry00IS commented 1 year ago

Background

In RealtimeCSG, brushes (convex meshes) are represented using the half-edge data structure (see here).

At the moment we convert all of the polygons of a generated convex mesh into planes and then feed those into a RealtimeCSG function that converts a set of planes into a brush. But converting planes (finding the intersection point of 3 planes, where sometimes there are infinite intersections) into a convex mesh is a difficult problem. That's why it often fails and has stability problems.

Your task

Build a function that can take a PolygonMesh (see here) and build a brush in the scene, by calling BrushFactory.CreateBrush (see here). This requires generating the half-edge connectivity data.

Files

The C# function has been prepared and only needs contents. Here is a special version of the 2D Shape Editor that you can add as a package:

com.aeternumgames.shapeeditor.zip

Here is the Bounty.cs file, place this file in your RealtimeCSG installation (in your Assets directory):

RealtimeCSGBounty.zip

Try extruding using "RealtimeCSG -> Create Bevel" and you will get a notification in the console window that leads you to the function that needs to be written.

public static CSGBrush CreateBrushFromPolygonMesh(Transform parent, string brushName, PolygonMesh mesh)
{
    var controlMesh = new ControlMesh();
    var shape = new Shape();

    Debug.Log("Bounty code goes here! Thank you! <3");
    return BrushFactory.CreateBrush(parent, brushName, controlMesh, shape);
}

Tips

Henry00IS commented 1 year ago

This is a functional prototype / proof of concept of Bounty.cs written by me. But it's not perfect, it will fail when the front or back scale of a "scaled extrude" is zero (a pyramid shape). It's also pretty hacky with LINQ.

using AeternumGames.ShapeEditor;
using InternalRealtimeCSG;
using RealtimeCSG.Components;
using RealtimeCSG.Legacy;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Shape = RealtimeCSG.Legacy.Shape;

namespace RealtimeCSG
{
    public static class Bounty
    {
        public static CSGBrush CreateBrushFromPolygonMesh(Transform parent, string brushName, PolygonMesh mesh)
        {
            foreach (var polygon in mesh)
            {
                polygon.Reverse();
            }

            var controlMesh = new ControlMesh();

            //// control mesh polygons:

            int edgeIndex = 0;
            controlMesh.Polygons = new Legacy.Polygon[mesh.Count];
            for (int i = 0; i < mesh.Count; i++)
            {
                controlMesh.Polygons[i] = new Legacy.Polygon(new int[mesh[i].Count], i);
                for (int j = 0; j < mesh[i].Count; j++)
                {
                    controlMesh.Polygons[i].EdgeIndices[j] = edgeIndex;
                    edgeIndex++;
                }
            }

            //// control mesh vertices:

            var vertices = new List<Vector3>();
            foreach (var polygon in mesh)
            {
                foreach (var vertex in polygon)
                {
                    vertices.Add(vertex.position);
                }
            }
            controlMesh.Vertices = vertices.Distinct().ToArray();

            //// control mesh half edges:

            var edges = new List<HalfEdge>();
            for (short i = 0; i < mesh.Count; i++)
            {
                var polygon = mesh[i];
                for (int j = 0; j < polygon.Count; j++)
                {
                    var vertex = polygon[j];
                    var vertexIndex = (short)vertices.FindIndex(v => v.EqualsWithEpsilon5(vertex.position));
                    edges.Add(new HalfEdge { PolygonIndex = i, VertexIndex = vertexIndex, TwinIndex = -1 });
                }
            }
            controlMesh.Edges = edges.ToArray();

            //// control mesh half edge twin association:

            var twn = 0;
            for (short i = 0; i < mesh.Count; i++)
            {
                var polygon = mesh[i];
                for (int j = 0; j < polygon.Count; j++)
                {
                    var previous = polygon.PreviousVertex(j);
                    var current = polygon[j];

                    var cnt = 0;
                    for (short z = 0; z < mesh.Count; z++)
                    {
                        var polygon2 = mesh[z];

                        for (int k = 0; k < polygon2.Count; k++)
                        {
                            cnt++;
                            if (i == z) continue;
                            var previous2 = polygon2.PreviousVertex(k);
                            var current2 = polygon2[k];

                            if (previous2.position.EqualsWithEpsilon5(current.position) && current2.position.EqualsWithEpsilon5(previous.position))
                            {
                                controlMesh.Edges[twn].TwinIndex = cnt - 1;
                            }
                        }
                    }

                    twn++;
                }
            }

            //// shape:

            var shape = new Shape();
            var material = CSGSettings.DefaultMaterial;
            var polygonCount = mesh.Count;

            shape.Surfaces = new Surface[polygonCount];
            shape.TexGens = new TexGen[polygonCount];
            shape.TexGenFlags = new TexGenFlags[polygonCount];
            for (int i = 0; i < polygonCount; i++)
            {
                shape.Surfaces[i].TexGenIndex = i;
                shape.Surfaces[i].Plane = GeometryUtility.CalcPolygonPlane(controlMesh, (short)i);
                GeometryUtility.CalculateTangents(shape.Surfaces[i].Plane.normal, out shape.Surfaces[i].Tangent, out shape.Surfaces[i].BiNormal);
                shape.TexGens[i].RenderMaterial = material;
                shape.TexGens[i].Scale = MathConstants.oneVector3;
                shape.TexGenFlags[i] = CSGSettings.DefaultTexGenFlags;
            }

            ShapeUtility.EnsureInitialized(shape);
            controlMesh.Valid = ControlMeshUtility.Validate(controlMesh, shape);

            if (controlMesh.Valid)
            {
                Debug.Log("The control mesh is valid!");
            }

            return BrushFactory.CreateBrush(parent, brushName, controlMesh, shape);
        }
    }
}