/// <summary> /// Write the csg into a mesh. /// </summary> internal static void Tessellate(this Csg.Solid csg, ref Mesh mesh) { foreach (var p in csg.Polygons) { p.AddToMesh(ref mesh); } mesh.ComputeNormals(); }
/// <summary> /// Write the csg into a mesh. /// </summary> internal static void Tessellate(this Csg.Solid csg, ref Mesh mesh, Transform transform = null, Color color = default(Color)) { foreach (var p in csg.Polygons) { p.AddToMesh(ref mesh); } mesh.ComputeNormals(); }
/// <summary> /// Triangulate this csg and pack the triangulated data into buffers /// appropriate for use with gltf. /// </summary> internal static GraphicsBuffers Tessellate(this Csg.Solid csg) { var buffers = new GraphicsBuffers(); ushort iCursor = 0; (Vector3 U, Vector3 V)basis; const float SEARCH_RADIUS = 0.001f; // Setup the octree to fit around the csg. // This requires one expensive initialization. var verts = csg.Polygons.SelectMany(p => p.Vertices).Select(v => v.Pos.ToElementsVector()).ToArray(); var bounds = new BBox3(verts); var center = bounds.Center(); var origin = new Point((float)center.X, (float)center.Y, (float)center.Z); var size = (float)bounds.Max.DistanceTo(bounds.Min); var octree = new PointOctree <(Vector3 position, Vector3 normal, ushort index)>(size, origin, SEARCH_RADIUS); foreach (var p in csg.Polygons) { var vertexIndices = new ushort[p.Vertices.Count]; var a = p.Vertices[0].Pos.ToElementsVector(); var b = p.Vertices[1].Pos.ToElementsVector(); var c = p.Vertices[2].Pos.ToElementsVector(); basis = ComputeBasisAndNormalForTriangle(a, b, c, out Vector3 normal); // Anything with 3 vertices is a triangle. Manually // tesselate triangles. For everything else, use // the tessellator. if (p.Vertices.Count > 2 && p.Vertices.Count <= 3) { for (var i = 0; i < p.Vertices.Count; i++) { var v = p.Vertices[i]; var op = new Point((float)v.Pos.X, (float)v.Pos.Y, (float)v.Pos.Z); var ep = v.Pos.ToElementsVector(); if (TryGetExistingVertex(op, ep, octree, normal, SEARCH_RADIUS, out ushort vertexIndex)) { vertexIndices[i] = vertexIndex; continue; } vertexIndices[i] = iCursor; iCursor++; var uu = basis.U.Dot(ep); var vv = basis.V.Dot(ep); buffers.AddVertex(ep, normal, new UV(uu, vv)); octree.Add((ep, normal, vertexIndices[i]), op);
/// <summary> /// Get the computed csg solid. /// The csg is centered on the origin by default. /// </summary> /// <param name="transformed">Should the csg be transformed by the element's transform?</param> internal Csg.Solid GetFinalCsgFromSolids(bool transformed = false) { // To properly compute csgs, all solid operation csgs need // to be transformed into their final position. Then the csgs // can be computed and by default the final csg will have the inverse of the // geometric element's transform applied to "reset" it. // The transforms applied to each node in the glTF will then // ensure that the elements are correctly transformed. Csg.Solid csg = new Csg.Solid(); var solids = Representation.SolidOperations.Where(op => op.IsVoid == false) .Select(op => TransformedSolidOperation(op)) .ToArray(); var voids = Representation.SolidOperations.Where(op => op.IsVoid == true) .Select(op => TransformedSolidOperation(op)) .ToArray(); if (this is IHasOpenings) { var openingContainer = (IHasOpenings)this; voids = voids.Concat(openingContainer.Openings.SelectMany(o => o.Representation.SolidOperations .Where(op => op.IsVoid == true) .Select(op => op._csg.Transform(o.Transform.ToMatrix4x4())))).ToArray(); } // Don't try CSG booleans if we only have one one solid. if (solids.Count() == 1) { csg = solids.First(); } else { csg = csg.Union(solids); } if (voids.Count() > 0) { csg = csg.Substract(voids); } if (Transform == null || transformed) { return(csg); } else { var inverse = new Transform(Transform); inverse.Invert(); csg = csg.Transform(inverse.ToMatrix4x4()); return(csg); } }
public CsgBenchmarks() { var line = new Line(new Vector3(0, 0, 0), new Vector3(10, 0, 5)); var profile = Polygon.Rectangle(Units.InchesToMeters(10), Units.InchesToMeters(20)); _beam = new Beam(line, profile, BuiltInMaterials.Steel); for (var i = 0.0; i <= 1.0; i += 1.0 / (double)NumberOfHoles) { var t = line.TransformAt(i); var lt = new Transform(t.Origin, t.ZAxis, t.XAxis.Negate()); lt.Move(lt.ZAxis * -0.5); var hole = new Extrude(Polygon.Rectangle(0.1, 0.1), 1.0, Vector3.ZAxis, true) { LocalTransform = lt }; _beam.Representation.SolidOperations.Add(hole); } _csg = _beam.GetFinalCsgFromSolids(); }
public static MeshData FromSolid(Csg.Solid solid, string name) { var vertices = solid.Polygons .SelectMany(polygon => polygon.Vertices) .Select(vertex => new Vector3((float)vertex.Pos.X, (float)vertex.Pos.Y, (float)vertex.Pos.Z) ).ToArray(); var normals = solid.Polygons.SelectMany(polygon => { var faceNormal = polygon.Plane.Normal; var normal = new Vector3( (float)faceNormal.X, (float)faceNormal.Y, (float)faceNormal.Z ); return(polygon.Vertices.Select(_ => normal)); }).ToArray(); var vi = 0; var indices = solid.Polygons.SelectMany(face => { var faceIndices = new List <ushort>(); for (var v = 2; v < face.Vertices.Count; v++) { faceIndices.Add((ushort)(vi)); faceIndices.Add((ushort)(vi + v - 1)); faceIndices.Add((ushort)(vi + v)); } vi += face.Vertices.Count; return(faceIndices); }).ToArray(); var builder = new MeshBuilder().WithFrontFaceClockwise(false); for (int i = 0; i < vertices.Length; i++) { builder.WithVertex(new VertexPositionNormal(vertices[i], normals[i])); } builder.WithIndices(indices); return(builder.Build <VertexPositionNormal>(name)); }
/// <summary> /// Triangulate this csg and pack the triangulated data into buffers /// appropriate for use with gltf. /// </summary> internal static void Tessellate(this Csg.Solid csg, out byte[] vertexBuffer, out byte[] indexBuffer, out byte[] normalBuffer, out byte[] colorBuffer, out byte[] uvBuffer, out double[] vmax, out double[] vmin, out double[] nmin, out double[] nmax, out float[] cmin, out float[] cmax, out ushort imin, out ushort imax, out double[] uvmin, out double[] uvmax) { var tessellations = new Tess[csg.Polygons.Count]; var fi = 0; foreach (var p in csg.Polygons) { var tess = new Tess(); tess.NoEmptyPolygons = true; tess.AddContour(p.Vertices.ToContourVertices()); tess.Tessellate(WindingRule.Positive, LibTessDotNet.Double.ElementType.Polygons, 3); tessellations[fi] = tess; fi++; } var floatSize = sizeof(float); var ushortSize = sizeof(ushort); var vertexCount = tessellations.Sum(t => t.VertexCount); var indexCount = tessellations.Sum(t => t.Elements.Length); vertexBuffer = new byte[vertexCount * floatSize * 3]; normalBuffer = new byte[vertexCount * floatSize * 3]; indexBuffer = new byte[indexCount * ushortSize]; uvBuffer = new byte[vertexCount * floatSize * 2]; // Vertex colors are not used in this context currently. colorBuffer = new byte[0]; cmin = new float[0]; cmax = new float[0]; vmax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; vmin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; nmin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; nmax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; uvmin = new double[2] { double.MaxValue, double.MaxValue }; uvmax = new double[2] { double.MinValue, double.MinValue }; imax = ushort.MinValue; imin = ushort.MaxValue; var vi = 0; var ii = 0; var uvi = 0; var iCursor = 0; (Vector3 U, Vector3 V)basis; for (var i = 0; i < tessellations.Length; i++) { var tess = tessellations[i]; if (tess.ElementCount == 0) { continue; } var a = tess.Vertices[tess.Elements[0]].Position.ToVector3(); var b = tess.Vertices[tess.Elements[1]].Position.ToVector3(); var c = tess.Vertices[tess.Elements[2]].Position.ToVector3(); var tmp = (b - a).Unitized(); var n = tmp.Cross(c - a).Unitized(); // Calculate the texture space basis vectors // from the first triangle. This is acceptable // for planar faces. // TODO: Update this when we support non-planar faces. // https://gamedev.stackexchange.com/questions/172352/finding-texture-coordinates-for-plane basis = n.ComputeDefaultBasisVectors(); for (var j = 0; j < tess.Vertices.Length; j++) { var v = tess.Vertices[j]; var p = v.Position.ToVector3(); System.Buffer.BlockCopy(BitConverter.GetBytes((float)p.X), 0, vertexBuffer, vi, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)p.Y), 0, vertexBuffer, vi + floatSize, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)p.Z), 0, vertexBuffer, vi + 2 * floatSize, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)n.X), 0, normalBuffer, vi, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)n.Y), 0, normalBuffer, vi + floatSize, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)n.Z), 0, normalBuffer, vi + 2 * floatSize, floatSize); var uu = basis.U.Dot(p); var vv = basis.V.Dot(p); System.Buffer.BlockCopy(BitConverter.GetBytes((float)uu), 0, uvBuffer, uvi, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)vv), 0, uvBuffer, uvi + floatSize, floatSize); uvi += 2 * floatSize; vi += 3 * floatSize; vmax[0] = Math.Max(vmax[0], v.Position.X); vmax[1] = Math.Max(vmax[1], v.Position.Y); vmax[2] = Math.Max(vmax[2], v.Position.Z); vmin[0] = Math.Min(vmin[0], v.Position.X); vmin[1] = Math.Min(vmin[1], v.Position.Y); vmin[2] = Math.Min(vmin[2], v.Position.Z); nmax[0] = Math.Max(nmax[0], n.X); nmax[1] = Math.Max(nmax[1], n.Y); nmax[2] = Math.Max(nmax[2], n.Z); nmin[0] = Math.Min(nmin[0], n.X); nmin[1] = Math.Min(nmin[1], n.Y); nmin[2] = Math.Min(nmin[2], n.Z); uvmax[0] = Math.Max(uvmax[0], uu); uvmax[1] = Math.Max(uvmax[1], vv); uvmin[0] = Math.Min(uvmin[0], uu); uvmin[1] = Math.Min(uvmin[1], vv); } for (var k = 0; k < tess.Elements.Length; k++) { var t = tess.Elements[k]; var index = (ushort)(t + iCursor); System.Buffer.BlockCopy(BitConverter.GetBytes(index), 0, indexBuffer, ii, ushortSize); imax = Math.Max(imax, index); imin = Math.Min(imin, index); ii += ushortSize; } iCursor = imax + 1; } }
/// <summary> /// Triangulate this csg and pack the triangulated data into buffers /// appropriate for use with gltf. /// </summary> internal static void Tessellate(this Csg.Solid csg, out byte[] vertexBuffer, out byte[] indexBuffer, out byte[] normalBuffer, out byte[] colorBuffer, out byte[] uvBuffer, out double[] vmax, out double[] vmin, out double[] nmin, out double[] nmax, out float[] cmin, out float[] cmax, out ushort imin, out ushort imax, out double[] uvmin, out double[] uvmax) { var tessellations = new Tess[csg.Polygons.Count]; var fi = 0; foreach (var p in csg.Polygons) { var tess = new Tess(); tess.NoEmptyPolygons = true; tess.AddContour(p.Vertices.ToContourVertices()); tess.Tessellate(WindingRule.Positive, LibTessDotNet.Double.ElementType.Polygons, 3); tessellations[fi] = tess; fi++; } var floatSize = sizeof(float); var ushortSize = sizeof(ushort); var vertexCount = tessellations.Sum(t => t.VertexCount); var indexCount = tessellations.Sum(t => t.Elements.Length); vertexBuffer = new byte[vertexCount * floatSize * 3]; normalBuffer = new byte[vertexCount * floatSize * 3]; indexBuffer = new byte[indexCount * ushortSize]; uvBuffer = new byte[vertexCount * floatSize * 2]; // Vertex colors are not used in this context currently. colorBuffer = new byte[0]; cmin = new float[0]; cmax = new float[0]; vmax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; vmin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; nmin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; nmax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; // TODO: Set this properly when solids get UV coordinates. uvmin = new double[2] { 0, 0 }; uvmax = new double[2] { 0, 0 }; imax = ushort.MinValue; imin = ushort.MaxValue; var vi = 0; var ii = 0; var uvi = 0; var iCursor = 0; for (var i = 0; i < tessellations.Length; i++) { var tess = tessellations[i]; if (tess.ElementCount == 0) { continue; } var a = tess.Vertices[tess.Elements[0]].Position.ToVector3(); var b = tess.Vertices[tess.Elements[1]].Position.ToVector3(); var c = tess.Vertices[tess.Elements[2]].Position.ToVector3(); var n = (b - a).Cross(c - a).Unitized(); for (var j = 0; j < tess.Vertices.Length; j++) { var v = tess.Vertices[j]; System.Buffer.BlockCopy(BitConverter.GetBytes((float)v.Position.X), 0, vertexBuffer, vi, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)v.Position.Y), 0, vertexBuffer, vi + floatSize, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)v.Position.Z), 0, vertexBuffer, vi + 2 * floatSize, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)n.X), 0, normalBuffer, vi, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)n.Y), 0, normalBuffer, vi + floatSize, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes((float)n.Z), 0, normalBuffer, vi + 2 * floatSize, floatSize); // TODO: Update Solids to use something other than UV = {0,0}. System.Buffer.BlockCopy(BitConverter.GetBytes(0f), 0, uvBuffer, uvi, floatSize); System.Buffer.BlockCopy(BitConverter.GetBytes(0f), 0, uvBuffer, uvi + floatSize, floatSize); uvi += 2 * floatSize; vi += 3 * floatSize; vmax[0] = Math.Max(vmax[0], v.Position.X); vmax[1] = Math.Max(vmax[1], v.Position.Y); vmax[2] = Math.Max(vmax[2], v.Position.Z); vmin[0] = Math.Min(vmin[0], v.Position.X); vmin[1] = Math.Min(vmin[1], v.Position.Y); vmin[2] = Math.Min(vmin[2], v.Position.Z); nmax[0] = Math.Max(nmax[0], n.X); nmax[1] = Math.Max(nmax[1], n.Y); nmax[2] = Math.Max(nmax[2], n.Z); nmin[0] = Math.Min(nmin[0], n.X); nmin[1] = Math.Min(nmin[1], n.Y); nmin[2] = Math.Min(nmin[2], n.Z); // uvmax[0] = Math.Max(uvmax[0], 0); // uvmax[1] = Math.Max(uvmax[1], 0); // uvmin[0] = Math.Min(uvmin[0], 0); // uvmin[1] = Math.Min(uvmin[1], 0); } for (var k = 0; k < tess.Elements.Length; k++) { var t = tess.Elements[k]; var index = (ushort)(t + iCursor); System.Buffer.BlockCopy(BitConverter.GetBytes(index), 0, indexBuffer, ii, ushortSize); imax = Math.Max(imax, index); imin = Math.Min(imin, index); ii += ushortSize; } iCursor = imax + 1; } }
/// <summary> /// Construct a CsgTessellationTargetProvider. /// </summary> /// <param name="csg"></param> public CsgTessellationTargetProvider(Csg.Solid csg) { this.csg = csg; }
/// <summary> /// Triangulate this csg and pack the triangulated data into buffers /// appropriate for use with gltf. /// </summary> internal static GraphicsBuffers Tessellate(this Csg.Solid csg, bool mergeVertices = false, Func <(Vector3, Vector3, UV, Color), (Vector3, Vector3, UV, Color)> modifyVertexAttributes = null)