diff --git a/CHANGELOG.md b/CHANGELOG.md index a8a773806..ffc80fd0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ - `Profile.ThickenedEdgePolygons` - `Elements.MEP` - `GeometricElement.RepresentationInstances` +- `ContentRepresentation` ### Fixed @@ -59,6 +60,7 @@ - `Polygon.Frames` now works correctly with `startSetbackDistance` and `endSetbackDistance` parameters. - `Polyline.TransformAt` returns correct transformations when parameter on domain is provided. - `IndexedPolycurve` constructor that takes list of `BoundedCurve` now produces `CurveIndices` that share vertices and are withing index range. This means `IndexedPolyline.TransformedPolyline` preserves `CurveIndicies` on new `IndexedPolyline`. +- `BoundedCurve.ToPolyline` now works correctly for `EllipticalArc` class. ### Changed - `GltfExtensions.UseReferencedContentExtension` is now true by default. diff --git a/Elements/src/CoreModels/ElementRepresentation.cs b/Elements/src/CoreModels/ElementRepresentation.cs index bd18b5cd4..535badb4e 100644 --- a/Elements/src/CoreModels/ElementRepresentation.cs +++ b/Elements/src/CoreModels/ElementRepresentation.cs @@ -2,7 +2,7 @@ using Elements; using Elements.Geometry; using glTFLoader.Schema; -using System; +using Elements.Serialization.glTF; /// /// The element's representation @@ -13,13 +13,18 @@ public abstract class ElementRepresentation : SharedObject /// Get graphics buffers and other metadata required to modify a GLB. /// /// The element with this representation. - /// The list of graphc buffers. + /// The list of graphic buffers. /// The buffer id. It will be used as a primitive name. /// The gltf primitive mode /// - /// True if there is graphicsbuffers data applicable to add, false otherwise. + /// True if there is graphics buffers data applicable to add, false otherwise. /// Out variables should be ignored if the return value is false. /// public abstract bool TryToGraphicsBuffers(GeometricElement element, out List graphicsBuffers, out string id, out MeshPrimitive.ModeEnum? mode); + + internal virtual List GetNodeExtensions(GeometricElement element) + { + return new List(); + } } \ No newline at end of file diff --git a/Elements/src/GeometricElement.cs b/Elements/src/GeometricElement.cs index 173b3b7db..95f81e3dc 100644 --- a/Elements/src/GeometricElement.cs +++ b/Elements/src/GeometricElement.cs @@ -213,7 +213,6 @@ public bool Intersects(Plane plane, var graphVertices = new List(); var graphEdges = new List>(); - var intersectionPoints = new List(); var beyondPolygonsList = new List(); if (Representation != null && _csg != null) @@ -252,7 +251,7 @@ public bool Intersects(Plane plane, var d = csgNormal.Cross(plane.Normal).Unitized(); edgeResults.Sort(new DirectionComparer(d)); - intersectionPoints.AddRange(edgeResults); + AddToGraph(edgeResults, graphVertices, graphEdges); } } @@ -268,43 +267,15 @@ public bool Intersects(Plane plane, if (instance.Representation is SolidRepresentation solidRepresentation) { - intersectionPoints.AddRange(solidRepresentation.CalculateIntersectionPoints(this, plane, - out var beyondPolygonsLocal)); - beyondPolygonsList.AddRange(beyondPolygonsLocal); + foreach (var intersection in solidRepresentation.CalculateIntersectionPoints(this, plane, + out var beyondPolygonsLocal)) + { + AddToGraph(intersection, graphVertices, graphEdges); + } } } } - if (!intersectionPoints.Any()) - { - return false; - } - - // Draw segments through the results and add to the - // half edge graph. - for (var j = 0; j < intersectionPoints.Count - 1; j += 2) - { - // Don't create zero-length edges. - if (intersectionPoints[j].IsAlmostEqualTo(intersectionPoints[j + 1])) - { - continue; - } - - var a = Solid.FindOrCreateGraphVertex(intersectionPoints[j], graphVertices, graphEdges); - var b = Solid.FindOrCreateGraphVertex(intersectionPoints[j + 1], graphVertices, graphEdges); - var e1 = (a, b, 0); - var e2 = (b, a, 0); - if (graphEdges[a].Contains(e1) || graphEdges[b].Contains(e2)) - { - continue; - } - else - { - graphEdges[a].Add(e1); - } - } - // } - var heg = new HalfEdgeGraph2d() { Vertices = graphVertices, @@ -364,6 +335,33 @@ public bool Intersects(Plane plane, } } + private static void AddToGraph(List intersectionPoints, List graphVertices, List> graphEdges) + { + // Draw segments through the results and add to the + // half edge graph. + for (var j = 0; j < intersectionPoints.Count - 1; j += 2) + { + // Don't create zero-length edges. + if (intersectionPoints[j].IsAlmostEqualTo(intersectionPoints[j + 1])) + { + continue; + } + + var a = Solid.FindOrCreateGraphVertex(intersectionPoints[j], graphVertices, graphEdges); + var b = Solid.FindOrCreateGraphVertex(intersectionPoints[j + 1], graphVertices, graphEdges); + var e1 = (a, b, 0); + var e2 = (b, a, 0); + if (graphEdges[a].Contains(e1) || graphEdges[b].Contains(e2)) + { + continue; + } + else + { + graphEdges[a].Add(e1); + } + } + } + /// /// Get the computed csg solid. /// The csg is centered on the origin by default. diff --git a/Elements/src/Geometry/Arc.cs b/Elements/src/Geometry/Arc.cs index 3f642a4a1..87aa593f8 100644 --- a/Elements/src/Geometry/Arc.cs +++ b/Elements/src/Geometry/Arc.cs @@ -484,29 +484,6 @@ public override Transform TransformAt(double u) return this.BasisCurve.TransformAt(u); } - /// - /// Create a polyline through a set of points along the curve. - /// - /// The number of divisions of the curve. - /// A polyline. - public override Polyline ToPolyline(int divisions = 10) - { - var pts = new List(divisions + 1); - var step = this.Domain.Length / divisions; - for (var t = this.Domain.Min; t < this.Domain.Max; t += step) - { - pts.Add(PointAt(t)); - } - - // We don't go all the way to the end parameter, and - // add it here explicitly because rounding errors can - // cause small imprecision which accumulates to make - // the final parameter slightly more/less than the actual - // end parameter. - pts.Add(PointAt(this.Domain.Max)); - return new Polyline(pts); - } - /// /// Get the parameter at a distance from the start parameter along the curve. /// diff --git a/Elements/src/Geometry/BoundedCurve.cs b/Elements/src/Geometry/BoundedCurve.cs index 32e3b8fa0..c8e79b858 100644 --- a/Elements/src/Geometry/BoundedCurve.cs +++ b/Elements/src/Geometry/BoundedCurve.cs @@ -93,10 +93,17 @@ public virtual Polyline ToPolyline(int divisions = 10) { var pts = new List(divisions + 1); var step = this.Domain.Length / divisions; - for (var t = 0; t <= divisions; t++) + for (var t = this.Domain.Min; t < this.Domain.Max; t += step) { - pts.Add(PointAt(t * step)); + pts.Add(PointAt(t)); } + + // We don't go all the way to the end parameter, and + // add it here explicitly because rounding errors can + // cause small imprecision which accumulates to make + // the final parameter slightly more/less than the actual + // end parameter. + pts.Add(PointAt(this.Domain.Max)); return new Polyline(pts); } diff --git a/Elements/src/Representations/ContentRepresentation.cs b/Elements/src/Representations/ContentRepresentation.cs new file mode 100644 index 000000000..2ec792384 --- /dev/null +++ b/Elements/src/Representations/ContentRepresentation.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Serialization.glTF; +using Elements.Utilities; +using glTFLoader.Schema; + +namespace Elements +{ + /// + /// Represents an element as a reference to a GLB file location within the content catalog. + /// + public class ContentRepresentation : ElementRepresentation + { + /// The URI of the glb for this element. + public string GlbLocation { get; set; } + + /// The bounding box of the content. + public BBox3 BoundingBox { get; set; } + + /// + /// Initializes a new instance of ContentRepresentation class + /// + /// The URI of the glb for this element. + /// The bounding box of the content. + public ContentRepresentation(string glbLocation, BBox3 boundingBox) + { + GlbLocation = glbLocation; + BoundingBox = boundingBox; + } + + /// + /// Initializes a new instance of ContentRepresentation class + /// + /// The URI of the glb for this element. + public ContentRepresentation(string glbLocation) : this(glbLocation, default) { } + + /// + public override bool TryToGraphicsBuffers(GeometricElement element, out List graphicsBuffers, out string id, out MeshPrimitive.ModeEnum? mode) + { + id = element.Id + "_mesh"; + + graphicsBuffers = new List(); + mode = MeshPrimitive.ModeEnum.TRIANGLES; + + if (!BoundingBox.IsValid() || BoundingBox.IsDegenerate()) + { + return true; + } + + var bottomProfile = new Geometry.Polygon(new List{ + new Vector3(BoundingBox.Min.X, BoundingBox.Min.Y, BoundingBox.Min.Z), + new Vector3(BoundingBox.Min.X, BoundingBox.Max.Y, BoundingBox.Min.Z), + new Vector3(BoundingBox.Max.X, BoundingBox.Max.Y, BoundingBox.Min.Z), + new Vector3(BoundingBox.Max.X, BoundingBox.Min.Y, BoundingBox.Min.Z), + }); + + var height = BoundingBox.Max.Z - BoundingBox.Min.Z; + var boxSolid = new Extrude(bottomProfile, height, Vector3.ZAxis, false); + + var csg = SolidOperationUtils.GetFinalCsgFromSolids(new List() { boxSolid }, element, false); + + if (csg == null) + { + return false; + } + + GraphicsBuffers buffers = null; + buffers = csg.Tessellate(); + + if (buffers.Vertices.Count == 0) + { + return false; + } + + graphicsBuffers.Add(buffers); + return true; + } + + internal override List GetNodeExtensions(GeometricElement element) + { + var extensions = base.GetNodeExtensions(element); + extensions.Add(new NodeExtension("HYPAR_referenced_content", "contentUrl", GlbLocation)); + return extensions; + } + } +} \ No newline at end of file diff --git a/Elements/src/Representations/SolidRepresentation.cs b/Elements/src/Representations/SolidRepresentation.cs index 6abab334b..5832a154a 100644 --- a/Elements/src/Representations/SolidRepresentation.cs +++ b/Elements/src/Representations/SolidRepresentation.cs @@ -147,9 +147,9 @@ public BBox3 ComputeBounds(GeometricElement element) /// The intersecting plane. /// The output collection of the polygons beyond the input plane. /// Returns the collection of intersection points. - public List CalculateIntersectionPoints(GeometricElement element, Plane plane, out List beyondPolygons) + public List> CalculateIntersectionPoints(GeometricElement element, Plane plane, out List beyondPolygons) { - var intersectionPoints = new List(); + var intersectionPoints = new List>(); beyondPolygons = new List(); var csg = SolidOperationUtils.GetFinalCsgFromSolids(SolidOperations, element, true); @@ -186,7 +186,7 @@ public List CalculateIntersectionPoints(GeometricElement element, Plane var d = csgNormal.Cross(plane.Normal).Unitized(); edgeResults.Sort(new DirectionComparer(d)); - intersectionPoints.AddRange(edgeResults); + intersectionPoints.Add(edgeResults); } return intersectionPoints; diff --git a/Elements/src/Serialization/glTF/GltfExtensions.cs b/Elements/src/Serialization/glTF/GltfExtensions.cs index e56d99ede..0ad8f3324 100644 --- a/Elements/src/Serialization/glTF/GltfExtensions.cs +++ b/Elements/src/Serialization/glTF/GltfExtensions.cs @@ -1080,7 +1080,7 @@ internal static Gltf InitializeGlTF(Model model, errors.Add(new ElementError(e.Id, ex)); } } - if (allBuffers.Sum(b => b.Count()) + buffer.Count == 0 && lights.Count == 0) + if (allBuffers.Sum(b => b.Count()) + buffer.Count == 0 && lights.Count == 0 && nodes.Count < 1) { return null; } @@ -1292,7 +1292,7 @@ private static void GetRenderDataForElement(Element e, var elementNodeId = NodeUtilities.AddInstanceNode(nodes, element.Transform, element.Id); foreach (var representation in element.RepresentationInstances) { - // get the unique id that contains representation Id and opening Ids + // get the unique id that contains representation Id and opening Ids int combinedId = representation.GetHashCode(element); if (representationsMap.TryGetValue(combinedId, out var mesh)) @@ -1302,31 +1302,52 @@ private static void GetRenderDataForElement(Element e, { NodeUtilities.SetRepresentationInfo(nodes[index], representation); NodeUtilities.SetElementInfo(nodes[index], element.Id); + foreach (var nodeExtension in representation.Representation.GetNodeExtensions(element)) + { + AddExtension(gltf, nodes[index], nodeExtension.Name, nodeExtension.Attributes); + } } } else if (representation.Representation.TryToGraphicsBuffers(geometricElement, out var graphicsBuffers, out var bufferId, out var mode)) { - meshId = AddMesh(bufferId, - buffer, - bufferViews, - accessors, - materialIndexMap[representation.Material.Id.ToString()], - graphicsBuffers, - (MeshPrimitive.ModeEnum)mode, - meshes); + var addedNodes = new List(); + if (graphicsBuffers.Any()) + { + meshId = AddMesh(bufferId, + buffer, + bufferViews, + accessors, + materialIndexMap[representation.Material.Id.ToString()], + graphicsBuffers, + (MeshPrimitive.ModeEnum)mode, + meshes); + + if (meshId != -1) + { + var meshIdList = new List { meshId }; + representationsMap.Add(combinedId, meshIdList); + addedNodes.AddRange(NodeUtilities.AddNodes(nodes, meshIdList, elementNodeId)); + } + } + else + { + meshId = NodeUtilities.AddEmptyNode(nodes, elementNodeId); + addedNodes.Add(meshId); + } // If the id == -1, the mesh is malformed. // It may have no geometry. if (meshId != -1) { - var meshIdList = new List { meshId }; - representationsMap.Add(combinedId, meshIdList); - var addedNodes = NodeUtilities.AddNodes(nodes, meshIdList, elementNodeId); foreach (var index in addedNodes) { NodeUtilities.SetRepresentationInfo(nodes[index], representation); NodeUtilities.SetElementInfo(nodes[index], element.Id); + foreach (var nodeExtension in representation.Representation.GetNodeExtensions(element)) + { + AddExtension(gltf, nodes[index], nodeExtension.Name, nodeExtension.Attributes); + } } } } diff --git a/Elements/src/Serialization/glTF/NodeExtension.cs b/Elements/src/Serialization/glTF/NodeExtension.cs new file mode 100644 index 000000000..3b964128f --- /dev/null +++ b/Elements/src/Serialization/glTF/NodeExtension.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Elements.Serialization.glTF +{ + internal class NodeExtension + { + public NodeExtension(string name, Dictionary attributes) + { + Name = name; + Attributes = attributes; + } + + public NodeExtension(string name) + { + Name = name; + } + + public NodeExtension(string name, string attributeName, object attributeValue) + { + Name = name; + Attributes.Add(attributeName, attributeValue); + } + + public string Name { get; set; } + + public Dictionary Attributes { get; } = new Dictionary(); + } +} \ No newline at end of file diff --git a/Elements/src/Serialization/glTF/NodeUtilities.cs b/Elements/src/Serialization/glTF/NodeUtilities.cs index 7059a7348..91ee6214e 100644 --- a/Elements/src/Serialization/glTF/NodeUtilities.cs +++ b/Elements/src/Serialization/glTF/NodeUtilities.cs @@ -156,6 +156,11 @@ internal static int AddInstanceNode(List nodes, Transfor return AddNode(nodes, newNode, 0); } + internal static int AddEmptyNode(List nodes, int parentId) + { + return AddNode(nodes, new Node(), parentId); + } + internal static int[] AddInstanceNode( List nodes, List meshIds, diff --git a/Elements/test/EllipticalArcTests.cs b/Elements/test/EllipticalArcTests.cs index deeca0c6f..afd71060d 100644 --- a/Elements/test/EllipticalArcTests.cs +++ b/Elements/test/EllipticalArcTests.cs @@ -29,5 +29,15 @@ public void EllipticalArcTransforms() } this.Model.AddElement(new ModelCurve(ellipticalArc, BuiltInMaterials.ZAxis)); } + + [Fact] + public void ToPolyline() + { + var arc = new EllipticalArc(Vector3.Origin, 1, 2, 10, 20); + var p = arc.ToPolyline(10); + Assert.Equal(10, p.Segments().Length); + Assert.Equal(arc.Start, p.Vertices[0]); + Assert.Equal(arc.End, p.Vertices[p.Vertices.Count - 1]); + } } } \ No newline at end of file