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