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