diff --git a/CHANGELOG.md b/CHANGELOG.md index e391a13da..733f10fc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## 1.2.0 ### Added +- `Polygon(IList @vertices, bool disableValidation = false)` +- `Polygon(bool disableValidation, params Vector3[] vertices)` +- `Polyline(IList @vertices, bool disableValidation = false)` +- `Polyline(bool disableValidation, params Vector3[] vertices)` - `Mesh.Intersects(Ray)` (same as `Ray.Intersects(Mesh)`) - `Ray.NearbyPoints()` - `PointOctree` @@ -21,6 +25,7 @@ - Fixed a bug where `Polyline.Frames` would return inconsistently-oriented transforms. - `Obstacle.FromBox` works properly with `AdaptiveGrid` transformation. - `AdaptiveGrid.SubtractObstacle` worked incorrectly in `AdaptiveGrid.Boundary` had elevation. +- #805 ## 1.1.0 @@ -79,11 +84,6 @@ - `ContinuousDimension` - `Vector3.AreCollinearByAngle(Vector3 a, Vector3 b, Vector3 c, double tolerance)` - -### Fixed - -- #805 - ### Fixed - `Line.IsCollinear(Line line)` would return `false` if lines are close to each other but not collinear @@ -92,11 +92,13 @@ - `Line.GetUParameter(Vector 3)` - calculate U parameter for point on line - `Line.MergeCollinearLine(Line line)` creates new line containing all four collinear vertices - `Line.Projected(Plane plane)` create new line projected onto plane +- `Profile.Split` would sometimes fail if the profile being split contained voids. ### Changed - Simplified `IEnumerable.ToGraphicsBuffers()` - `TryToGraphicsBuffers` is now public +- `Solid SweepFaceAlongCurve` now has an additional parameter, `profileRotation`, which enables the caller to pass a profile rotation into sweep creation. ## 1.0.0 diff --git a/Elements.Benchmarks/Trace.cs b/Elements.Benchmarks/Trace.cs index 5aaf7e45c..4d0cd5b02 100644 --- a/Elements.Benchmarks/Trace.cs +++ b/Elements.Benchmarks/Trace.cs @@ -22,6 +22,7 @@ public void TraceModelCreation() } [EventPipeProfiler(EventPipeProfile.CpuSampling)] + [MemoryDiagnoser] [SimpleJob] public class TraceGltfSerialization { diff --git a/Elements/src/Geometry/Bezier.cs b/Elements/src/Geometry/Bezier.cs index 00a3f1c94..1d40eaaaf 100644 --- a/Elements/src/Geometry/Bezier.cs +++ b/Elements/src/Geometry/Bezier.cs @@ -74,13 +74,19 @@ public override BBox3 Bounds() /// /// /// - /// - public override Transform[] Frames(double startSetback = 0, double endSetback = 0) + /// + public override Transform[] Frames(double startSetback = 0, + double endSetback = 0, + double additionalRotation = 0.0) { var transforms = new Transform[_samples + 1]; for (var i = 0; i <= _samples; i++) { transforms[i] = TransformAt(i * 1.0 / _samples); + if (additionalRotation != 0.0) + { + transforms[i].RotateAboutPoint(transforms[i].Origin, transforms[i].ZAxis, additionalRotation); + } } return transforms; } diff --git a/Elements/src/Geometry/Circle.cs b/Elements/src/Geometry/Circle.cs index 46805a5e9..3a1e2d296 100644 --- a/Elements/src/Geometry/Circle.cs +++ b/Elements/src/Geometry/Circle.cs @@ -34,7 +34,7 @@ public Polygon ToPolygon(int divisions = 10) { pts[i] = this.PointAt((double)i / (double)divisions); } - return new Polygon(pts); + return new Polygon(pts, true); } } } \ No newline at end of file diff --git a/Elements/src/Geometry/CsgExtensions.cs b/Elements/src/Geometry/CsgExtensions.cs index 8cc3af9d4..ea1e22fb4 100644 --- a/Elements/src/Geometry/CsgExtensions.cs +++ b/Elements/src/Geometry/CsgExtensions.cs @@ -25,10 +25,9 @@ internal static void Tessellate(this Csg.Solid csg, ref Mesh mesh) /// appropriate for use with gltf. /// internal static GraphicsBuffers Tessellate(this Csg.Solid csg, - bool mergeVertices = false, Func<(Vector3, Vector3, UV, Color), (Vector3, Vector3, UV, Color)> modifyVertexAttributes = null) { - return Tessellate(new[] { csg }, mergeVertices, modifyVertexAttributes); + return Tessellate(new[] { csg }, modifyVertexAttributes); } /// @@ -36,14 +35,9 @@ internal static GraphicsBuffers Tessellate(this Csg.Solid csg, /// buffers appropriate for use with gltf. /// internal static GraphicsBuffers Tessellate(this Csg.Solid[] csgs, - bool mergeVertices = false, Func<(Vector3, Vector3, UV, Color), (Vector3, Vector3, UV, Color)> modifyVertexAttributes = null) { - var buffers = new GraphicsBuffers(); - - Tessellation.Tessellation.Tessellate(csgs.Select(csg => new CsgTessellationTargetProvider(csg)), - buffers, - mergeVertices, + var buffers = Tessellation.Tessellation.Tessellate(csgs.Select(csg => new CsgTessellationTargetProvider(csg)), modifyVertexAttributes); return buffers; } diff --git a/Elements/src/Geometry/Curve.cs b/Elements/src/Geometry/Curve.cs index dcd5dce44..3081063df 100644 --- a/Elements/src/Geometry/Curve.cs +++ b/Elements/src/Geometry/Curve.cs @@ -27,14 +27,21 @@ public abstract partial class Curve : ICurve, ITransformable /// /// The offset parameter from the start of the curve. /// The offset parameter from the end of the curve. + /// An additional rotation of the frame at each point. /// A collection of transforms. - public virtual Transform[] Frames(double startSetback = 0.0, double endSetback = 0.0) + public virtual Transform[] Frames(double startSetback = 0.0, + double endSetback = 0.0, + double additionalRotation = 0.0) { var parameters = GetSampleParameters(startSetback, endSetback); var transforms = new Transform[parameters.Length]; for (var i = 0; i < parameters.Length; i++) { transforms[i] = TransformAt(parameters[i]); + if (additionalRotation != 0.0) + { + transforms[i].RotateAboutPoint(transforms[i].Origin, transforms[i].ZAxis, additionalRotation); + } } return transforms; } diff --git a/Elements/src/Geometry/GraphicsBuffers.cs b/Elements/src/Geometry/GraphicsBuffers.cs index 35ef48918..a8e9f6448 100644 --- a/Elements/src/Geometry/GraphicsBuffers.cs +++ b/Elements/src/Geometry/GraphicsBuffers.cs @@ -4,101 +4,81 @@ namespace Elements.Geometry { /// - /// A generic container for graphics data. This is broken out primarily to facilitate - /// simpler testing of graphics buffers. + /// A container for graphics data. + /// The buffers used in this class align with webgl requirements. /// - internal interface IGraphicsBuffers + public class GraphicsBuffers : IGraphicsBuffers { /// - /// Add a vertex to the graphics buffers. - /// - /// The position of the vertex. - /// The normal of the vertex. - /// The UV of the vertex. - /// The vertex color. - void AddVertex(Vector3 position, Vector3 normal, UV uv, Color? color = null); - - /// - /// Add a vertex to the graphics buffers. + /// The number of vertices represented by the buffer. /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - void AddVertex(double x, double y, double z, double nx, double ny, double nz, double u, double v, Color? color = null); + public int VertexCount + { + get { return this.Vertices.Count / sizeof(float) / 3; } + } /// - /// Add an index to the graphics buffers. + /// The number of facets represeted by the buffer. /// - /// The index to add. - void AddIndex(ushort index); - } + public int FacetCount + { + get { return this.Indices.Count / sizeof(ushort) / 3; } + } - /// - /// A container for graphics data. - /// The buffers used in this class align with webgl requirements. - /// - public class GraphicsBuffers : IGraphicsBuffers - { /// /// A collection of vertex positions stored as sequential bytes. /// - public List Vertices { get; } + public List Vertices { get; private set; } /// /// A collection of indices stored as sequential bytes. /// - public List Indices { get; } + public List Indices { get; private set; } /// /// A collection of sequential normal values stored as sequential bytes. /// - public List Normals { get; } + public List Normals { get; private set; } /// /// A collection of sequential color values stored as sequential bytes. /// - public List Colors { get; } + public List Colors { get; private set; } /// /// A collection of UV values stored as sequential bytes. /// - public List UVs { get; } + public List UVs { get; private set; } /// /// The maximum of the axis-aligned bounding box of the data as [x,y,z]. /// - public double[] VMax { get; } + public double[] VMax { get; private set; } /// /// The minimum of the axis-aligned bounding box of the data as [x,y,z]. /// - public double[] VMin { get; } + public double[] VMin { get; private set; } /// /// The minimum normal of the data as [x,y,z]. /// - public double[] NMin { get; } + public double[] NMin { get; private set; } /// /// The maximum normal of the data as [x,y,z]. /// - public double[] NMax { get; } + public double[] NMax { get; private set; } /// /// The minimum color value as [r,g,b]. /// - public double[] CMin { get; } + public double[] CMin { get; private set; } /// /// The maximum color value as [r,g,b]. /// - public double[] CMax { get; } + public double[] CMax { get; private set; } /// /// The maximum index value. @@ -113,36 +93,19 @@ public class GraphicsBuffers : IGraphicsBuffers /// /// The maximum UV value as [u,v]. /// - public double[] UVMin { get; } + public double[] UVMin { get; private set; } /// /// The maximum UV value as [u,v]. /// - public double[] UVMax { get; } + public double[] UVMax { get; private set; } /// /// Construct an empty graphics buffers object. /// public GraphicsBuffers() { - // Initialize everything - this.Vertices = new List(); - this.Normals = new List(); - this.Indices = new List(); - this.UVs = new List(); - this.Colors = new List(); - - this.CMin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; - this.CMax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; - - this.VMax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; - this.VMin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; - - this.NMin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; - this.NMax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; - - this.UVMin = new double[2] { double.MaxValue, double.MaxValue }; - this.UVMax = new double[2] { double.MinValue, double.MinValue }; + Initialize(); } /// @@ -201,7 +164,7 @@ public void AddVertex(double x, double y, double z, double nx, double ny, double this.UVMin[0] = Math.Min(this.UVMin[0], u); this.UVMin[1] = Math.Min(this.UVMin[1], v); - if (color.HasValue && color.Value != default(Color)) + if (color.HasValue && color.Value != default) { this.CMax[0] = Math.Max(this.CMax[0], color.Value.Red); this.CMax[1] = Math.Max(this.CMax[1], color.Value.Green); @@ -227,5 +190,31 @@ public void AddIndex(ushort index) this.IMin = Math.Min(this.IMin, index); } + /// + /// Initialize the graphics buffer to a known size. + /// + /// The number of vertices. + /// The number of indices. + public void Initialize(int vertexCount = 0, int indexCount = 0) + { + // Initialize everything + this.Vertices = new List(sizeof(float) * 3 * vertexCount); + this.Normals = new List(sizeof(float) * 3 * vertexCount); + this.Indices = new List(sizeof(ushort) * indexCount); + this.UVs = new List(sizeof(float) * 2 * vertexCount); + this.Colors = new List(sizeof(float) * 3 * vertexCount); + + this.CMin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; + this.CMax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; + + this.VMax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; + this.VMin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; + + this.NMin = new double[3] { double.MaxValue, double.MaxValue, double.MaxValue }; + this.NMax = new double[3] { double.MinValue, double.MinValue, double.MinValue }; + + this.UVMin = new double[2] { double.MaxValue, double.MaxValue }; + this.UVMax = new double[2] { double.MinValue, double.MinValue }; + } } } \ No newline at end of file diff --git a/Elements/src/Geometry/IGraphicsBuffers.cs b/Elements/src/Geometry/IGraphicsBuffers.cs new file mode 100644 index 000000000..87285a8a5 --- /dev/null +++ b/Elements/src/Geometry/IGraphicsBuffers.cs @@ -0,0 +1,47 @@ +using Elements.Geometry; + +namespace Elements +{ + /// + /// A generic container for graphics data. This is broken out primarily to facilitate + /// simpler testing of graphics buffers. + /// + internal interface IGraphicsBuffers + { + /// + /// Initialize a graphics buffer to a sepcific vertex size. + /// + /// The number of vertices. + /// The number of indices. + void Initialize(int vertexCount, int indexCount); + + /// + /// Add a vertex to the graphics buffers. + /// + /// The position of the vertex. + /// The normal of the vertex. + /// The UV of the vertex. + /// The vertex color. + void AddVertex(Vector3 position, Vector3 normal, UV uv, Color? color = null); + + /// + /// Add a vertex to the graphics buffers. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + void AddVertex(double x, double y, double z, double nx, double ny, double nz, double u, double v, Color? color = null); + + /// + /// Add an index to the graphics buffers. + /// + /// The index to add. + void AddIndex(ushort index); + } +} \ No newline at end of file diff --git a/Elements/src/Geometry/Interfaces/ICurve.cs b/Elements/src/Geometry/Interfaces/ICurve.cs index b92669b94..518aed1ee 100644 --- a/Elements/src/Geometry/Interfaces/ICurve.cs +++ b/Elements/src/Geometry/Interfaces/ICurve.cs @@ -29,8 +29,9 @@ public interface ICurve /// /// The offset from the start of the ICurve. /// The offset from the end of the ICurve. + /// An additional rotation of the frame at each point. /// A collection of Transforms. - Transform[] Frames(double startSetback = 0.0, double endSetback = 0.0); + Transform[] Frames(double startSetback = 0.0, double endSetback = 0.0, double additionalRotation = 0.0); /// /// Get the bounding box of this curve. diff --git a/Elements/src/Geometry/Kernel.cs b/Elements/src/Geometry/Kernel.cs index ebd588fa3..7ce3584f0 100644 --- a/Elements/src/Geometry/Kernel.cs +++ b/Elements/src/Geometry/Kernel.cs @@ -29,9 +29,9 @@ public static Kernel Instance /// Create a sweep along a curve. /// /// A solid. - public Solid CreateSweepAlongCurve(Profile profile, Curve curve, double startSetback, double endSetback) + public Solid CreateSweepAlongCurve(Profile profile, Curve curve, double startSetback, double endSetback, double profileRotation) { - return Solid.SweepFaceAlongCurve(profile.Perimeter, profile.Voids, curve, startSetback, endSetback); + return Solid.SweepFaceAlongCurve(profile.Perimeter, profile.Voids != null && profile.Voids.Count > 0 ? profile.Voids : null, curve, startSetback, endSetback, profileRotation); } /// diff --git a/Elements/src/Geometry/Polygon.cs b/Elements/src/Geometry/Polygon.cs index 42af1742d..ccc84e33e 100644 --- a/Elements/src/Geometry/Polygon.cs +++ b/Elements/src/Geometry/Polygon.cs @@ -34,6 +34,16 @@ public Polygon(IList @vertices) : base(vertices) _plane = Plane(); } + /// + /// Construct a polygon. + /// + /// A collection of vertex locations. + /// Should self-intersection testing be disabled? + public Polygon(IList @vertices, bool disableValidation = false) : base(vertices, disableValidation) + { + _plane = Plane(); + } + /// /// Validate that this Polygon's vertices are coplanar, clean up any /// duplicate vertices, and fix any overlapping edges. @@ -79,6 +89,13 @@ protected override void ValidateVertices() /// The vertices of the polygon. public Polygon(params Vector3[] vertices) : this(new List(vertices)) { } + /// + /// Construct a polygon from points. + /// + /// Should self-intersection testing be disabled? + /// The vertices of the polygon. + public Polygon(bool disableValidation, params Vector3[] vertices) : this(new List(vertices), disableValidation) { } + /// /// Construct a transformed copy of this Polygon. /// @@ -2017,7 +2034,10 @@ public Polygon CollinearPointsRemoved() /// /// /// - public override Transform[] Frames(double startSetback, double endSetback) + /// + public override Transform[] Frames(double startSetback = 0.0, + double endSetback = 0.0, + double additionalRotation = 0.0) { // Create an array of transforms with the same // number of items as the vertices. @@ -2030,6 +2050,10 @@ public override Transform[] Frames(double startSetback, double endSetback) { var a = this.Vertices[i]; result[i] = CreateMiterTransform(i, a, up); + if (additionalRotation != 0.0) + { + result[i].RotateAboutPoint(result[i].Origin, result[i].ZAxis, additionalRotation); + } } return result; } diff --git a/Elements/src/Geometry/Polygons.cs b/Elements/src/Geometry/Polygons.cs index fc2e1db18..f54f08d08 100644 --- a/Elements/src/Geometry/Polygons.cs +++ b/Elements/src/Geometry/Polygons.cs @@ -21,7 +21,7 @@ public static Polygon Rectangle(double width, double height) var c = new Vector3(width / 2, height / 2); var d = new Vector3(-width / 2, height / 2); - return new Polygon(new[] { a, b, c, d }); + return new Polygon(true, a, b, c, d); } /// @@ -37,7 +37,7 @@ public static Polygon Rectangle(Vector3 min, Vector3 max) var c = max; var d = new Vector3(min.X, max.Y); - return new Polygon(new[] { a, b, c, d }); + return new Polygon(true, a, b, c, d); } /// @@ -55,7 +55,7 @@ public static Polygon Circle(double radius = 1.0, int divisions = 10) var t = i * (Math.PI * 2 / divisions); verts[i] = new Vector3(radius * Math.Cos(t), radius * Math.Sin(t)); } - return new Polygon(verts); + return new Polygon(verts, true); } /// @@ -84,7 +84,7 @@ public static Polygon Ngon(int sides, double radius = 0.5) var t = i * (Math.PI * 2 / sides); verts[i] = new Vector3(radius * Math.Cos(t), radius * Math.Sin(t)); } - return new Polygon(verts); + return new Polygon(verts, true); } /// @@ -111,7 +111,7 @@ public static Polygon L(double width, double length, double thickness) var d = new Vector3(thickness, thickness, 0); var e = new Vector3(thickness, length, 0); var f = new Vector3(0, length, 0); - return new Polygon(new[] { a, b, c, d, e, f }); + return new Polygon(true, a, b, c, d, e, f); } /// @@ -139,7 +139,7 @@ public static Polygon Star(double outerRadius, double innerRadius, int points) verts.Add(c1.PointAt(t)); } } - return new Polygon(verts); + return new Polygon(verts, true); } } } \ No newline at end of file diff --git a/Elements/src/Geometry/Polyline.cs b/Elements/src/Geometry/Polyline.cs index 809ec2605..144846293 100644 --- a/Elements/src/Geometry/Polyline.cs +++ b/Elements/src/Geometry/Polyline.cs @@ -44,6 +44,23 @@ public Polyline(IList @vertices) : base() _bounds = new BBox3(Vertices); } + + /// + /// Construct a polyline. + /// + /// A collection of vertex locations. + /// Should self intersection testing be disabled? + public Polyline(IList @vertices, bool disableValidation = false) : base() + { + this.Vertices = @vertices; + + if (!Validator.DisableValidationOnConstruction && !disableValidation) + { + ValidateVertices(); + } + _bounds = new BBox3(Vertices); + } + /// /// Clean up any duplicate vertices, and warn about any vertices that are too close to each other. /// @@ -63,6 +80,17 @@ public Polyline(params Vector3[] vertices) : this(new List(vertices)) } + /// + /// Construct a polyline from points. This is a convenience constructor + /// that can be used like this: `new Polyline((0,0,0), (10,0,0), (10,10,0))` + /// + /// Should self intersection testing be disabled? + /// The vertices of the polyline. + public Polyline(bool disableValidation, params Vector3[] vertices) : this(new List(vertices), disableValidation) + { + + } + /// /// Calculate the length of the polygon. /// @@ -353,7 +381,10 @@ protected virtual Vector3[] NormalsAtVertices() /// /// /// - public override Transform[] Frames(double startSetback = 0, double endSetback = 0) + /// + public override Transform[] Frames(double startSetback = 0.0, + double endSetback = 0.0, + double additionalRotation = 0.0) { var normals = this.NormalsAtVertices(); @@ -363,6 +394,10 @@ public override Transform[] Frames(double startSetback = 0, double endSetback = { var a = this.Vertices[i]; result[i] = CreateOrthogonalTransform(i, a, normals[i]); + if (additionalRotation != 0.0) + { + result[i].RotateAboutPoint(result[i].Origin, result[i].ZAxis, additionalRotation); + } } return result; } diff --git a/Elements/src/Geometry/Profiles/ParametricProfile.cs b/Elements/src/Geometry/Profiles/ParametricProfile.cs index e3248fcac..d452a7bc9 100644 --- a/Elements/src/Geometry/Profiles/ParametricProfile.cs +++ b/Elements/src/Geometry/Profiles/ParametricProfile.cs @@ -73,7 +73,7 @@ public ParametricProfile(List perimeterVectorExpressions, private string CompilePolygonScriptFromExpressions(List expressions) { var sb = new StringBuilder(); - sb.Append("new Polygon(new[]{"); + sb.Append("new Polygon(true, new[]{"); foreach (var expr in expressions) { diff --git a/Elements/src/Geometry/Profiles/WideFlangeProfile.cs b/Elements/src/Geometry/Profiles/WideFlangeProfile.cs index bf01d7490..08c125ce3 100644 --- a/Elements/src/Geometry/Profiles/WideFlangeProfile.cs +++ b/Elements/src/Geometry/Profiles/WideFlangeProfile.cs @@ -227,7 +227,7 @@ private static Polygon CreateProfile(double bf, var l = new Vector3(o.X + width / 2 + horizontalOffset, o.Y + height / 2 - thicknessFlange + verticalOffset); var m = new Vector3(o.X + width / 2 + horizontalOffset, o.Y + height / 2 + verticalOffset); - return new Polygon(new[] { a, b, c, e, f, g, h, i, j, k, l, m }); + return new Polygon(false, a, b, c, e, f, g, h, i, j, k, l, m); } } } \ No newline at end of file diff --git a/Elements/src/Geometry/Solids/Solid.cs b/Elements/src/Geometry/Solids/Solid.cs index b72b4213e..536f1f5a4 100644 --- a/Elements/src/Geometry/Solids/Solid.cs +++ b/Elements/src/Geometry/Solids/Solid.cs @@ -105,12 +105,14 @@ public static Solid SweepFace(Polygon perimeter, /// The curve along which to sweep. /// The setback distance of the sweep from the start of the curve. /// The setback distance of the sweep from the end of the curve. + /// The rotation of the profile. /// A solid. public static Solid SweepFaceAlongCurve(Polygon perimeter, IList holes, ICurve curve, double startSetback = 0, - double endSetback = 0) + double endSetback = 0, + double profileRotation = 0) { var solid = new Solid(); @@ -130,7 +132,7 @@ public static Solid SweepFaceAlongCurve(Polygon perimeter, var ssb = startSetback / l; var esb = endSetback / l; - var transforms = curve.Frames(ssb, esb); + var transforms = curve.Frames(ssb, esb, profileRotation); if (curve is Polygon) { diff --git a/Elements/src/Geometry/Solids/Sweep.cs b/Elements/src/Geometry/Solids/Sweep.cs index 1e0f641b4..7301a0182 100644 --- a/Elements/src/Geometry/Solids/Sweep.cs +++ b/Elements/src/Geometry/Solids/Sweep.cs @@ -111,9 +111,7 @@ public double ProfileRotation private void UpdateGeometry() { - var profileTrans = new Transform(); - profileTrans.Rotate(profileTrans.ZAxis, this.ProfileRotation); - this._solid = Kernel.Instance.CreateSweepAlongCurve(profileTrans.OfProfile(this._profile), this._curve, this._startSetback, this._endSetback); + this._solid = Kernel.Instance.CreateSweepAlongCurve(this._profile, this._curve, this._startSetback, this._endSetback, this._profileRotation); } } } \ No newline at end of file diff --git a/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs b/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs index 12c7925c8..cd94e6aa1 100644 --- a/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs +++ b/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs @@ -29,8 +29,10 @@ public Tess GetTess() { var tess = new Tess { + UsePooling = true, NoEmptyPolygons = true }; + tess.AddContour(face.Outer.ToContourVertexArray(transform)); if (face.Inner != null) diff --git a/Elements/src/Geometry/Tessellation/Tessellation.cs b/Elements/src/Geometry/Tessellation/Tessellation.cs index 2087b48db..aa7c3e3dc 100644 --- a/Elements/src/Geometry/Tessellation/Tessellation.cs +++ b/Elements/src/Geometry/Tessellation/Tessellation.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using LibTessDotNet.Double; [assembly: InternalsVisibleTo("Hypar.Elements.Tests")] @@ -15,46 +16,43 @@ internal static class Tessellation /// Triangulate a collection of CSGs and pack the triangulated data into /// a supplied buffers object. /// - internal static void Tessellate(IEnumerable providers, - IGraphicsBuffers buffers, - bool mergeVertices = false, - Func<(Vector3, Vector3, UV, Color), (Vector3, Vector3, UV, Color)> modifyVertexAttributes = null) + internal static T Tessellate(IEnumerable providers, + Func<(Vector3, Vector3, UV, Color), (Vector3, Vector3, UV, Color)> modifyVertexAttributes = null) where T : IGraphicsBuffers { - var allVertices = new List<(Vector3 position, Vector3 normal, UV uv, Color color)>(); + + // Gather all the tessellations + var tesses = new List(); foreach (var provider in providers) { foreach (var target in provider.GetTessellationTargets()) { - TessellatePolygon(target.GetTess(), buffers, allVertices, mergeVertices); + tesses.Add(target.GetTess()); } } - foreach (var v in allVertices) + // Pre-allocate a buffer big enough to hold all the tessellations + var buffer = (IGraphicsBuffers)Activator.CreateInstance(typeof(T)); + buffer.Initialize(tesses.Sum(tess => tess.VertexCount), tesses.Sum(tess => tess.Elements.Length)); + + ushort indexOffset = 0; + foreach (var tess in tesses) { - if (modifyVertexAttributes != null) - { - var mod = modifyVertexAttributes(v); - buffers.AddVertex(mod.Item1, mod.Item2, mod.Item3, mod.Item4); - } - else - { - buffers.AddVertex(v.position, v.normal, v.uv); - } + PackTessellationIntoBuffers(tess, buffer, modifyVertexAttributes, ref indexOffset); } + + return (T)buffer; } - private static void TessellatePolygon(Tess tess, + private static void PackTessellationIntoBuffers(Tess tess, IGraphicsBuffers buffers, - List<(Vector3 position, Vector3 normal, UV uv, Color color)> allVertices, - bool mergeVertices = false) + Func<(Vector3, Vector3, UV, Color), (Vector3, Vector3, UV, Color)> modifyVertexAttributes, + ref ushort indexOffset) { if (tess.ElementCount == 0) { return; } - var vertexIndices = new ushort[tess.Vertices.Length]; - // We pick the first triangle from the tesselator, // instead of the first three vertices, which are not guaranteed to be // wound correctly. @@ -75,18 +73,28 @@ private static void TessellatePolygon(Tess tess, var uu = U.Dot(v.Position.X, v.Position.Y, v.Position.Z); var vv = V.Dot(v.Position.X, v.Position.Y, v.Position.Z); - vertexIndices[i] = (ushort)GetOrCreateVertex(new Vector3(v.Position.X, v.Position.Y, v.Position.Z), - new Vector3(n.X, n.Y, n.Z), - new UV(uu, vv), - allVertices, - mergeVertices); + var v1 = new Vector3(v.Position.X, v.Position.Y, v.Position.Z); + var uv1 = new UV(uu, vv); + var c1 = default(Color); + + if (modifyVertexAttributes != null) + { + var mod = modifyVertexAttributes((v1, n, uv1, c1)); + buffers.AddVertex(mod.Item1, mod.Item2, mod.Item3, mod.Item4); + } + else + { + buffers.AddVertex(v1, n, uv1, c1); + } } for (var k = 0; k < tess.Elements.Length; k++) { - var index = vertexIndices[tess.Elements[k]]; + var index = (ushort)(tess.Elements[k] + indexOffset); buffers.AddIndex(index); } + + indexOffset += (ushort)tess.Vertices.Length; } private static Vector3 ToElementsVector(this ContourVertex v) @@ -94,28 +102,6 @@ private static Vector3 ToElementsVector(this ContourVertex v) return new Vector3(v.Position.X, v.Position.Y, v.Position.Z); } - private static int GetOrCreateVertex(Vector3 position, - Vector3 normal, - UV uv, - List<(Vector3 position, Vector3 normal, UV uv, Color color)> pts, - bool mergeVertices) - { - if (mergeVertices) - { - var index = pts.FindIndex(p => - { - return p.position.IsAlmostEqualTo(position) && p.normal.AngleTo(normal) < 45.0; - }); - if (index != -1) - { - return index; - } - } - - pts.Add((position, normal, uv, default(Color))); - return pts.Count - 1; - } - internal static (Vector3 U, Vector3 V) ComputeBasisAndNormalForTriangle(Vector3 a, Vector3 b, Vector3 c, out Vector3 n) { var tmp = (b - a).Unitized(); diff --git a/Elements/src/Geometry/Transform.cs b/Elements/src/Geometry/Transform.cs index 39f84360c..aa05b758b 100644 --- a/Elements/src/Geometry/Transform.cs +++ b/Elements/src/Geometry/Transform.cs @@ -385,7 +385,7 @@ public void Rotate(Vector3 axis, double angle) { var m = new Matrix(); m.SetupRotate(axis, angle * (Math.PI / 180.0)); - this.Matrix = this.Matrix * m; + this.Matrix *= m; } /// diff --git a/Elements/src/Serialization/glTF/GltfExtensions.cs b/Elements/src/Serialization/glTF/GltfExtensions.cs index e52cd9c30..9cd0b6c8e 100644 --- a/Elements/src/Serialization/glTF/GltfExtensions.cs +++ b/Elements/src/Serialization/glTF/GltfExtensions.cs @@ -1143,8 +1143,7 @@ private static void GetRenderDataForElement(Element e, nodes, materialId, ref meshId, - content, - mergeVertices); + content); if (!meshElementMap.ContainsKey(e.Id)) { meshElementMap.Add(e.Id, new List { meshId }); @@ -1166,8 +1165,7 @@ private static void GetRenderDataForElement(Element e, nodes, materialId, ref meshId, - geometricElement, - mergeVertices); + geometricElement); if (meshId > -1 && !meshElementMap.ContainsKey(e.Id)) { meshElementMap.Add(e.Id, new List { meshId }); @@ -1377,8 +1375,7 @@ private static int ProcessGeometricRepresentation(Element e, List nodes, string materialId, ref int meshId, - GeometricElement geometricElement, - bool mergeVertices = false) + GeometricElement geometricElement) { geometricElement.UpdateRepresentations(); @@ -1401,8 +1398,7 @@ private static int ProcessGeometricRepresentation(Element e, ref buffers, bufferViews, accessors, - meshes, - mergeVertices); + meshes); // If the id == -1, the mesh is malformed. // It may have no geometry. @@ -1428,8 +1424,7 @@ private static int ProcessSolidsAsCSG(GeometricElement geometricElement, ref List buffer, List bufferViews, List accessors, - List meshes, - bool mergeVertices = false) + List meshes) { GraphicsBuffers buffers = null; if (geometricElement.Representation.SkipCSGUnion) @@ -1437,16 +1432,13 @@ private static int ProcessSolidsAsCSG(GeometricElement geometricElement, // There's a special flag on Representation that allows you to // skip CSG unions. In this case, we tessellate all solids // individually, and do no booleaning. Voids are also ignored. - buffers = new GraphicsBuffers(); - Tessellation.Tessellate(geometricElement.Representation.SolidOperations.Select(so => new SolidTesselationTargetProvider(so.Solid, so.LocalTransform)), - buffers, - mergeVertices, + buffers = Tessellation.Tessellate(geometricElement.Representation.SolidOperations.Select(so => new SolidTesselationTargetProvider(so.Solid, so.LocalTransform)), geometricElement.ModifyVertexAttributes); } else { var csg = geometricElement.GetFinalCsgFromSolids(); - buffers = csg.Tessellate(mergeVertices, geometricElement.ModifyVertexAttributes); + buffers = csg.Tessellate(geometricElement.ModifyVertexAttributes); } if (buffers.Vertices.Count == 0) diff --git a/Elements/src/Validators/Validators.cs b/Elements/src/Validators/Validators.cs index f31bd88c1..189f83f8c 100644 --- a/Elements/src/Validators/Validators.cs +++ b/Elements/src/Validators/Validators.cs @@ -339,9 +339,11 @@ public void PostConstruct(object obj) private void UpdateGeometry(Sweep sweep) { - var profileTrans = new Transform(); - profileTrans.Rotate(profileTrans.ZAxis, sweep.ProfileRotation); - sweep._solid = Kernel.Instance.CreateSweepAlongCurve(profileTrans.OfProfile(sweep.Profile), sweep.Curve, sweep.StartSetback, sweep.EndSetback); + sweep._solid = Kernel.Instance.CreateSweepAlongCurve(sweep.Profile, + sweep.Curve, + sweep.StartSetback, + sweep.EndSetback, + sweep.ProfileRotation); } public void PreConstruct(object[] args) diff --git a/Elements/test/CsgTests.cs b/Elements/test/CsgTests.cs index 67fe218c8..2c058df07 100644 --- a/Elements/test/CsgTests.cs +++ b/Elements/test/CsgTests.cs @@ -5,8 +5,6 @@ using System; using Xunit; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; using Elements.Geometry.Tessellation; @@ -183,9 +181,8 @@ public void TessellatorProducesCorrectVertexNormals() var geoElem = new GeometricElement(representation: new Extrude(shape, 1, Vector3.ZAxis, false)); Model.AddElement(geoElem); var solid = geoElem.GetFinalCsgFromSolids(); - var mgb = new MockGraphicsBuffer(); var arrows = new ModelArrows(); - Tessellation.Tessellate(new Csg.Solid[] { solid }.Select(s => new CsgTessellationTargetProvider(solid)), mgb); + var mgb = Tessellation.Tessellate(new Csg.Solid[] { solid }.Select(s => new CsgTessellationTargetProvider(solid))); for (int i = 0; i < mgb.Indices.Count; i += 3) { var a = mgb.Indices[i]; @@ -206,9 +203,10 @@ public void TessellatorProducesCorrectVertexNormals() private class MockGraphicsBuffer : IGraphicsBuffers { - public List Indices { get; set; } = new List(); + public List Indices { get; set; } + + public List<(Vector3 position, Vector3 normal)> Vertices { get; set; } - public List<(Vector3 position, Vector3 normal)> Vertices { get; set; } = new List<(Vector3 position, Vector3 normal)>(); public void AddIndex(ushort index) { Indices.Add(index); @@ -223,6 +221,12 @@ public void AddVertex(double x, double y, double z, double nx, double ny, double { Vertices.Add((new Vector3(x, y, z), new Vector3(nx, ny, nz))); } + + public void Initialize(int vertexCount = 0, int indexCount = 0) + { + this.Vertices = new List<(Vector3 position, Vector3 normal)>(); + this.Indices = new List(); + } } } } \ No newline at end of file diff --git a/Elements/test/SolidTests.cs b/Elements/test/SolidTests.cs index dbbcdbe63..1197e4cbe 100644 --- a/Elements/test/SolidTests.cs +++ b/Elements/test/SolidTests.cs @@ -10,6 +10,7 @@ using Elements.Serialization.glTF; using Elements.Serialization.JSON; using System.Linq; +using Elements.Geometry.Tessellation; namespace Elements.Tests { @@ -729,6 +730,16 @@ public void TwoHoles() Assert.Equal(14, result.Faces.Count); } + [Fact] + public void TessellationHasCorrectNumberOfVertices() + { + var panel = new Panel(Polygon.L(5, 5, 2)); + panel.UpdateRepresentations(); + var buffer = Tessellation.Tessellate(panel.Representation.SolidOperations.Select(so => new SolidTesselationTargetProvider(so.Solid, so.LocalTransform))); + Assert.Equal(12, buffer.VertexCount); // Two faces of 6 vertices each + Assert.Equal(8, buffer.FacetCount); // Two faces of 4 facets each. + } + private class DebugInfo { public List Solid { get; set; }