diff --git a/Elements/src/CoreModels/ElementRepresentation.cs b/Elements/src/CoreModels/ElementRepresentation.cs index 535badb4e..4545df75f 100644 --- a/Elements/src/CoreModels/ElementRepresentation.cs +++ b/Elements/src/CoreModels/ElementRepresentation.cs @@ -27,4 +27,14 @@ internal virtual List GetNodeExtensions(GeometricElement element) { return new List(); } + + /// + ///Creates the set of snapping points + /// + /// The element with this representation. + /// + public virtual List CreateSnappingPoints(GeometricElement element) + { + return new List(); + } } \ No newline at end of file diff --git a/Elements/src/CoreModels/RepresentationInstance.cs b/Elements/src/CoreModels/RepresentationInstance.cs index c98ebe486..29914c2e8 100644 --- a/Elements/src/CoreModels/RepresentationInstance.cs +++ b/Elements/src/CoreModels/RepresentationInstance.cs @@ -58,7 +58,7 @@ public RepresentationInstance(ElementRepresentation representation, Material mat public List RepresentationTypes { get; set; } = new List(); /// - /// Indicates if this element representation instance is displayed by default. + /// Indicates if this element representation instance is displayed by default. /// Element can have several default representations. /// public bool IsDefault { get; set; } = true; diff --git a/Elements/src/Geometry/Solids/Extrude.cs b/Elements/src/Geometry/Solids/Extrude.cs index 04ce5d007..4af71ef56 100644 --- a/Elements/src/Geometry/Solids/Extrude.cs +++ b/Elements/src/Geometry/Solids/Extrude.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Elements.Validators; using Newtonsoft.Json; @@ -108,6 +109,37 @@ public Extrude(Profile profile, double height, Vector3 direction, bool isVoid = UpdateGeometry(); } + internal override List CreateSnappingPoints(GeometricElement element) + { + var result = new List(); + var localTransform = new Transform(Direction * Height); + var bottomVertices = new List(); + // add perimeter bottom points + result.Add(new SnappingPoints(Profile.Perimeter.Vertices, SnappingEdgeMode.LineLoop)); + bottomVertices.AddRange(Profile.Perimeter.Vertices); + // add perimeter top points + result.Add(new SnappingPoints(Profile.Perimeter.TransformedPolygon(localTransform).Vertices, SnappingEdgeMode.LineLoop)); + + // add each void + foreach (var item in Profile.Voids) + { + result.Add(new SnappingPoints(item.Vertices, SnappingEdgeMode.LineLoop)); + bottomVertices.AddRange(item.Vertices); + result.Add(new SnappingPoints(item.TransformedPolygon(localTransform).Vertices, SnappingEdgeMode.LineLoop)); + } + + // connect top and bottom points + var edges = new List(); + foreach (var item in bottomVertices) + { + edges.Add(item); + edges.Add(localTransform.OfPoint(item)); + } + result.Add(new SnappingPoints(edges, SnappingEdgeMode.Lines)); + + return result; + } + private void UpdateGeometry() { this._solid = Kernel.Instance.CreateExtrude(this._profile, this._height, this._direction, this._reverseWinding); diff --git a/Elements/src/Geometry/Solids/Lamina.cs b/Elements/src/Geometry/Solids/Lamina.cs index 58f90dc0e..e9e8331b4 100644 --- a/Elements/src/Geometry/Solids/Lamina.cs +++ b/Elements/src/Geometry/Solids/Lamina.cs @@ -82,6 +82,19 @@ public Lamina(Profile profile, bool isVoid = false) : this(profile.Perimeter, pr } + internal override List CreateSnappingPoints(GeometricElement element) + { + var result = new List(); + result.Add(new SnappingPoints(Perimeter.Vertices, SnappingEdgeMode.LineLoop)); + + foreach (var item in Voids) + { + result.Add(new SnappingPoints(item.Vertices, SnappingEdgeMode.LineLoop)); + } + + return result; + } + private void UpdateGeometry() { this._solid = Kernel.Instance.CreateLamina(this._perimeter, this._voids); diff --git a/Elements/src/Geometry/Solids/SolidOperation.cs b/Elements/src/Geometry/Solids/SolidOperation.cs index 497d4c9ee..3a88192d6 100644 --- a/Elements/src/Geometry/Solids/SolidOperation.cs +++ b/Elements/src/Geometry/Solids/SolidOperation.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Newtonsoft.Json; namespace Elements.Geometry.Solids @@ -53,5 +54,10 @@ protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.Cal if (handler != null) handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); } + + internal virtual List CreateSnappingPoints(GeometricElement element) + { + return new List(); + } } } \ No newline at end of file diff --git a/Elements/src/Geometry/Solids/Sweep.cs b/Elements/src/Geometry/Solids/Sweep.cs index 683a5efb2..689144a47 100644 --- a/Elements/src/Geometry/Solids/Sweep.cs +++ b/Elements/src/Geometry/Solids/Sweep.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Newtonsoft.Json; namespace Elements.Geometry.Solids @@ -109,6 +110,11 @@ public double ProfileRotation } } + internal override List CreateSnappingPoints(GeometricElement element) + { + return base.CreateSnappingPoints(element); + } + private void UpdateGeometry() { this._solid = Kernel.Instance.CreateSweepAlongCurve(this._profile, this._curve, this._startSetback, this._endSetback, this._profileRotation); diff --git a/Elements/src/Representations/CurveRepresentation.cs b/Elements/src/Representations/CurveRepresentation.cs index b2e8a3731..aeaf61e16 100644 --- a/Elements/src/Representations/CurveRepresentation.cs +++ b/Elements/src/Representations/CurveRepresentation.cs @@ -45,5 +45,14 @@ public override bool TryToGraphicsBuffers(GeometricElement element, out List + public override List CreateSnappingPoints(GeometricElement element) + { + var snappingPoints = new List(); + var curvePoints = _curve.RenderVertices(); + snappingPoints.Add(new SnappingPoints(curvePoints)); + return snappingPoints; + } } } \ No newline at end of file diff --git a/Elements/src/Representations/SolidRepresentation.cs b/Elements/src/Representations/SolidRepresentation.cs index 5832a154a..d8521056c 100644 --- a/Elements/src/Representations/SolidRepresentation.cs +++ b/Elements/src/Representations/SolidRepresentation.cs @@ -191,5 +191,18 @@ public List> CalculateIntersectionPoints(GeometricElement element, return intersectionPoints; } + + /// + public override List CreateSnappingPoints(GeometricElement element) + { + var snappingPoints = new List(); + + foreach (var solidOperation in SolidOperations) + { + snappingPoints.AddRange(solidOperation.CreateSnappingPoints(element)); + } + + return snappingPoints; + } } } \ No newline at end of file diff --git a/Elements/src/Serialization/JSON/VectorListToByteArrayConverter.cs b/Elements/src/Serialization/JSON/VectorListToByteArrayConverter.cs new file mode 100644 index 000000000..35feb04c0 --- /dev/null +++ b/Elements/src/Serialization/JSON/VectorListToByteArrayConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using Elements.Geometry; +using Newtonsoft.Json; + +namespace Elements.Serialization.JSON +{ + internal class VectorListToByteArrayConverter : JsonConverter + { + public override bool CanRead => false; + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(List); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var points = value as List; + var valueAsArray = new double[points.Count * 3]; + for (int i = 0; i < points.Count; i++) + { + valueAsArray[i * 3] = points[i].X; + valueAsArray[i * 3 + 1] = points[i].Y; + valueAsArray[i * 3 + 2] = points[i].Z; + } + serializer.Serialize(writer, valueAsArray); + } + } +} \ No newline at end of file diff --git a/Elements/src/Serialization/glTF/GltfExtensions.cs b/Elements/src/Serialization/glTF/GltfExtensions.cs index 0ad8f3324..f2221edb3 100644 --- a/Elements/src/Serialization/glTF/GltfExtensions.cs +++ b/Elements/src/Serialization/glTF/GltfExtensions.cs @@ -452,6 +452,19 @@ private static void AddExtension(Gltf gltf, glTFLoader.Schema.Node gltfNode, str gltfNode.Extensions.Add(extensionName, extensionAttributes); } + /// + /// Add a custom Mesh extension. + /// + private static void AddExtension(Gltf gltf, glTFLoader.Schema.Mesh gltfMesh, string extensionName, Dictionary extensionAttributes) + { + if (gltfMesh.Extensions == null) + { + gltfMesh.Extensions = new Dictionary(); + } + AddExtension(gltf, extensionName, extensionAttributes); + gltfMesh.Extensions.Add(extensionName, extensionAttributes); + } + /// /// Add a generic custom extension. /// @@ -1328,6 +1341,11 @@ private static void GetRenderDataForElement(Element e, var meshIdList = new List { meshId }; representationsMap.Add(combinedId, meshIdList); addedNodes.AddRange(NodeUtilities.AddNodes(nodes, meshIdList, elementNodeId)); + var snappingPoints = representation.Representation.CreateSnappingPoints(element); + if (snappingPoints.Any()) + { + AddExtension(gltf, meshes[meshId], "HYPAR_snapping_points", new Dictionary() { { "points", snappingPoints } }); + } } } else diff --git a/Elements/src/Serialization/glTF/NodeUtilities.cs b/Elements/src/Serialization/glTF/NodeUtilities.cs index 91ee6214e..7e521c856 100644 --- a/Elements/src/Serialization/glTF/NodeUtilities.cs +++ b/Elements/src/Serialization/glTF/NodeUtilities.cs @@ -90,7 +90,7 @@ internal static Node AddInstanceAsCopyOfNode( // Two new nodes are created: a top-level node, which has the // element's Transform, and one just below that, which handles // flipping the orientation of the glb to have Z up. That node has - // the node to copy as its only child. + // the node to copy as its only child. // We use the node to copy exactly as is, with an unmodified // transform. // We need the outermost node to be "purely" the element's @@ -109,7 +109,7 @@ internal static Node AddInstanceAsCopyOfNode( var rootTransform = new Transform(); // glb has Y up. transform it to have Z up so we // can create instances of it in a Z up world. It will get switched - // back to Y up further up in the node hierarchy. + // back to Y up further up in the node hierarchy. rootTransform.Rotate(new Vector3(1, 0, 0), 90.0); float[] glbOrientationTransform = TransformToMatrix(rootTransform); var elementOrientationNode = new glTFLoader.Schema.Node(); @@ -237,9 +237,14 @@ public static void SetRepresentationInfo(this Node node, RepresentationInstance var extensionDict = new Dictionary { {"isDefault", representationInstance.IsDefault}, - {"representationType", representationInstance.RepresentationTypes} + {"representationType", representationInstance.RepresentationTypes}, }; + if (node.Mesh.HasValue) + { + extensionDict.Add("mesh", node.Mesh.Value); + } + node.Extensions["HYPAR_representation_info"] = extensionDict; } } diff --git a/Elements/src/Snapping/SnappingEdgeMode.cs b/Elements/src/Snapping/SnappingEdgeMode.cs new file mode 100644 index 000000000..63ffb38d5 --- /dev/null +++ b/Elements/src/Snapping/SnappingEdgeMode.cs @@ -0,0 +1,25 @@ +namespace Elements +{ + /// + /// Enumerates the modes for creating snap edges. + /// + public enum SnappingEdgeMode + { + /// + /// No edges are created; only individual point snaps. + /// + Points, + /// + /// A snap edge is drawn between every pair of points, creating a network of edges. + /// + Lines, + /// + /// Snap edges connect each subsequent point and also close the shape by connecting the last to the first point. + /// + LineLoop, + /// + /// Snap edges connect each subsequent point, without closing the shape. + /// + LineStrip + } +} \ No newline at end of file diff --git a/Elements/src/Snapping/SnappingPoints.cs b/Elements/src/Snapping/SnappingPoints.cs new file mode 100644 index 000000000..8c00dfe61 --- /dev/null +++ b/Elements/src/Snapping/SnappingPoints.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Elements.Geometry; +using Elements.Serialization.JSON; +using Newtonsoft.Json; + +namespace Elements +{ + /// + /// Provides information about snapping points. + /// + public class SnappingPoints + { + /// + /// Initializes a new instance of SnappingPoints class. + /// + /// The set of points. + /// The mode for creating snap edges. + public SnappingPoints(IEnumerable points, SnappingEdgeMode edgeMode = SnappingEdgeMode.LineStrip) + { + Points.AddRange(points); + EdgeMode = edgeMode; + } + + /// + /// Snapping points. + /// + [JsonProperty("points")] + [JsonConverter(typeof(VectorListToByteArrayConverter))] + public List Points { get; } = new List(); + + /// + /// The modes for creating snap edges. + /// + [JsonProperty("edgeMode")] + public SnappingEdgeMode EdgeMode { get; set; } + } +} \ No newline at end of file