From 4829135a6ff6a2d6596fbd8c863e57f9d5cff7cf Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 12:03:31 -0700 Subject: [PATCH 01/24] Don't create individual tesselators. --- .../Tessellation/SolidFaceTessAdapter.cs | 48 +++++++++++++++++++ .../SolidTessellationTargetProvider.cs | 5 +- .../src/Geometry/Tessellation/Tessellation.cs | 4 +- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs b/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs index 12c7925c8..e6ff19d69 100644 --- a/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs +++ b/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs @@ -3,6 +3,54 @@ namespace Elements.Geometry.Tessellation { + /// + /// An object which provides a tessellation for a solid. + /// + internal class SolidTessAdapter : ITessAdapter + { + private readonly Solid solid; + private readonly Transform transform; + + /// + /// Construct a solid tessellation adapter. + /// + /// + /// + public SolidTessAdapter(Solid solid, Transform transform = null) + { + this.solid = solid; + this.transform = transform; + } + + public Tess GetTess() + { + var tess = new Tess + { + NoEmptyPolygons = true + }; + foreach (var face in solid.Faces.Values) + { + tess.AddContour(face.Outer.ToContourVertexArray(transform)); + + if (face.Inner != null) + { + foreach (var loop in face.Inner) + { + tess.AddContour(loop.ToContourVertexArray(transform)); + } + } + } + + tess.Tessellate(WindingRule.Positive, ElementType.Polygons, 3); + return tess; + } + + public bool RequiresTessellation() + { + return true; + } + } + /// /// An object which provides a tessellation for a solid face. /// diff --git a/Elements/src/Geometry/Tessellation/SolidTessellationTargetProvider.cs b/Elements/src/Geometry/Tessellation/SolidTessellationTargetProvider.cs index 9d246ade7..9834ed03c 100644 --- a/Elements/src/Geometry/Tessellation/SolidTessellationTargetProvider.cs +++ b/Elements/src/Geometry/Tessellation/SolidTessellationTargetProvider.cs @@ -27,10 +27,7 @@ public SolidTesselationTargetProvider(Solid solid, Transform transform = null) /// public IEnumerable GetTessellationTargets() { - foreach (var f in solid.Faces.Values) - { - yield return new SolidFaceTessAdapter(f, transform); - } + yield return new SolidTessAdapter(solid, transform); } } } \ No newline at end of file diff --git a/Elements/src/Geometry/Tessellation/Tessellation.cs b/Elements/src/Geometry/Tessellation/Tessellation.cs index 2087b48db..f66ab0f04 100644 --- a/Elements/src/Geometry/Tessellation/Tessellation.cs +++ b/Elements/src/Geometry/Tessellation/Tessellation.cs @@ -25,7 +25,7 @@ internal static void Tessellate(IEnumerable provide { foreach (var target in provider.GetTessellationTargets()) { - TessellatePolygon(target.GetTess(), buffers, allVertices, mergeVertices); + PackTessellationIntoBuffers(target.GetTess(), buffers, allVertices, mergeVertices); } } @@ -43,7 +43,7 @@ internal static void Tessellate(IEnumerable provide } } - 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) From 488f564cf069230a2b2050f970c657672d61af77 Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 12:53:02 -0700 Subject: [PATCH 02/24] Don't allocate a rotated transform during solid operations. --- Elements/src/Geometry/Curve.cs | 9 ++++++++- Elements/src/Geometry/Interfaces/ICurve.cs | 3 ++- Elements/src/Geometry/Kernel.cs | 4 ++-- Elements/src/Geometry/Solids/Solid.cs | 6 ++++-- Elements/src/Geometry/Solids/Sweep.cs | 4 +--- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Elements/src/Geometry/Curve.cs b/Elements/src/Geometry/Curve.cs index dcd5dce44..4f97e61de 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].Rotate(transforms[i].ZAxis, additionalRotation); + } } return transforms; } 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..cd5ffeb81 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, curve, startSetback, endSetback, profileRotation); } /// 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 From b0b656d4d882a2db8db4b643e029c74112fdc0fb Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 12:53:16 -0700 Subject: [PATCH 03/24] Don't validate for known polygon types. --- Elements/src/Geometry/Bezier.cs | 10 ++++++-- Elements/src/Geometry/Circle.cs | 2 +- Elements/src/Geometry/Polygon.cs | 19 ++++++++++++-- Elements/src/Geometry/Polygons.cs | 12 ++++----- Elements/src/Geometry/Polyline.cs | 25 ++++++++++++++++--- .../Geometry/Profiles/ParametricProfile.cs | 2 +- .../Geometry/Profiles/WideFlangeProfile.cs | 2 +- Elements/src/Validators/Validators.cs | 8 +++--- 8 files changed, 61 insertions(+), 19 deletions(-) diff --git a/Elements/src/Geometry/Bezier.cs b/Elements/src/Geometry/Bezier.cs index 00a3f1c94..19c36a5f4 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].Rotate(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/Polygon.cs b/Elements/src/Geometry/Polygon.cs index 1d161d772..058ee4b2e 100644 --- a/Elements/src/Geometry/Polygon.cs +++ b/Elements/src/Geometry/Polygon.cs @@ -28,8 +28,9 @@ public partial class Polygon : Polyline /// Construct a polygon. /// /// A collection of vertex locations. + /// Should self-intersection testing be disabled? [JsonConstructor] - public Polygon(IList @vertices) : base(vertices) + public Polygon(IList @vertices, bool disableValidation = false) : base(vertices, disableValidation) { _plane = Plane(); } @@ -79,6 +80,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. /// @@ -1975,7 +1983,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. @@ -1988,6 +1999,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].Rotate(result[i].ZAxis, additionalRotation); + } } return result; } diff --git a/Elements/src/Geometry/Polygons.cs b/Elements/src/Geometry/Polygons.cs index fc2e1db18..d2e4f368c 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, new[] { 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, new[] { 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, new[] { 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 1e037aad7..1a0c5123b 100644 --- a/Elements/src/Geometry/Polyline.cs +++ b/Elements/src/Geometry/Polyline.cs @@ -32,12 +32,13 @@ public class Polyline : Curve, IEquatable /// Construct a polyline. /// /// A collection of vertex locations. + /// Should self intersection testing be disabled? [JsonConstructor] - public Polyline(IList @vertices) : base() + public Polyline(IList @vertices, bool disableValidation = false) : base() { this.Vertices = @vertices; - if (!Validator.DisableValidationOnConstruction) + if (!Validator.DisableValidationOnConstruction && !disableValidation) { ValidateVertices(); } @@ -63,6 +64,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 +365,10 @@ protected virtual Vector3[] NormalsAtVertices() /// /// /// - public override Transform[] Frames(double startSetback, double endSetback) + /// + public override Transform[] Frames(double startSetback = 0.0, + double endSetback = 0.0, + double additionalRotation = 0.0) { var normals = this.NormalsAtVertices(); @@ -363,6 +378,10 @@ public override Transform[] Frames(double startSetback, double endSetback) { var a = this.Vertices[i]; result[i] = CreateOrthogonalTransform(i, a, normals[i]); + if (additionalRotation != 0.0) + { + result[i].Rotate(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..49b7bffcb 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, new[] { a, b, c, e, f, g, h, i, j, k, l, m }); } } } \ No newline at end of file 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) From 7dba1ecbc507d5f11036481c800187cee4e30ebc Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 14:03:41 -0700 Subject: [PATCH 04/24] Don't mess with existing serialization constructors. --- Elements/src/Geometry/Polygon.cs | 11 ++++++++++- Elements/src/Geometry/Polyline.cs | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Elements/src/Geometry/Polygon.cs b/Elements/src/Geometry/Polygon.cs index 058ee4b2e..dfe9f5a18 100644 --- a/Elements/src/Geometry/Polygon.cs +++ b/Elements/src/Geometry/Polygon.cs @@ -28,8 +28,17 @@ public partial class Polygon : Polyline /// Construct a polygon. /// /// A collection of vertex locations. - /// Should self-intersection testing be disabled? [JsonConstructor] + 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(); diff --git a/Elements/src/Geometry/Polyline.cs b/Elements/src/Geometry/Polyline.cs index 1a0c5123b..b3c9e5624 100644 --- a/Elements/src/Geometry/Polyline.cs +++ b/Elements/src/Geometry/Polyline.cs @@ -32,8 +32,24 @@ public class Polyline : Curve, IEquatable /// Construct a polyline. /// /// A collection of vertex locations. - /// Should self intersection testing be disabled? [JsonConstructor] + public Polyline(IList @vertices) : base() + { + this.Vertices = @vertices; + + if (!Validator.DisableValidationOnConstruction) + { + ValidateVertices(); + } + _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; From 4627315f880d63de0b7e7427014791405134634b Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 14:15:35 -0700 Subject: [PATCH 05/24] Update changelog. --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a913d013b..2a7b16525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,15 @@ ## 1.0.1 +### 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)` + ### Fixed - #805 - -### Fixed - `Line.IsCollinear(Line line)` would return `false` if lines are close to each other but not collinear - `Vector3.AreCollinear(Vector3 a, Vector3 b, Vector3 c)` would return `false` if points coordinates difference is larger than `Vector3.EPSILON` - `EdgeDisplaySettings` for materials to control the display of lines in supported viewers (like Hypar.io). @@ -13,10 +18,10 @@ - `Line.MergeCollinearLine(Line line)` creates new line containing all four collinear vertices - `Line.Projected(Plane plane)` create new line projected onto plane - ### 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 From f48342999c673e48ba07d3aa3b8012625c2433c0 Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 15:49:40 -0700 Subject: [PATCH 06/24] Avoid creating empty faces. --- Elements/src/Geometry/Kernel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Elements/src/Geometry/Kernel.cs b/Elements/src/Geometry/Kernel.cs index cd5ffeb81..7ce3584f0 100644 --- a/Elements/src/Geometry/Kernel.cs +++ b/Elements/src/Geometry/Kernel.cs @@ -31,7 +31,7 @@ public static Kernel Instance /// A solid. public Solid CreateSweepAlongCurve(Profile profile, Curve curve, double startSetback, double endSetback, double profileRotation) { - return Solid.SweepFaceAlongCurve(profile.Perimeter, profile.Voids, curve, startSetback, endSetback, profileRotation); + return Solid.SweepFaceAlongCurve(profile.Perimeter, profile.Voids != null && profile.Voids.Count > 0 ? profile.Voids : null, curve, startSetback, endSetback, profileRotation); } /// From 30cb88817e44d8c34573abd612b63afbabe3f871 Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 15:49:56 -0700 Subject: [PATCH 07/24] Output memory as well. --- Elements.Benchmarks/Trace.cs | 1 + 1 file changed, 1 insertion(+) 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 { From e5efe5d991571d09255ebbb0cd08bbaf11f8bb5f Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 15:50:40 -0700 Subject: [PATCH 08/24] It's a 2D tessellator. We can't do it per solid. --- .../Tessellation/SolidFaceTessAdapter.cs | 50 +------------------ .../SolidTessellationTargetProvider.cs | 5 +- 2 files changed, 6 insertions(+), 49 deletions(-) diff --git a/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs b/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs index e6ff19d69..cd94e6aa1 100644 --- a/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs +++ b/Elements/src/Geometry/Tessellation/SolidFaceTessAdapter.cs @@ -3,54 +3,6 @@ namespace Elements.Geometry.Tessellation { - /// - /// An object which provides a tessellation for a solid. - /// - internal class SolidTessAdapter : ITessAdapter - { - private readonly Solid solid; - private readonly Transform transform; - - /// - /// Construct a solid tessellation adapter. - /// - /// - /// - public SolidTessAdapter(Solid solid, Transform transform = null) - { - this.solid = solid; - this.transform = transform; - } - - public Tess GetTess() - { - var tess = new Tess - { - NoEmptyPolygons = true - }; - foreach (var face in solid.Faces.Values) - { - tess.AddContour(face.Outer.ToContourVertexArray(transform)); - - if (face.Inner != null) - { - foreach (var loop in face.Inner) - { - tess.AddContour(loop.ToContourVertexArray(transform)); - } - } - } - - tess.Tessellate(WindingRule.Positive, ElementType.Polygons, 3); - return tess; - } - - public bool RequiresTessellation() - { - return true; - } - } - /// /// An object which provides a tessellation for a solid face. /// @@ -77,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/SolidTessellationTargetProvider.cs b/Elements/src/Geometry/Tessellation/SolidTessellationTargetProvider.cs index 9834ed03c..9d246ade7 100644 --- a/Elements/src/Geometry/Tessellation/SolidTessellationTargetProvider.cs +++ b/Elements/src/Geometry/Tessellation/SolidTessellationTargetProvider.cs @@ -27,7 +27,10 @@ public SolidTesselationTargetProvider(Solid solid, Transform transform = null) /// public IEnumerable GetTessellationTargets() { - yield return new SolidTessAdapter(solid, transform); + foreach (var f in solid.Faces.Values) + { + yield return new SolidFaceTessAdapter(f, transform); + } } } } \ No newline at end of file From 26b004846b96d4e212ca886078f51ff3380a7843 Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Thu, 26 May 2022 21:24:45 -0700 Subject: [PATCH 09/24] Transform rotation around a point. --- CHANGELOG.md | 1 + Elements/src/Geometry/Bezier.cs | 2 +- Elements/src/Geometry/CsgExtensions.cs | 10 +--- Elements/src/Geometry/Curve.cs | 2 +- Elements/src/Geometry/GraphicsBuffers.cs | 29 +++++++++- Elements/src/Geometry/Polygon.cs | 2 +- Elements/src/Geometry/Polyline.cs | 2 +- .../src/Geometry/Tessellation/Tessellation.cs | 57 +++++++++++-------- Elements/src/Geometry/Transform.cs | 22 ++++++- .../src/Serialization/glTF/GltfExtensions.cs | 9 +-- Elements/test/CsgTests.cs | 57 +++++++++---------- 11 files changed, 119 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7b16525..97a0a734d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - `Polygon(bool disableValidation, params Vector3[] vertices)` - `Polyline(IList @vertices, bool disableValidation = false)` - `Polyline(bool disableValidation, params Vector3[] vertices)` +- `Transform.Rotate(Vector3 axis, double angle, Vector3 origin)` ### Fixed - #805 diff --git a/Elements/src/Geometry/Bezier.cs b/Elements/src/Geometry/Bezier.cs index 19c36a5f4..86a8b7f42 100644 --- a/Elements/src/Geometry/Bezier.cs +++ b/Elements/src/Geometry/Bezier.cs @@ -85,7 +85,7 @@ public override Transform[] Frames(double startSetback = 0, transforms[i] = TransformAt(i * 1.0 / _samples); if (additionalRotation != 0.0) { - transforms[i].Rotate(transforms[i].ZAxis, additionalRotation); + transforms[i].Rotate(transforms[i].ZAxis, additionalRotation, transforms[i].Origin); } } return transforms; diff --git a/Elements/src/Geometry/CsgExtensions.cs b/Elements/src/Geometry/CsgExtensions.cs index 8cc3af9d4..965b71855 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 4f97e61de..6597793e9 100644 --- a/Elements/src/Geometry/Curve.cs +++ b/Elements/src/Geometry/Curve.cs @@ -40,7 +40,7 @@ public virtual Transform[] Frames(double startSetback = 0.0, transforms[i] = TransformAt(parameters[i]); if (additionalRotation != 0.0) { - transforms[i].Rotate(transforms[i].ZAxis, additionalRotation); + transforms[i].Rotate(transforms[i].ZAxis, additionalRotation, transforms[i].Origin); } } return transforms; diff --git a/Elements/src/Geometry/GraphicsBuffers.cs b/Elements/src/Geometry/GraphicsBuffers.cs index 35ef48918..952d52c91 100644 --- a/Elements/src/Geometry/GraphicsBuffers.cs +++ b/Elements/src/Geometry/GraphicsBuffers.cs @@ -145,6 +145,33 @@ public GraphicsBuffers() this.UVMax = new double[2] { double.MinValue, double.MinValue }; } + /// + /// Construct an empty graphics buffers object with pre-allocated collections. + /// + /// + /// + public GraphicsBuffers(int vertexCount, int indexCount) + { + // 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 }; + } + /// /// Add a vertex to the graphics buffers. /// @@ -201,7 +228,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); diff --git a/Elements/src/Geometry/Polygon.cs b/Elements/src/Geometry/Polygon.cs index dfe9f5a18..0d046938d 100644 --- a/Elements/src/Geometry/Polygon.cs +++ b/Elements/src/Geometry/Polygon.cs @@ -2010,7 +2010,7 @@ public override Transform[] Frames(double startSetback = 0.0, result[i] = CreateMiterTransform(i, a, up); if (additionalRotation != 0.0) { - result[i].Rotate(result[i].ZAxis, additionalRotation); + result[i].Rotate(result[i].ZAxis, additionalRotation, result[i].Origin); } } return result; diff --git a/Elements/src/Geometry/Polyline.cs b/Elements/src/Geometry/Polyline.cs index b3c9e5624..bd4a18684 100644 --- a/Elements/src/Geometry/Polyline.cs +++ b/Elements/src/Geometry/Polyline.cs @@ -396,7 +396,7 @@ public override Transform[] Frames(double startSetback = 0.0, result[i] = CreateOrthogonalTransform(i, a, normals[i]); if (additionalRotation != 0.0) { - result[i].Rotate(result[i].ZAxis, additionalRotation); + result[i].Rotate(result[i].ZAxis, additionalRotation, result[i].Origin); } } return result; diff --git a/Elements/src/Geometry/Tessellation/Tessellation.cs b/Elements/src/Geometry/Tessellation/Tessellation.cs index f66ab0f04..36aa6f95c 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,42 @@ 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, + internal static GraphicsBuffers Tessellate(IEnumerable providers, Func<(Vector3, Vector3, UV, Color), (Vector3, Vector3, UV, Color)> modifyVertexAttributes = null) { - 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()) { - PackTessellationIntoBuffers(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 buffers = new GraphicsBuffers(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, buffers, modifyVertexAttributes, ref indexOffset); } + + return buffers; } 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 +72,28 @@ private static void PackTessellationIntoBuffers(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) diff --git a/Elements/src/Geometry/Transform.cs b/Elements/src/Geometry/Transform.cs index cab259f9c..9c240f9b1 100644 --- a/Elements/src/Geometry/Transform.cs +++ b/Elements/src/Geometry/Transform.cs @@ -385,7 +385,27 @@ 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; + } + + /// + /// Apply a rotation around a to the transform around a point. + /// + /// The axis of rotation. + /// The angle of rotation in degrees. + /// The point around which to rotate. + public void Rotate(Vector3 axis, double angle, Vector3 origin) + { + var mT1 = new Matrix(); + mT1.SetupTranslation(origin.Negate()); + + var mT2 = new Matrix(); + mT2.SetupTranslation(origin); + + var mR = new Matrix(); + mR.SetupRotate(axis, angle * (Math.PI / 180.0)); + + this.Matrix = this.Matrix * mT1 * mR * mT2; } /// diff --git a/Elements/src/Serialization/glTF/GltfExtensions.cs b/Elements/src/Serialization/glTF/GltfExtensions.cs index 3e8e11cfd..e0347f272 100644 --- a/Elements/src/Serialization/glTF/GltfExtensions.cs +++ b/Elements/src/Serialization/glTF/GltfExtensions.cs @@ -86,7 +86,7 @@ public static void ToGlTF(this Model model, string path, out List err if (SaveGlb(model, path, out errors, drawEdges)) { return; - } + } // Else fall through to produce an empty GLTF. } else @@ -1425,16 +1425,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/test/CsgTests.cs b/Elements/test/CsgTests.cs index 67fe218c8..e2f9343ed 100644 --- a/Elements/test/CsgTests.cs +++ b/Elements/test/CsgTests.cs @@ -174,35 +174,34 @@ public void UnionWithProblematicPolygons() var solid = element.GetFinalCsgFromSolids(); } - [Fact] - public void TessellatorProducesCorrectVertexNormals() - { - Name = nameof(TessellatorProducesCorrectVertexNormals); - var shape = new Polygon((4.96243, 50.58403), (5.78472, 50.58403), (5.78472, 65.83403), (-7.05727, 65.83403), (-7.05727, 50.57403), (4.96243, 50.57403)); - - 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); - for (int i = 0; i < mgb.Indices.Count; i += 3) - { - var a = mgb.Indices[i]; - var b = mgb.Indices[i + 1]; - var c = mgb.Indices[i + 2]; - var verts = new[] { mgb.Vertices[a], mgb.Vertices[b], mgb.Vertices[c] }; - verts.ToList().ForEach((v) => - { - arrows.Vectors.Add((v.position, v.normal, 0.2, Colors.Blue)); - }); - var triangle = new Polygon(verts.Select(v => v.position).ToList()); - var normal = verts[0].normal; - Assert.True(triangle.Normal().Dot(normal.Unitized()) > 0, "The vertex normals are pointing in the opposite direction as their triangles' winding should suggest"); - Model.AddElement(triangle.TransformedPolygon(new Transform(normal * 0.2))); - } - Model.AddElement(arrows); - } + // [Fact] + // public void TessellatorProducesCorrectVertexNormals() + // { + // Name = nameof(TessellatorProducesCorrectVertexNormals); + // var shape = new Polygon((4.96243, 50.58403), (5.78472, 50.58403), (5.78472, 65.83403), (-7.05727, 65.83403), (-7.05727, 50.57403), (4.96243, 50.57403)); + + // var geoElem = new GeometricElement(representation: new Extrude(shape, 1, Vector3.ZAxis, false)); + // Model.AddElement(geoElem); + // var solid = geoElem.GetFinalCsgFromSolids(); + // var arrows = new ModelArrows(); + // 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]; + // var b = mgb.Indices[i + 1]; + // var c = mgb.Indices[i + 2]; + // var verts = new[] { mgb.Vertices[a], mgb.Vertices[b], mgb.Vertices[c] }; + // verts.ToList().ForEach((v) => + // { + // arrows.Vectors.Add((v.position, v.normal, 0.2, Colors.Blue)); + // }); + // var triangle = new Polygon(verts.Select(v => v.position).ToList()); + // var normal = verts[0].normal; + // Assert.True(triangle.Normal().Dot(normal.Unitized()) > 0, "The vertex normals are pointing in the opposite direction as their triangles' winding should suggest"); + // Model.AddElement(triangle.TransformedPolygon(new Transform(normal * 0.2))); + // } + // Model.AddElement(arrows); + // } private class MockGraphicsBuffer : IGraphicsBuffers { From 02712753926ae03efa2300d729fb2c863e68ea2f Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Fri, 27 May 2022 07:04:55 -0700 Subject: [PATCH 10/24] Do they speak english on what?! --- Elements/src/Geometry/Transform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Elements/src/Geometry/Transform.cs b/Elements/src/Geometry/Transform.cs index 9c240f9b1..6e6122436 100644 --- a/Elements/src/Geometry/Transform.cs +++ b/Elements/src/Geometry/Transform.cs @@ -389,7 +389,7 @@ public void Rotate(Vector3 axis, double angle) } /// - /// Apply a rotation around a to the transform around a point. + /// Apply a rotation to the transform around a point. /// /// The axis of rotation. /// The angle of rotation in degrees. From ebca233a2386a3a6f59c1c562d9f7fcd555e0c96 Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Sat, 28 May 2022 12:09:11 -0700 Subject: [PATCH 11/24] Support generic creation of graphics buffers. --- Elements/src/Geometry/CsgExtensions.cs | 2 +- Elements/src/Geometry/GraphicsBuffers.cs | 134 ++++++------------ Elements/src/Geometry/IGraphicsBuffers.cs | 47 ++++++ .../src/Geometry/Tessellation/Tessellation.cs | 11 +- .../src/Serialization/glTF/GltfExtensions.cs | 2 +- Elements/test/CsgTests.cs | 69 ++++----- 6 files changed, 132 insertions(+), 133 deletions(-) create mode 100644 Elements/src/Geometry/IGraphicsBuffers.cs diff --git a/Elements/src/Geometry/CsgExtensions.cs b/Elements/src/Geometry/CsgExtensions.cs index 965b71855..ea1e22fb4 100644 --- a/Elements/src/Geometry/CsgExtensions.cs +++ b/Elements/src/Geometry/CsgExtensions.cs @@ -37,7 +37,7 @@ internal static GraphicsBuffers Tessellate(this Csg.Solid csg, internal static GraphicsBuffers Tessellate(this Csg.Solid[] csgs, Func<(Vector3, Vector3, UV, Color), (Vector3, Vector3, UV, Color)> modifyVertexAttributes = null) { - var buffers = Tessellation.Tessellation.Tessellate(csgs.Select(csg => new CsgTessellationTargetProvider(csg)), + var buffers = Tessellation.Tessellation.Tessellate(csgs.Select(csg => new CsgTessellationTargetProvider(csg)), modifyVertexAttributes); return buffers; } diff --git a/Elements/src/Geometry/GraphicsBuffers.cs b/Elements/src/Geometry/GraphicsBuffers.cs index 952d52c91..989b6d5a4 100644 --- a/Elements/src/Geometry/GraphicsBuffers.cs +++ b/Elements/src/Geometry/GraphicsBuffers.cs @@ -3,42 +3,6 @@ namespace Elements.Geometry { - /// - /// A generic container for graphics data. This is broken out primarily to facilitate - /// simpler testing of graphics buffers. - /// - internal interface 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. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - 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); - } - /// /// A container for graphics data. /// The buffers used in this class align with webgl requirements. @@ -48,57 +12,57 @@ 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,63 +77,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 }; - } - - /// - /// Construct an empty graphics buffers object with pre-allocated collections. - /// - /// - /// - public GraphicsBuffers(int vertexCount, int indexCount) - { - // 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 }; + Initialize(); } /// @@ -254,5 +174,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/Tessellation/Tessellation.cs b/Elements/src/Geometry/Tessellation/Tessellation.cs index 36aa6f95c..295ea79d8 100644 --- a/Elements/src/Geometry/Tessellation/Tessellation.cs +++ b/Elements/src/Geometry/Tessellation/Tessellation.cs @@ -16,8 +16,8 @@ internal static class Tessellation /// Triangulate a collection of CSGs and pack the triangulated data into /// a supplied buffers object. /// - internal static GraphicsBuffers Tessellate(IEnumerable providers, - 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 { // Gather all the tessellations @@ -31,15 +31,16 @@ internal static GraphicsBuffers Tessellate(IEnumerable tess.VertexCount), tesses.Sum(tess => tess.Elements.Length)); - ushort indexOffset = 0; + var buffers = (IGraphicsBuffers)Activator.CreateInstance(typeof(T)); + buffers.Initialize(tesses.Sum(tess => tess.VertexCount), tesses.Sum(tess => tess.Elements.Length)); + ushort indexOffset = 0; foreach (var tess in tesses) { PackTessellationIntoBuffers(tess, buffers, modifyVertexAttributes, ref indexOffset); } - return buffers; + return (T)buffers; } private static void PackTessellationIntoBuffers(Tess tess, diff --git a/Elements/src/Serialization/glTF/GltfExtensions.cs b/Elements/src/Serialization/glTF/GltfExtensions.cs index e0347f272..efefc2257 100644 --- a/Elements/src/Serialization/glTF/GltfExtensions.cs +++ b/Elements/src/Serialization/glTF/GltfExtensions.cs @@ -1425,7 +1425,7 @@ 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 = Tessellation.Tessellate(geometricElement.Representation.SolidOperations.Select(so => new SolidTesselationTargetProvider(so.Solid, so.LocalTransform)), + buffers = Tessellation.Tessellate(geometricElement.Representation.SolidOperations.Select(so => new SolidTesselationTargetProvider(so.Solid, so.LocalTransform)), geometricElement.ModifyVertexAttributes); } else diff --git a/Elements/test/CsgTests.cs b/Elements/test/CsgTests.cs index e2f9343ed..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; @@ -174,40 +172,41 @@ public void UnionWithProblematicPolygons() var solid = element.GetFinalCsgFromSolids(); } - // [Fact] - // public void TessellatorProducesCorrectVertexNormals() - // { - // Name = nameof(TessellatorProducesCorrectVertexNormals); - // var shape = new Polygon((4.96243, 50.58403), (5.78472, 50.58403), (5.78472, 65.83403), (-7.05727, 65.83403), (-7.05727, 50.57403), (4.96243, 50.57403)); - - // var geoElem = new GeometricElement(representation: new Extrude(shape, 1, Vector3.ZAxis, false)); - // Model.AddElement(geoElem); - // var solid = geoElem.GetFinalCsgFromSolids(); - // var arrows = new ModelArrows(); - // 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]; - // var b = mgb.Indices[i + 1]; - // var c = mgb.Indices[i + 2]; - // var verts = new[] { mgb.Vertices[a], mgb.Vertices[b], mgb.Vertices[c] }; - // verts.ToList().ForEach((v) => - // { - // arrows.Vectors.Add((v.position, v.normal, 0.2, Colors.Blue)); - // }); - // var triangle = new Polygon(verts.Select(v => v.position).ToList()); - // var normal = verts[0].normal; - // Assert.True(triangle.Normal().Dot(normal.Unitized()) > 0, "The vertex normals are pointing in the opposite direction as their triangles' winding should suggest"); - // Model.AddElement(triangle.TransformedPolygon(new Transform(normal * 0.2))); - // } - // Model.AddElement(arrows); - // } + [Fact] + public void TessellatorProducesCorrectVertexNormals() + { + Name = nameof(TessellatorProducesCorrectVertexNormals); + var shape = new Polygon((4.96243, 50.58403), (5.78472, 50.58403), (5.78472, 65.83403), (-7.05727, 65.83403), (-7.05727, 50.57403), (4.96243, 50.57403)); + + var geoElem = new GeometricElement(representation: new Extrude(shape, 1, Vector3.ZAxis, false)); + Model.AddElement(geoElem); + var solid = geoElem.GetFinalCsgFromSolids(); + var arrows = new ModelArrows(); + 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]; + var b = mgb.Indices[i + 1]; + var c = mgb.Indices[i + 2]; + var verts = new[] { mgb.Vertices[a], mgb.Vertices[b], mgb.Vertices[c] }; + verts.ToList().ForEach((v) => + { + arrows.Vectors.Add((v.position, v.normal, 0.2, Colors.Blue)); + }); + var triangle = new Polygon(verts.Select(v => v.position).ToList()); + var normal = verts[0].normal; + Assert.True(triangle.Normal().Dot(normal.Unitized()) > 0, "The vertex normals are pointing in the opposite direction as their triangles' winding should suggest"); + Model.AddElement(triangle.TransformedPolygon(new Transform(normal * 0.2))); + } + Model.AddElement(arrows); + } 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); @@ -222,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 From acd906555410dbc3d1f1f5ccc5bc0ed315a084f1 Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Sat, 28 May 2022 12:49:25 -0700 Subject: [PATCH 12/24] Remove merge vertices for now. --- Elements/src/Geometry/GraphicsBuffers.cs | 16 ++++++++++ .../src/Geometry/Tessellation/Tessellation.cs | 30 +++---------------- .../src/Serialization/glTF/GltfExtensions.cs | 15 ++++------ Elements/test/SolidTests.cs | 11 +++++++ 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Elements/src/Geometry/GraphicsBuffers.cs b/Elements/src/Geometry/GraphicsBuffers.cs index 989b6d5a4..a8e9f6448 100644 --- a/Elements/src/Geometry/GraphicsBuffers.cs +++ b/Elements/src/Geometry/GraphicsBuffers.cs @@ -9,6 +9,22 @@ namespace Elements.Geometry /// public class GraphicsBuffers : IGraphicsBuffers { + /// + /// The number of vertices represented by the buffer. + /// + public int VertexCount + { + get { return this.Vertices.Count / sizeof(float) / 3; } + } + + /// + /// The number of facets represeted by the buffer. + /// + public int FacetCount + { + get { return this.Indices.Count / sizeof(ushort) / 3; } + } + /// /// A collection of vertex positions stored as sequential bytes. /// diff --git a/Elements/src/Geometry/Tessellation/Tessellation.cs b/Elements/src/Geometry/Tessellation/Tessellation.cs index 295ea79d8..aa7c3e3dc 100644 --- a/Elements/src/Geometry/Tessellation/Tessellation.cs +++ b/Elements/src/Geometry/Tessellation/Tessellation.cs @@ -31,16 +31,16 @@ internal static T Tessellate(IEnumerable provide } // Pre-allocate a buffer big enough to hold all the tessellations - var buffers = (IGraphicsBuffers)Activator.CreateInstance(typeof(T)); - buffers.Initialize(tesses.Sum(tess => tess.VertexCount), tesses.Sum(tess => tess.Elements.Length)); + 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) { - PackTessellationIntoBuffers(tess, buffers, modifyVertexAttributes, ref indexOffset); + PackTessellationIntoBuffers(tess, buffer, modifyVertexAttributes, ref indexOffset); } - return (T)buffers; + return (T)buffer; } private static void PackTessellationIntoBuffers(Tess tess, @@ -102,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/Serialization/glTF/GltfExtensions.cs b/Elements/src/Serialization/glTF/GltfExtensions.cs index efefc2257..f2bea2009 100644 --- a/Elements/src/Serialization/glTF/GltfExtensions.cs +++ b/Elements/src/Serialization/glTF/GltfExtensions.cs @@ -1131,8 +1131,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 }); @@ -1154,8 +1153,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 }); @@ -1365,8 +1363,7 @@ private static int ProcessGeometricRepresentation(Element e, List nodes, string materialId, ref int meshId, - GeometricElement geometricElement, - bool mergeVertices = false) + GeometricElement geometricElement) { geometricElement.UpdateRepresentations(); @@ -1389,8 +1386,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. @@ -1416,8 +1412,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) 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; } From da8ef112867acf23d02e681c96210c958cc9a19f Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Fri, 5 Aug 2022 13:03:09 -0400 Subject: [PATCH 13/24] use octree to speed up mesh ray intersection --- Elements/src/Geometry/BBox3.cs | 3 +- Elements/src/Geometry/Mesh.cs | 90 ++++++++++++++++++++++++++++++++-- Elements/src/Geometry/Ray.cs | 28 +++++++---- Elements/test/RayTests.cs | 28 +++++++++++ 4 files changed, 134 insertions(+), 15 deletions(-) diff --git a/Elements/src/Geometry/BBox3.cs b/Elements/src/Geometry/BBox3.cs index bfac4a7aa..f21c52357 100644 --- a/Elements/src/Geometry/BBox3.cs +++ b/Elements/src/Geometry/BBox3.cs @@ -90,7 +90,7 @@ public BBox3(IEnumerable points) } } - internal void Extend(Vector3 v) + internal BBox3 Extend(Vector3 v) { var newMin = new Vector3(Min.X, Min.Y, Min.Z); if (v.X < this.Min.X) newMin.X = v.X; @@ -103,6 +103,7 @@ internal void Extend(Vector3 v) if (v.Y > this.Max.Y) newMax.Y = v.Y; if (v.Z > this.Max.Z) newMax.Z = v.Z; this.Max = newMax; + return this; } /// diff --git a/Elements/src/Geometry/Mesh.cs b/Elements/src/Geometry/Mesh.cs index c2ac78b1b..b5044d1bc 100644 --- a/Elements/src/Geometry/Mesh.cs +++ b/Elements/src/Geometry/Mesh.cs @@ -16,16 +16,24 @@ namespace Elements.Geometry [JsonConverter(typeof(MeshConverter))] public partial class Mesh { + + private double _maxTriangleSize = 0; private PointOctree _octree = new PointOctree(100000, new Octree.Point(0f, 0f, 0f), (float)Vector3.EPSILON); - /// The mesh' vertices. + /// The mesh's vertices. [JsonProperty("Vertices", Required = Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public IList Vertices { get; set; } - /// The mesh' triangles. + /// The mesh's triangles. [JsonProperty("Triangles", Required = Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public IList Triangles { get; set; } + private BBox3 _bbox = new BBox3(); + /// + /// The mesh's bounding box. + /// + public BBox3 BoundingBox => _bbox; + /// /// Construct a mesh. /// @@ -34,8 +42,16 @@ public partial class Mesh [JsonConstructor] public Mesh(IList @vertices, IList @triangles) { - this.Vertices = @vertices; - this.Triangles = @triangles; + Vertices = new List(); + Triangles = new List(); + foreach (var v in @vertices) + { + AddVertex(v); + } + foreach (var t in @triangles) + { + AddTriangle(t); + } } /// @@ -186,6 +202,14 @@ public Triangle AddTriangle(Vertex a, Vertex b, Vertex c) { throw new ArgumentException($"Not a valid Triangle. Duplicate vertex at {duplicate}."); } + for (int i = 0; i < 3; i++) + { + var sideLength = t.Vertices[i].Position.DistanceTo(t.Vertices[(i + 1) % 3].Position); + if (sideLength > this._maxTriangleSize) + { + this._maxTriangleSize = sideLength; + } + } this.Triangles.Add(t); return t; } @@ -200,6 +224,14 @@ public Triangle AddTriangle(Triangle t) { throw new ArgumentException($"Not a valid Triangle. Duplicate vertex at {duplicate}."); } + for (int i = 0; i < 3; i++) + { + var sideLength = t.Vertices[i].Position.DistanceTo(t.Vertices[(i + 1) % 3].Position); + if (sideLength > this._maxTriangleSize) + { + this._maxTriangleSize = sideLength; + } + } this.Triangles.Add(t); return t; } @@ -223,7 +255,7 @@ public Vertex AddVertex(Vector3 position, bool merge = false, double edgeAngle = 30.0) { - var p = new Octree.Point((float)position.X, (float)position.Y, (float)position.Z); + var p = position.ToOctreePoint(); if (merge) { @@ -243,6 +275,7 @@ public Vertex AddVertex(Vector3 position, this.Vertices.Add(v); v.Index = (this.Vertices.Count) - 1; this._octree.Add(v, p); + this._bbox = this._bbox.Extend(v.Position); return v; } @@ -253,6 +286,8 @@ public Vertex AddVertex(Vector3 position, public Vertex AddVertex(Vertex v) { this.Vertices.Add(v); + this._octree.Add(v, v.Position.ToOctreePoint()); + this._bbox = this._bbox.Extend(v.Position); v.Index = (this.Vertices.Count) - 1; return v; } @@ -335,6 +370,28 @@ public List GetNakedBoundaries() return polygons; } + /// + /// Does the provided ray intersect this mesh mesh? + /// + /// The Ray to intersect. + /// The location of intersection. + /// True if an intersection result occurs. + /// False if no intersection occurs. + public bool Intersects(Ray ray, out Vector3 intersection) + { + var nearbyVertices = this._octree.GetNearby(ray.ToOctreeRay(), (float)_maxTriangleSize).ToList(); + var nearbyTriangles = nearbyVertices.SelectMany(v => v.Triangles).Distinct(); + intersection = default; + foreach (var t in nearbyTriangles) + { + if (ray.Intersects(t, out intersection)) + { + return true; + } + } + return false; + } + private double SignedVolumeOfTriangle(Triangle t) { var p1 = t.Vertices[0].Position; @@ -404,4 +461,27 @@ internal static Mesh ToMesh(this Tess tess, } } + + internal static class OctreeExtensions + { + internal static Octree.Ray ToOctreeRay(this Ray ray) + { + return new Octree.Ray(ray.Origin.ToOctreePoint(), ray.Direction.ToOctreePoint()); + } + + internal static Octree.Point ToOctreePoint(this Vector3 point) + { + return new Octree.Point((float)point.X, (float)point.Y, (float)point.Z); + } + + internal static BBox3 ToBbox3(this Octree.BoundingBox bbox) + { + return new BBox3(bbox.Min.ToVector3(), bbox.Max.ToVector3()); + } + + internal static Vector3 ToVector3(this Octree.Point p) + { + return new Vector3(p.X, p.Y, p.Z); + } + } } \ No newline at end of file diff --git a/Elements/src/Geometry/Ray.cs b/Elements/src/Geometry/Ray.cs index 0835beb41..753c4568e 100644 --- a/Elements/src/Geometry/Ray.cs +++ b/Elements/src/Geometry/Ray.cs @@ -282,15 +282,7 @@ public bool Intersects(Topography topo, out Vector3 result) public bool Intersects(Mesh mesh, out Vector3 result) { - result = default; - foreach (var t in mesh.Triangles) - { - if (this.Intersects(t, out result)) - { - return true; - } - } - return false; + return mesh.Intersects(this, out result); } /// @@ -370,6 +362,24 @@ public bool Intersects(Vector3 start, Vector3 end, out Vector3 result) return false; } + /// + /// Find points in the collection that are within the provided distance of this ray. + /// + /// The collection of points to search + /// The maximum distance from the ray. + /// Points that are within the given distance of the ray. + public Vector3[] NearbyPoints(IEnumerable points, double distance) + { + // TODO: calibrate these values + var octree = new Octree.PointOctree(10000, new Octree.Point(0f, 0f, 0f), (float)Vector3.EPSILON * 100); + foreach (var point in points) + { + octree.Add(point, point.ToOctreePoint()); + } + var nearbyPoints = octree.GetNearby(this.ToOctreeRay(), (float)distance); + return nearbyPoints; + } + /// /// Is this ray equal to the provided ray? /// diff --git a/Elements/test/RayTests.cs b/Elements/test/RayTests.cs index 6f0163a32..25abf2b8f 100644 --- a/Elements/test/RayTests.cs +++ b/Elements/test/RayTests.cs @@ -302,6 +302,34 @@ private static void RayIntersectsGeometryWithTransformation() Assert.True(ray.Intersects(mass, out var _)); } + [Fact] + private void RayNearbyPoints() + { + Name = nameof(RayNearbyPoints); + var points = new List(); + var random = new Random(1); + for (int i = 0; i < 1000; i++) + { + var point = new Vector3(random.NextDouble() * 10, random.NextDouble() * 10, random.NextDouble() * 10); + points.Add(point); + } + var modelpts = new ModelPoints(points, BuiltInMaterials.ZAxis); + Model.AddElement(modelpts); + var ray = new Ray((0, 0, 0), new Vector3(1, 1, 1)); + var nearbyPoints = ray.NearbyPoints(points, 1); + var rayAsLine = new Line((0, 0, 0), (10, 10, 10)); + Model.AddElement(rayAsLine); + foreach (var p in nearbyPoints) + { + var distance = p.DistanceTo(rayAsLine, out var pt); + var line = new Line(p, pt); + var mc = new ModelCurve(line, BuiltInMaterials.XAxis); + Model.AddElement(mc); + Assert.True(distance < 1); + } + + } + private static Vector3 Center(Triangle t) { return new Vector3[] { t.Vertices[0].Position, t.Vertices[1].Position, t.Vertices[2].Position }.Average(); From 9b853199e8bf9343f8b1a88fdf718e0eaa813af7 Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Fri, 5 Aug 2022 14:51:50 -0400 Subject: [PATCH 14/24] add benchmark --- Elements.Benchmarks/Mesh.cs | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Elements.Benchmarks/Mesh.cs diff --git a/Elements.Benchmarks/Mesh.cs b/Elements.Benchmarks/Mesh.cs new file mode 100644 index 000000000..dd7887498 --- /dev/null +++ b/Elements.Benchmarks/Mesh.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using Elements.Geometry; +using Elements.Serialization.glTF; + +namespace Elements.Benchmarks +{ + public class Mesh + { + private Geometry.Mesh _mesh; + private List _rays; + public Mesh() + { + var random = new Random(10); + _mesh = new Geometry.Mesh(); + _rays = new List(); + var xCount = 100; + var yCount = 300; + for (int i = 0; i < xCount; i++) + { + for (int j = 0; j < yCount; j++) + { + var point = new Vector3(i, j, random.NextDouble() * 2); + var c = _mesh.AddVertex(point); + if (i != 0 && j != 0) + { + // add faces + var d = _mesh.Vertices[i * yCount + j - 1]; + var a = _mesh.Vertices[(i - 1) * yCount + j - 1]; + var b = _mesh.Vertices[(i - 1) * yCount + j]; + _mesh.AddTriangle(a, b, c); + _mesh.AddTriangle(c, d, a); + } + } + } + + // create 1000 random rays + for (int i = 0; i < 1000; i++) + { + var ray = new Ray(new Vector3(random.NextDouble() * xCount, random.NextDouble() * yCount, 2.1), new Vector3(random.NextDouble() * 2 - 1, random.NextDouble() * 2 - 1, -1)); + _rays.Add(ray); + } + } + + [Benchmark(Description = "Intersect 1000 rays with mesh.")] + public void IntersectRays() + { + foreach (var ray in _rays) + { + ray.Intersects(_mesh, out var _); + } + } + } +} \ No newline at end of file From b0b0d8f772ce4b3acca7162f2f50db5a0a086478 Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Fri, 5 Aug 2022 14:58:01 -0400 Subject: [PATCH 15/24] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33ad48848..73e31dcec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,12 @@ ## 1.2.0 +### Added +- `Mesh.Intersects(Ray)` (same as `Ray.Intersects(Mesh)`) +- `Ray.NearestPoints()` ### Changed - MeshElement constructor signature modified to be compatible with code generation. +- Improved performance of mesh/ray intersection ## 1.1.0 From 1b2d0988a7c9cdc0e41b2a33dacf00b36cacc62a Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Fri, 5 Aug 2022 14:59:21 -0400 Subject: [PATCH 16/24] fix changelog naming --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73e31dcec..cd184743f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added - `Mesh.Intersects(Ray)` (same as `Ray.Intersects(Mesh)`) -- `Ray.NearestPoints()` +- `Ray.NearbyPoints()` ### Changed - MeshElement constructor signature modified to be compatible with code generation. - Improved performance of mesh/ray intersection From e522942b3dc97e24f324924eef1129a6c610b312 Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Sun, 7 Aug 2022 06:33:19 -0400 Subject: [PATCH 17/24] Add support for dash settings --- Elements/src/EdgeDisplaySettings.cs | 34 +++++++++++++++++++ .../src/Serialization/glTF/GltfExtensions.cs | 2 ++ Elements/test/ModelCurveTests.cs | 4 +-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Elements/src/EdgeDisplaySettings.cs b/Elements/src/EdgeDisplaySettings.cs index 94300b1ee..ec11b9241 100644 --- a/Elements/src/EdgeDisplaySettings.cs +++ b/Elements/src/EdgeDisplaySettings.cs @@ -14,6 +14,21 @@ public class EdgeDisplaySettings /// How the Width should be interpreted. If set to Screen Units, Width is interpreted as a constant pixel width (and rounded to the nearest integer). If set to World Units, Width is interpreted as a constant meter width. /// public EdgeDisplayWidthMode WidthMode { get; set; } = EdgeDisplayWidthMode.ScreenUnits; + + /// + /// Whether and how to display dashes along the line. + /// + public EdgeDisplayDashMode DashMode { get; set; } = EdgeDisplayDashMode.None; + + /// + /// The size of the dash. If Mode is set to None, this value will be ignored. Note that the units for this value (screen vs world) are affected by the choice of Dash Mode. + /// + public double DashSize { get; set; } = 1; + + /// + /// The size of the gaps between dashes. If Mode is set to None, this value will be ignored. If this value is set to null, DashSize will be used. Note that the units for this value (screen vs world) are affected by the choice of Dash Mode. + /// + public double? GapSize { get; set; } = 1; } /// @@ -30,4 +45,23 @@ public enum EdgeDisplayWidthMode /// WorldUnits = 1, } + + /// + /// Different ways to interpret the Width property of a EdgeDisplaySettings. + /// + public enum EdgeDisplayDashMode + { + /// + /// Dashed display is not enabled. Dash size is ignored. + /// + None = 0, + /// + /// Dash sizes are specified in pixels, and maintain a constant size when zooming. + /// + ScreenUnits = 1, + /// + /// Dash sizes are specified in meters, and maintain a constant size relative to the model. + /// + WorldUnits = 2, + } } \ No newline at end of file diff --git a/Elements/src/Serialization/glTF/GltfExtensions.cs b/Elements/src/Serialization/glTF/GltfExtensions.cs index e52cd9c30..ad663ad23 100644 --- a/Elements/src/Serialization/glTF/GltfExtensions.cs +++ b/Elements/src/Serialization/glTF/GltfExtensions.cs @@ -232,6 +232,8 @@ internal static Dictionary AddMaterials(this Gltf gltf, AddExtension(gltf, gltfMaterial, "HYPAR_materials_edge_settings", new Dictionary{ {"lineWidth", material.EdgeDisplaySettings.LineWidth}, {"widthMode", (int)material.EdgeDisplaySettings.WidthMode}, + {"dashMode", (int)material.EdgeDisplaySettings.DashMode}, + {"dashSize", material.EdgeDisplaySettings.DashSize} }); } diff --git a/Elements/test/ModelCurveTests.cs b/Elements/test/ModelCurveTests.cs index 88ba4e4d3..aa9faba3e 100644 --- a/Elements/test/ModelCurveTests.cs +++ b/Elements/test/ModelCurveTests.cs @@ -71,9 +71,9 @@ public void ModelCurveWithLineWeights() var ctrlPts = new List { a, b, c, d, e, f }; var bezier = new Bezier(ctrlPts); - var lineModelCurve = new ModelCurve(line, new Material("Red", Colors.Red) { EdgeDisplaySettings = new EdgeDisplaySettings { LineWidth = 5 } }); + var lineModelCurve = new ModelCurve(line, new Material("Red", Colors.Red) { EdgeDisplaySettings = new EdgeDisplaySettings { LineWidth = 5, DashMode = EdgeDisplayDashMode.WorldUnits, DashSize = 0.1 } }); var arcModelCurve = new ModelCurve(arc, new Material("Orange", Colors.Orange) { EdgeDisplaySettings = new EdgeDisplaySettings { LineWidth = 0.1, WidthMode = EdgeDisplayWidthMode.WorldUnits } }, new Transform(5, 0, 0)); - var plineModelCurve = new ModelCurve(pline, new Material("Purple", Colors.Purple) { EdgeDisplaySettings = new EdgeDisplaySettings { LineWidth = 10, WidthMode = EdgeDisplayWidthMode.ScreenUnits } }, new Transform(10, 0, 0)); + var plineModelCurve = new ModelCurve(pline, new Material("Purple", Colors.Purple) { EdgeDisplaySettings = new EdgeDisplaySettings { LineWidth = 10, WidthMode = EdgeDisplayWidthMode.ScreenUnits, DashMode = EdgeDisplayDashMode.ScreenUnits, DashSize = 10, GapSize = 4 } }, new Transform(10, 0, 0)); var bezierModelCurve = new ModelCurve(bezier, new Material("Green", Colors.Green) { EdgeDisplaySettings = new EdgeDisplaySettings { LineWidth = 1, WidthMode = EdgeDisplayWidthMode.WorldUnits } }, new Transform(15, 0, 0)); // From 37f2ab8bf3eed0127ae81f33b49960b24673876c Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Mon, 8 Aug 2022 07:32:40 -0400 Subject: [PATCH 18/24] include Triangle validation under validation flag --- Elements/src/Geometry/Mesh.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Elements/src/Geometry/Mesh.cs b/Elements/src/Geometry/Mesh.cs index c2ac78b1b..01e37321d 100644 --- a/Elements/src/Geometry/Mesh.cs +++ b/Elements/src/Geometry/Mesh.cs @@ -182,7 +182,7 @@ public GraphicsBuffers GetBuffers() public Triangle AddTriangle(Vertex a, Vertex b, Vertex c) { var t = new Triangle(a, b, c); - if (t.HasDuplicatedVertices(out Vector3 duplicate)) + if (!Validators.Validator.DisableValidationOnConstruction && t.HasDuplicatedVertices(out Vector3 duplicate)) { throw new ArgumentException($"Not a valid Triangle. Duplicate vertex at {duplicate}."); } From 5aa1cb57e34d9c5b458bfb17e82ce5460bf29eba Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Mon, 8 Aug 2022 09:44:20 -0400 Subject: [PATCH 19/24] expose octree --- CHANGELOG.md | 1 + Elements.Benchmarks/Mesh.cs | 68 +++++++++++----- Elements/src/Geometry/Line.cs | 4 +- Elements/src/Geometry/Mesh.cs | 70 ++++++++-------- Elements/src/Geometry/Ray.cs | 7 +- Elements/src/Search/Octree.cs | 146 ++++++++++++++++++++++++++++++++++ 6 files changed, 232 insertions(+), 64 deletions(-) create mode 100644 Elements/src/Search/Octree.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index cd184743f..126fc7080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - `Mesh.Intersects(Ray)` (same as `Ray.Intersects(Mesh)`) - `Ray.NearbyPoints()` +- `PointOctree` ### Changed - MeshElement constructor signature modified to be compatible with code generation. - Improved performance of mesh/ray intersection diff --git a/Elements.Benchmarks/Mesh.cs b/Elements.Benchmarks/Mesh.cs index dd7887498..16ca28b98 100644 --- a/Elements.Benchmarks/Mesh.cs +++ b/Elements.Benchmarks/Mesh.cs @@ -7,34 +7,19 @@ namespace Elements.Benchmarks { - public class Mesh + public class MeshRayIntersection { - private Geometry.Mesh _mesh; + private Mesh _mesh; private List _rays; - public Mesh() + + public MeshRayIntersection() { var random = new Random(10); - _mesh = new Geometry.Mesh(); + _mesh = new Mesh(); _rays = new List(); var xCount = 100; var yCount = 300; - for (int i = 0; i < xCount; i++) - { - for (int j = 0; j < yCount; j++) - { - var point = new Vector3(i, j, random.NextDouble() * 2); - var c = _mesh.AddVertex(point); - if (i != 0 && j != 0) - { - // add faces - var d = _mesh.Vertices[i * yCount + j - 1]; - var a = _mesh.Vertices[(i - 1) * yCount + j - 1]; - var b = _mesh.Vertices[(i - 1) * yCount + j]; - _mesh.AddTriangle(a, b, c); - _mesh.AddTriangle(c, d, a); - } - } - } + MeshConstruction.BuildRandomMesh(_mesh, random, xCount, yCount); // create 1000 random rays for (int i = 0; i < 1000; i++) @@ -44,6 +29,7 @@ public Mesh() } } + [Benchmark(Description = "Intersect 1000 rays with mesh.")] public void IntersectRays() { @@ -53,4 +39,44 @@ public void IntersectRays() } } } + public class MeshConstruction + { + public static void BuildRandomMesh(Mesh m, Random random, int xCount, int yCount) + { + for (int i = 0; i < xCount; i++) + { + for (int j = 0; j < yCount; j++) + { + var point = new Vector3(i, j, random.NextDouble() * 2); + var c = m.AddVertex(point); + if (i != 0 && j != 0) + { + // add faces + var d = m.Vertices[i * yCount + j - 1]; + var a = m.Vertices[(i - 1) * yCount + j - 1]; + var b = m.Vertices[(i - 1) * yCount + j]; + m.AddTriangle(a, b, c); + m.AddTriangle(c, d, a); + } + } + } + } + + [Params(true, false)] + public bool BuildOctree { get; set; } + + [Params(1000, 5000, 10000, 30000)] + public int VertexCount { get; set; } + + [Benchmark(Description = "Construct Mesh")] + public void ConstructMesh() + { + var mesh = new Mesh + { + BuildOctree = BuildOctree + }; + BuildRandomMesh(mesh, new Random(10), 100, VertexCount / 100); + } + + } } \ No newline at end of file diff --git a/Elements/src/Geometry/Line.cs b/Elements/src/Geometry/Line.cs index bcc116f23..e5a3f3bc0 100644 --- a/Elements/src/Geometry/Line.cs +++ b/Elements/src/Geometry/Line.cs @@ -1,5 +1,3 @@ -using System.Net.Sockets; -using System.Numerics; using Elements.Validators; using System; using System.Collections.Generic; @@ -510,7 +508,7 @@ public static bool PointOnLine(Vector3 point, Vector3 start, Vector3 end, bool i var delta = end - start; var lambda = (point - start).Dot(delta) / (end - start).Dot(delta); - if( lambda > 0 && lambda < 1) + if (lambda > 0 && lambda < 1) { var pointOnLine = start + lambda * delta; return pointOnLine.IsAlmostEqualTo(point); diff --git a/Elements/src/Geometry/Mesh.cs b/Elements/src/Geometry/Mesh.cs index 9e9c61637..0e0d2402d 100644 --- a/Elements/src/Geometry/Mesh.cs +++ b/Elements/src/Geometry/Mesh.cs @@ -1,7 +1,7 @@ +using Elements.Search; using Elements.Serialization.JSON; using LibTessDotNet.Double; using Newtonsoft.Json; -using Octree; using System; using System.Collections.Generic; using System.IO; @@ -18,7 +18,7 @@ public partial class Mesh { private double _maxTriangleSize = 0; - private PointOctree _octree = new PointOctree(100000, new Octree.Point(0f, 0f, 0f), (float)Vector3.EPSILON); + private readonly PointOctree _octree = new PointOctree(100000, (0, 0, 0), Vector3.EPSILON); /// The mesh's vertices. [JsonProperty("Vertices", Required = Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] @@ -34,6 +34,16 @@ public partial class Mesh /// public BBox3 BoundingBox => _bbox; + /// + /// When constructing meshes, by default we build an octree lookup + /// structure of the mesh's vertices, in order to speed up coincident + /// vertex checks and intersections. This can slow down extremely large + /// meshes — disable this setting to speed up mesh construction if you + /// won't be using `Intersects()` or the `merge` flag on `AddVertex`. + /// + [JsonIgnore] + public bool BuildOctree { get; set; } = true; + /// /// Construct a mesh. /// @@ -255,26 +265,32 @@ public Vertex AddVertex(Vector3 position, bool merge = false, double edgeAngle = 30.0) { - var p = position.ToOctreePoint(); - - if (merge) + var v = new Vertex(position, normal, color); + if (BuildOctree) { - var search = this._octree.GetNearby(p, (float)Vector3.EPSILON); - if (search.Length > 0) + + if (merge) { - var angle = search[0].Normal.AngleTo(normal); - if (angle < edgeAngle) + var search = this._octree.GetNearby(position, Vector3.EPSILON); + if (search.Length > 0) { - return search[0]; + var angle = search[0].Normal.AngleTo(normal); + if (angle < edgeAngle) + { + return search[0]; + } } } + this._octree.Add(v, position); + } + else if (merge) + { + throw new ArgumentException("Cannot merge vertices if BuildOctree is false. Before adding any vertices to this mesh, set BuildOctree to true."); } - var v = new Vertex(position, normal, color); v.UV = uv; this.Vertices.Add(v); v.Index = (this.Vertices.Count) - 1; - this._octree.Add(v, p); this._bbox = this._bbox.Extend(v.Position); return v; } @@ -286,7 +302,10 @@ public Vertex AddVertex(Vector3 position, public Vertex AddVertex(Vertex v) { this.Vertices.Add(v); - this._octree.Add(v, v.Position.ToOctreePoint()); + if (BuildOctree) + { + this._octree.Add(v, v.Position); + } this._bbox = this._bbox.Extend(v.Position); v.Index = (this.Vertices.Count) - 1; return v; @@ -379,7 +398,7 @@ public List GetNakedBoundaries() /// False if no intersection occurs. public bool Intersects(Ray ray, out Vector3 intersection) { - var nearbyVertices = this._octree.GetNearby(ray.ToOctreeRay(), (float)_maxTriangleSize).ToList(); + var nearbyVertices = this._octree.GetNearby(ray, _maxTriangleSize).ToList(); var nearbyTriangles = nearbyVertices.SelectMany(v => v.Triangles).Distinct(); intersection = default; foreach (var t in nearbyTriangles) @@ -461,27 +480,4 @@ internal static Mesh ToMesh(this Tess tess, } } - - internal static class OctreeExtensions - { - internal static Octree.Ray ToOctreeRay(this Ray ray) - { - return new Octree.Ray(ray.Origin.ToOctreePoint(), ray.Direction.ToOctreePoint()); - } - - internal static Octree.Point ToOctreePoint(this Vector3 point) - { - return new Octree.Point((float)point.X, (float)point.Y, (float)point.Z); - } - - internal static BBox3 ToBbox3(this Octree.BoundingBox bbox) - { - return new BBox3(bbox.Min.ToVector3(), bbox.Max.ToVector3()); - } - - internal static Vector3 ToVector3(this Octree.Point p) - { - return new Vector3(p.X, p.Y, p.Z); - } - } } \ No newline at end of file diff --git a/Elements/src/Geometry/Ray.cs b/Elements/src/Geometry/Ray.cs index 753c4568e..28358e47c 100644 --- a/Elements/src/Geometry/Ray.cs +++ b/Elements/src/Geometry/Ray.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Elements.Geometry.Solids; +using Elements.Search; namespace Elements.Geometry { @@ -371,12 +372,12 @@ public bool Intersects(Vector3 start, Vector3 end, out Vector3 result) public Vector3[] NearbyPoints(IEnumerable points, double distance) { // TODO: calibrate these values - var octree = new Octree.PointOctree(10000, new Octree.Point(0f, 0f, 0f), (float)Vector3.EPSILON * 100); + var octree = new PointOctree(10000, (0, 0, 0), (float)Vector3.EPSILON * 100); foreach (var point in points) { - octree.Add(point, point.ToOctreePoint()); + octree.Add(point, point); } - var nearbyPoints = octree.GetNearby(this.ToOctreeRay(), (float)distance); + var nearbyPoints = octree.GetNearby(this, (float)distance); return nearbyPoints; } diff --git a/Elements/src/Search/Octree.cs b/Elements/src/Search/Octree.cs new file mode 100644 index 000000000..909a6bd77 --- /dev/null +++ b/Elements/src/Search/Octree.cs @@ -0,0 +1,146 @@ +using o = Octree; +using Elements.Geometry; +using System.Collections.Generic; + +namespace Elements.Search +{ + /// + /// A Dynamic Octree for storing any objects that can be described as a single point. This is a thin wrapper around the PointOctree class from NetOctree (https://github.com/mcserep/NetOctree). + /// + /// + /// Octree: An octree is a tree data structure which divides 3D space into smaller partitions (nodes) + /// and places objects into the appropriate nodes. This allows fast access to objects + /// in an area of interest without having to check every object. + /// + /// Dynamic: The octree grows or shrinks as required when objects as added or removed. + /// It also splits and merges nodes as appropriate. There is no maximum depth. + /// + /// The content of the octree can be anything, since the bounds data is supplied separately. + public class PointOctree + { + private readonly o.PointOctree _octree; + + /// + /// Constructor for the point octree. + /// + /// Size of the sides of the initial node. The octree will never shrink smaller than this. + /// Position of the center of the initial node. + /// Nodes will stop splitting if the new nodes would be smaller than this. + public PointOctree(double initialWorldSize, Vector3 initialWorldPos, double minNodeSize) + { + _octree = new o.PointOctree((float)initialWorldSize, initialWorldPos.ToOctreePoint(), (float)minNodeSize); + } + + /// + /// Returns all objects in the tree. + /// If none, returns an empty array (not null). + /// + /// All objects. + public ICollection GetAll() + { + return _octree.GetAll(); + } + + /// + /// Add an object. + /// + /// Object to add. + /// Position of the object. + public void Add(T obj, Vector3 objPos) + { + _octree.Add(obj, objPos.ToOctreePoint()); + } + + /// + /// Returns objects that are within of the specified ray. + /// If none, returns an empty array (not null). + /// + /// The ray. + /// Maximum distance from the ray to consider. + /// Objects within range. + public T[] GetNearby(Ray ray, double maxDistance) + { + return _octree.GetNearby(ray.ToOctreeRay(), (float)maxDistance); + } + + /// + /// Returns objects that are within of the specified position. + /// If none, returns an empty array (not null). + /// + /// The position. Passing as ref to improve performance since it won't have to be copied. + /// Maximum distance from the position to consider. + /// Objects within range. + public T[] GetNearby(Vector3 position, double maxDistance) + { + return _octree.GetNearby(position.ToOctreePoint(), (float)maxDistance); + } + + /// + /// The total amount of objects currently in the tree + /// + public int Count + { + get + { + return _octree.Count; + } + } + + /// + /// Gets the bounding box that represents the whole octree + /// + /// The bounding box of the root node. + public BBox3 MaxBounds + { + get + { + return _octree.MaxBounds.ToBbox3(); + } + } + + /// + /// Remove an object. Makes the assumption that the object only exists once in the tree. + /// + /// Object to remove. + /// True if the object was removed successfully. + public bool Remove(T obj) + { + return _octree.Remove(obj); + } + + /// + /// Removes the specified object at the given position. Makes the assumption that the object only exists once in the tree. + /// + /// Object to remove. + /// Position of the object. + /// True if the object was removed successfully. + public bool Remove(T obj, Vector3 objPos) + { + return _octree.Remove(obj, objPos.ToOctreePoint()); + } + } + + internal static class OctreeExtensions + { + internal static o.Ray ToOctreeRay(this Ray ray) + { + return new o.Ray(ray.Origin.ToOctreePoint(), ray.Direction.ToOctreePoint()); + } + + internal static o.Point ToOctreePoint(this Vector3 point) + { + return new o.Point((float)point.X, (float)point.Y, (float)point.Z); + } + + internal static BBox3 ToBbox3(this o.BoundingBox bbox) + { + return new BBox3(bbox.Min.ToVector3(), bbox.Max.ToVector3()); + } + + internal static Vector3 ToVector3(this o.Point p) + { + return new Vector3(p.X, p.Y, p.Z); + } + } + +} \ No newline at end of file From 8b6840c8873c9748d65afcaf1f03d4fb4eee935f Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Mon, 8 Aug 2022 10:25:37 -0400 Subject: [PATCH 20/24] dont gitignore buildOctree --- Elements/src/Geometry/Mesh.cs | 25 ++++++++- Elements/test/MeshTests.cs | 95 ++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/Elements/src/Geometry/Mesh.cs b/Elements/src/Geometry/Mesh.cs index 0e0d2402d..a1019d933 100644 --- a/Elements/src/Geometry/Mesh.cs +++ b/Elements/src/Geometry/Mesh.cs @@ -41,7 +41,6 @@ public partial class Mesh /// meshes — disable this setting to speed up mesh construction if you /// won't be using `Intersects()` or the `merge` flag on `AddVertex`. /// - [JsonIgnore] public bool BuildOctree { get; set; } = true; /// @@ -285,7 +284,7 @@ public Vertex AddVertex(Vector3 position, } else if (merge) { - throw new ArgumentException("Cannot merge vertices if BuildOctree is false. Before adding any vertices to this mesh, set BuildOctree to true."); + throw new ArgumentException("Cannot merge vertices if `BuildOctree` is false. Before adding any vertices to this mesh, set `BuildOctree` to true, or run `Mesh.ForceBuildOctree()`."); } v.UV = uv; @@ -311,6 +310,24 @@ public Vertex AddVertex(Vertex v) return v; } + /// + /// If this mesh was loaded without an octree, construct one so that you + /// can utilize capabilities that rely on the octree, like + /// `Intersects()` and the merge option in `AddVertex()`. + /// + public void ForceBuildOctree() + { + if (this._octree.Count > 0) + { + throw new Exception("Octree already built."); + } + this.BuildOctree = true; + foreach (var v in Vertices) + { + this._octree.Add(v, v.Position); + } + } + /// /// Calculate the volume of the mesh. /// This value will be inexact for open meshes. @@ -398,6 +415,10 @@ public List GetNakedBoundaries() /// False if no intersection occurs. public bool Intersects(Ray ray, out Vector3 intersection) { + if (!this.BuildOctree) + { + throw new Exception("The Intersects method is only available if you have constructed the mesh with `BuildOctree = true`, or run `Mesh.ForceBuildOctree()`."); + } var nearbyVertices = this._octree.GetNearby(ray, _maxTriangleSize).ToList(); var nearbyTriangles = nearbyVertices.SelectMany(v => v.Triangles).Distinct(); intersection = default; diff --git a/Elements/test/MeshTests.cs b/Elements/test/MeshTests.cs index 85c18175d..ea5abc416 100644 --- a/Elements/test/MeshTests.cs +++ b/Elements/test/MeshTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Elements.Geometry; using Elements.Geometry.Solids; @@ -7,7 +8,7 @@ namespace Elements.Tests { - public class MeshTests + public class MeshTests : ModelTest { [Fact] public void Volume() @@ -45,6 +46,98 @@ public void ReadMeshSerializedAsNull() Newtonsoft.Json.JsonConvert.DeserializeObject(json, new[] { new MeshConverter() }); } + + [Fact] + public void ThrowsForIntersectOrMergeWhenBuildOctreeFalse() + { + var _mesh = new Mesh() + { + BuildOctree = false + }; + var xCount = 10; + var yCount = 10; + var random = new Random(); + for (int i = 0; i < xCount; i++) + { + for (int j = 0; j < yCount; j++) + { + var point = new Vector3(i, j, random.NextDouble() * 2); + var c = _mesh.AddVertex(point); + if (i != 0 && j != 0) + { + // add faces + var d = _mesh.Vertices[i * yCount + j - 1]; + var a = _mesh.Vertices[(i - 1) * yCount + j - 1]; + var b = _mesh.Vertices[(i - 1) * yCount + j]; + _mesh.AddTriangle(a, b, c); + _mesh.AddTriangle(c, d, a); + } + } + } + var vertToAddWithMerge = _mesh.Vertices[10].Position; + Assert.Throws(() => + { + _mesh.AddVertex(vertToAddWithMerge, merge: true); + }); + + var ray = new Ray(new Vector3(5, 5, 5), new Vector3(0, 0, -1)); + Assert.Throws(() => + { + _mesh.Intersects(ray, out var pt); + }); + } + + [Fact] + public void IntersectsRays() + { + Name = nameof(IntersectsRays); + var random = new Random(10); + var _mesh = new Mesh(); + var _rays = new List(); + var xCount = 100; + var yCount = 100; + for (int i = 0; i < xCount; i++) + { + for (int j = 0; j < yCount; j++) + { + var point = new Vector3(i, j, random.NextDouble() * 2); + var c = _mesh.AddVertex(point); + if (i != 0 && j != 0) + { + // add faces + var d = _mesh.Vertices[i * yCount + j - 1]; + var a = _mesh.Vertices[(i - 1) * yCount + j - 1]; + var b = _mesh.Vertices[(i - 1) * yCount + j]; + _mesh.AddTriangle(a, b, c); + _mesh.AddTriangle(c, d, a); + } + } + } + + // create 1000 random rays + for (int i = 0; i < 1000; i++) + { + var ray = new Ray(new Vector3(random.NextDouble() * (xCount - 1), random.NextDouble() * (yCount - 1), 5), new Vector3(0, 0, -1)); + _rays.Add(ray); + Model.AddElement(new ModelCurve(new Line(ray.Origin, ray.Origin + ray.Direction * 0.1), BuiltInMaterials.XAxis)); + } + _mesh.ComputeNormals(); + Model.AddElement(new MeshElement(_mesh) { Material = new Material("b") { Color = (0.6, 0.6, 0.6, 1), DoubleSided = true } }); + + var pts = new List(); + + foreach (var ray in _rays) + { + if (ray.Intersects(_mesh, out var p)) + { + pts.Add(p); + var l = new Line(p, ray.Origin); + Model.AddElement(l); + } + } + + Assert.Equal(_rays.Count, pts.Count); + } public class InputsWithMesh { [JsonConstructor] From 5a8aa0a4b3681cde32ac52caf662353c86365ef2 Mon Sep 17 00:00:00 2001 From: Andrew Heumann Date: Mon, 8 Aug 2022 22:08:47 -0400 Subject: [PATCH 21/24] Build Octree on demand --- Elements.Benchmarks/Mesh.cs | 10 +---- Elements/src/Geometry/Mesh.cs | 70 ++++++++++++----------------------- Elements/test/MeshTests.cs | 45 ++-------------------- 3 files changed, 28 insertions(+), 97 deletions(-) diff --git a/Elements.Benchmarks/Mesh.cs b/Elements.Benchmarks/Mesh.cs index 16ca28b98..3ba82dbb5 100644 --- a/Elements.Benchmarks/Mesh.cs +++ b/Elements.Benchmarks/Mesh.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; using BenchmarkDotNet.Attributes; using Elements.Geometry; -using Elements.Serialization.glTF; namespace Elements.Benchmarks { @@ -62,19 +60,13 @@ public static void BuildRandomMesh(Mesh m, Random random, int xCount, int yCount } } - [Params(true, false)] - public bool BuildOctree { get; set; } - [Params(1000, 5000, 10000, 30000)] public int VertexCount { get; set; } [Benchmark(Description = "Construct Mesh")] public void ConstructMesh() { - var mesh = new Mesh - { - BuildOctree = BuildOctree - }; + var mesh = new Mesh(); BuildRandomMesh(mesh, new Random(10), 100, VertexCount / 100); } diff --git a/Elements/src/Geometry/Mesh.cs b/Elements/src/Geometry/Mesh.cs index a1019d933..ce5c1ffd1 100644 --- a/Elements/src/Geometry/Mesh.cs +++ b/Elements/src/Geometry/Mesh.cs @@ -18,7 +18,7 @@ public partial class Mesh { private double _maxTriangleSize = 0; - private readonly PointOctree _octree = new PointOctree(100000, (0, 0, 0), Vector3.EPSILON); + private PointOctree _octree = null; /// The mesh's vertices. [JsonProperty("Vertices", Required = Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] @@ -34,15 +34,6 @@ public partial class Mesh /// public BBox3 BoundingBox => _bbox; - /// - /// When constructing meshes, by default we build an octree lookup - /// structure of the mesh's vertices, in order to speed up coincident - /// vertex checks and intersections. This can slow down extremely large - /// meshes — disable this setting to speed up mesh construction if you - /// won't be using `Intersects()` or the `merge` flag on `AddVertex`. - /// - public bool BuildOctree { get; set; } = true; - /// /// Construct a mesh. /// @@ -265,27 +256,22 @@ public Vertex AddVertex(Vector3 position, double edgeAngle = 30.0) { var v = new Vertex(position, normal, color); - if (BuildOctree) - { - if (merge) + + if (merge) + { + var search = GetOctree().GetNearby(position, Vector3.EPSILON); + if (search.Length > 0) { - var search = this._octree.GetNearby(position, Vector3.EPSILON); - if (search.Length > 0) + var angle = search[0].Normal.AngleTo(normal); + if (angle < edgeAngle) { - var angle = search[0].Normal.AngleTo(normal); - if (angle < edgeAngle) - { - return search[0]; - } + return search[0]; } } - this._octree.Add(v, position); - } - else if (merge) - { - throw new ArgumentException("Cannot merge vertices if `BuildOctree` is false. Before adding any vertices to this mesh, set `BuildOctree` to true, or run `Mesh.ForceBuildOctree()`."); } + // If the octree is null, do nothing — we'll build it when we need it. If we've already constructed it, let's keep it up to date. + this._octree?.Add(v, position); v.UV = uv; this.Vertices.Add(v); @@ -301,31 +287,25 @@ public Vertex AddVertex(Vector3 position, public Vertex AddVertex(Vertex v) { this.Vertices.Add(v); - if (BuildOctree) - { - this._octree.Add(v, v.Position); - } + // If the octree is null, do nothing — we'll build it when we need it. If we've already constructed it, let's keep it up to date. + this._octree?.Add(v, v.Position); this._bbox = this._bbox.Extend(v.Position); v.Index = (this.Vertices.Count) - 1; return v; } - /// - /// If this mesh was loaded without an octree, construct one so that you - /// can utilize capabilities that rely on the octree, like - /// `Intersects()` and the merge option in `AddVertex()`. - /// - public void ForceBuildOctree() + private PointOctree GetOctree() { - if (this._octree.Count > 0) - { - throw new Exception("Octree already built."); - } - this.BuildOctree = true; - foreach (var v in Vertices) + if (_octree == null) { - this._octree.Add(v, v.Position); + _octree = new PointOctree(Math.Max(_bbox.Max.DistanceTo(_bbox.Min), 100), _bbox.PointAt(0.5, 0.5, 0.5), Vector3.EPSILON); + // make sure existing vertices are added to the octree — we're initializing it for the first time + foreach (var v in Vertices) + { + _octree.Add(v, v.Position); + } } + return _octree; } /// @@ -415,11 +395,7 @@ public List GetNakedBoundaries() /// False if no intersection occurs. public bool Intersects(Ray ray, out Vector3 intersection) { - if (!this.BuildOctree) - { - throw new Exception("The Intersects method is only available if you have constructed the mesh with `BuildOctree = true`, or run `Mesh.ForceBuildOctree()`."); - } - var nearbyVertices = this._octree.GetNearby(ray, _maxTriangleSize).ToList(); + var nearbyVertices = GetOctree().GetNearby(ray, _maxTriangleSize).ToList(); var nearbyTriangles = nearbyVertices.SelectMany(v => v.Triangles).Distinct(); intersection = default; foreach (var t in nearbyTriangles) diff --git a/Elements/test/MeshTests.cs b/Elements/test/MeshTests.cs index ea5abc416..c13cd3de1 100644 --- a/Elements/test/MeshTests.cs +++ b/Elements/test/MeshTests.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; using Elements.Geometry; using Elements.Geometry.Solids; using Elements.Serialization.JSON; @@ -46,47 +49,6 @@ public void ReadMeshSerializedAsNull() Newtonsoft.Json.JsonConvert.DeserializeObject(json, new[] { new MeshConverter() }); } - - [Fact] - public void ThrowsForIntersectOrMergeWhenBuildOctreeFalse() - { - var _mesh = new Mesh() - { - BuildOctree = false - }; - var xCount = 10; - var yCount = 10; - var random = new Random(); - for (int i = 0; i < xCount; i++) - { - for (int j = 0; j < yCount; j++) - { - var point = new Vector3(i, j, random.NextDouble() * 2); - var c = _mesh.AddVertex(point); - if (i != 0 && j != 0) - { - // add faces - var d = _mesh.Vertices[i * yCount + j - 1]; - var a = _mesh.Vertices[(i - 1) * yCount + j - 1]; - var b = _mesh.Vertices[(i - 1) * yCount + j]; - _mesh.AddTriangle(a, b, c); - _mesh.AddTriangle(c, d, a); - } - } - } - var vertToAddWithMerge = _mesh.Vertices[10].Position; - Assert.Throws(() => - { - _mesh.AddVertex(vertToAddWithMerge, merge: true); - }); - - var ray = new Ray(new Vector3(5, 5, 5), new Vector3(0, 0, -1)); - Assert.Throws(() => - { - _mesh.Intersects(ray, out var pt); - }); - } - [Fact] public void IntersectsRays() { @@ -138,6 +100,7 @@ public void IntersectsRays() Assert.Equal(_rays.Count, pts.Count); } + public class InputsWithMesh { [JsonConstructor] From ef4b8d2507d5a2f81798e23aadd91b10a88595af Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Mon, 8 Aug 2022 21:00:57 -0700 Subject: [PATCH 22/24] Use RotateAboutPoint. --- CHANGELOG.md | 1 - Elements/src/Geometry/Bezier.cs | 2 +- Elements/src/Geometry/Curve.cs | 2 +- Elements/src/Geometry/Polygon.cs | 2 +- Elements/src/Geometry/Polyline.cs | 8 ++++---- Elements/src/Geometry/Transform.cs | 20 -------------------- 6 files changed, 7 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1291de111..0d69be669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,6 @@ - `Polygon(bool disableValidation, params Vector3[] vertices)` - `Polyline(IList @vertices, bool disableValidation = false)` - `Polyline(bool disableValidation, params Vector3[] vertices)` -- `Transform.Rotate(Vector3 axis, double angle, Vector3 origin)` ### Changed - MeshElement constructor signature modified to be compatible with code generation. diff --git a/Elements/src/Geometry/Bezier.cs b/Elements/src/Geometry/Bezier.cs index 86a8b7f42..1d40eaaaf 100644 --- a/Elements/src/Geometry/Bezier.cs +++ b/Elements/src/Geometry/Bezier.cs @@ -85,7 +85,7 @@ public override Transform[] Frames(double startSetback = 0, transforms[i] = TransformAt(i * 1.0 / _samples); if (additionalRotation != 0.0) { - transforms[i].Rotate(transforms[i].ZAxis, additionalRotation, transforms[i].Origin); + transforms[i].RotateAboutPoint(transforms[i].Origin, transforms[i].ZAxis, additionalRotation); } } return transforms; diff --git a/Elements/src/Geometry/Curve.cs b/Elements/src/Geometry/Curve.cs index 6597793e9..3081063df 100644 --- a/Elements/src/Geometry/Curve.cs +++ b/Elements/src/Geometry/Curve.cs @@ -40,7 +40,7 @@ public virtual Transform[] Frames(double startSetback = 0.0, transforms[i] = TransformAt(parameters[i]); if (additionalRotation != 0.0) { - transforms[i].Rotate(transforms[i].ZAxis, additionalRotation, transforms[i].Origin); + transforms[i].RotateAboutPoint(transforms[i].Origin, transforms[i].ZAxis, additionalRotation); } } return transforms; diff --git a/Elements/src/Geometry/Polygon.cs b/Elements/src/Geometry/Polygon.cs index 0f7513186..ccc84e33e 100644 --- a/Elements/src/Geometry/Polygon.cs +++ b/Elements/src/Geometry/Polygon.cs @@ -2052,7 +2052,7 @@ public override Transform[] Frames(double startSetback = 0.0, result[i] = CreateMiterTransform(i, a, up); if (additionalRotation != 0.0) { - result[i].Rotate(result[i].ZAxis, additionalRotation, result[i].Origin); + result[i].RotateAboutPoint(result[i].Origin, result[i].ZAxis, additionalRotation); } } return result; diff --git a/Elements/src/Geometry/Polyline.cs b/Elements/src/Geometry/Polyline.cs index ab7660b28..ac7dc74b3 100644 --- a/Elements/src/Geometry/Polyline.cs +++ b/Elements/src/Geometry/Polyline.cs @@ -396,7 +396,7 @@ public override Transform[] Frames(double startSetback = 0.0, result[i] = CreateOrthogonalTransform(i, a, normals[i]); if (additionalRotation != 0.0) { - result[i].Rotate(result[i].ZAxis, additionalRotation, result[i].Origin); + result[i].RotateAboutPoint(result[i].Origin, result[i].ZAxis, additionalRotation); } } return result; @@ -1114,7 +1114,7 @@ public bool Intersects(Polygon polygon, out List sharedSegments) /// New polyline or null if any of points is not on polyline public Polyline GetSubsegment(Vector3 start, Vector3 end) { - if(start.IsAlmostEqualTo(end)) + if (start.IsAlmostEqualTo(end)) { return null; } @@ -1122,7 +1122,7 @@ public Polyline GetSubsegment(Vector3 start, Vector3 end) var startParameter = GetParameterAt(start); var endParameter = GetParameterAt(end); - if(startParameter < 0 || endParameter < 0) + if (startParameter < 0 || endParameter < 0) { return null; } @@ -1132,7 +1132,7 @@ public Polyline GetSubsegment(Vector3 start, Vector3 end) var vertices = new List(); var lastVertex = Vector3.Origin; - if(startParameter < endParameter) + if (startParameter < endParameter) { firstParameter = startParameter; lastParameter = endParameter; diff --git a/Elements/src/Geometry/Transform.cs b/Elements/src/Geometry/Transform.cs index 3720fa473..aa05b758b 100644 --- a/Elements/src/Geometry/Transform.cs +++ b/Elements/src/Geometry/Transform.cs @@ -388,26 +388,6 @@ public void Rotate(Vector3 axis, double angle) this.Matrix *= m; } - /// - /// Apply a rotation to the transform around a point. - /// - /// The axis of rotation. - /// The angle of rotation in degrees. - /// The point around which to rotate. - public void Rotate(Vector3 axis, double angle, Vector3 origin) - { - var mT1 = new Matrix(); - mT1.SetupTranslation(origin.Negate()); - - var mT2 = new Matrix(); - mT2.SetupTranslation(origin); - - var mR = new Matrix(); - mR.SetupRotate(axis, angle * (Math.PI / 180.0)); - - this.Matrix = this.Matrix * mT1 * mR * mT2; - } - /// /// Apply a rotation to the transform around the Z axis. /// From 59e4f4e1c62ef0b96da488fb732e2bd7850ea467 Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Mon, 8 Aug 2022 21:07:24 -0700 Subject: [PATCH 23/24] Pickup PR comment. --- Elements/src/Geometry/Polygons.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Elements/src/Geometry/Polygons.cs b/Elements/src/Geometry/Polygons.cs index d2e4f368c..df2920bfd 100644 --- a/Elements/src/Geometry/Polygons.cs +++ b/Elements/src/Geometry/Polygons.cs @@ -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(true, new[] { a, b, c, d }); + return new Polygon(true, a, b, c, d); } /// From 1ef7dce2097bfded5e7e27ce98a7c18c12d03e5d Mon Sep 17 00:00:00 2001 From: Ian Keough Date: Mon, 8 Aug 2022 21:08:51 -0700 Subject: [PATCH 24/24] Use params version everywhere. --- Elements/src/Geometry/Polygons.cs | 4 ++-- Elements/src/Geometry/Profiles/WideFlangeProfile.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Elements/src/Geometry/Polygons.cs b/Elements/src/Geometry/Polygons.cs index df2920bfd..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(true, new[] { a, b, c, d }); + return new Polygon(true, a, b, c, d); } /// @@ -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(true, new[] { a, b, c, d, e, f }); + return new Polygon(true, a, b, c, d, e, f); } /// diff --git a/Elements/src/Geometry/Profiles/WideFlangeProfile.cs b/Elements/src/Geometry/Profiles/WideFlangeProfile.cs index 49b7bffcb..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(false, 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