diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f1092a94..4aade9b23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ - `GeometricElement.RepresentationInstances` - `ContentRepresentation` - `Elements.Door` +- `ComponentBase.UseRepresentationInstances` - an option flag to make generating fitting models faster/smaller. ### Fixed @@ -64,6 +65,7 @@ - `BoundedCurve.ToPolyline` now works correctly for `EllipticalArc` class. ### Changed + - `GltfExtensions.UseReferencedContentExtension` is now true by default. - `GeometricElement.Intersects` method now supports multiple representations. - `GltfExtensions.ToGlTF` creates parent node for element and child nodes for representation instances. diff --git a/Elements.MEP/src/Fittings/Elbow.cs b/Elements.MEP/src/Fittings/Elbow.cs index 10b019560..3e267da94 100644 --- a/Elements.MEP/src/Fittings/Elbow.cs +++ b/Elements.MEP/src/Fittings/Elbow.cs @@ -29,7 +29,7 @@ public Elbow(Vector3 position, Vector3 startDirection, Vector3 endDirection, dou public override void UpdateRepresentations() { - var profile = new Circle(Vector3.Origin, this.Start.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); + var profile = new Circle(Vector3.Origin, Start.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); var oneSweep = new Sweep(profile, GetSweepLine(), @@ -38,8 +38,19 @@ public override void UpdateRepresentations() 0, false); - var arrows = this.Start.GetArrow(this.Transform.Origin).Concat(this.End.GetArrow(this.Transform.Origin)); - this.Representation = new Representation(new List { oneSweep }.Concat(arrows).Concat(GetExtensions()).ToList()); + var arrows = new List(); + arrows.AddRange(Start.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform())); + arrows.AddRange(End.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform())); + var solidOperations = new List { oneSweep }.Concat(arrows).Concat(GetExtensions()).ToList(); + + if (UseRepresentationInstances) + { + FittingRepresentationStorage.SetFittingRepresentation(this, () => solidOperations); + } + else + { + Representation = new Representation(solidOperations); + } } public override Port[] GetPorts() @@ -64,23 +75,25 @@ private Vector3 BendRadiusOffset(double? bendRadius, Vector3 direction) private Polyline GetSweepLine() { - var sweepLine = new List(); - sweepLine.Add(this.Start.Position - this.Transform.Origin); + var sweepLine = new List + { + Start.Position - Transform.Origin + }; - if (this.BendRadius != 0) + if (BendRadius != 0) { var startDirection = Vector3.XAxis; - var startPoint = startDirection * this.BendRadius; + var startPoint = startDirection * BendRadius; var startNormal = startDirection.Cross(Vector3.ZAxis).Unitized(); - var originalPlane = new Polygon(Vector3.Origin, (this.Start.Position - this.Transform.Origin).Unitized(), (this.End.Position - this.Transform.Origin).Unitized()); + var originalPlane = new Polygon(Vector3.Origin, (Start.Position - Transform.Origin).Unitized(), (End.Position - Transform.Origin).Unitized()); var transform = originalPlane.ToTransform(); var inverted = transform.Inverted(); originalPlane.Transform(inverted); var angleBetweenOriginalVectors = originalPlane.Vertices[1].PlaneAngleTo(originalPlane.Vertices[2]) * Math.PI / 180; var endDirection = new Vector3(Math.Cos(angleBetweenOriginalVectors), Math.Sin(angleBetweenOriginalVectors)); - var endPoint = endDirection * this.BendRadius; + var endPoint = endDirection * BendRadius; var endNormal = endDirection.Cross(Vector3.ZAxis).Unitized(); new Ray(startPoint, startNormal).Intersects(new Ray(endPoint, endNormal), out var intersectionPoint, true); @@ -103,9 +116,17 @@ private Polyline GetSweepLine() sweepLine.Add(Vector3.Origin); } - sweepLine.Add(this.End.Position - this.Transform.Origin); + sweepLine.Add(End.Position - Transform.Origin); - return new Polyline(sweepLine); + if (UseRepresentationInstances) + { + var t = GetRotatedTransform().Inverted(); + return new Polyline(sweepLine.Select(v => t.OfPoint(v)).ToList()); + } + else + { + return new Polyline(sweepLine); + } } public override Transform GetRotatedTransform() @@ -114,5 +135,11 @@ public override Transform GetRotatedTransform() var t = new Transform(Vector3.Origin, End.Direction, zAxis); return t; } + + /// + public override string GetRepresentationHash() + { + return $"{this.GetType().Name}-{this.Diameter}-{this.BendRadius}-{this.Angle}"; + } } } \ No newline at end of file diff --git a/Elements.MEP/src/Fittings/Fitting.cs b/Elements.MEP/src/Fittings/Fitting.cs index 368a365eb..05052c0c9 100644 --- a/Elements.MEP/src/Fittings/Fitting.cs +++ b/Elements.MEP/src/Fittings/Fitting.cs @@ -15,6 +15,11 @@ public Port[] GetConnectors() return GetPorts(); } + public virtual string GetRepresentationHash() + { + return this.GetHashCode().ToString(); + } + abstract public Port[] GetPorts(); public abstract Transform GetRotatedTransform(); diff --git a/Elements.MEP/src/Fittings/FittingRepresentationStorage.cs b/Elements.MEP/src/Fittings/FittingRepresentationStorage.cs new file mode 100644 index 000000000..d25c2cd67 --- /dev/null +++ b/Elements.MEP/src/Fittings/FittingRepresentationStorage.cs @@ -0,0 +1,27 @@ + +using System; +using System.Collections.Generic; +using Elements.Geometry; +using Elements.Geometry.Solids; + +namespace Elements.Fittings +{ + static class FittingRepresentationStorage + { + private static readonly Dictionary> _fittings = new Dictionary>(); + public static Dictionary> Fittings => _fittings; + + public static void SetFittingRepresentation(Fitting fitting, Func> makeSolids) + { + var hash = fitting.GetRepresentationHash(); + if (!_fittings.ContainsKey(hash)) + { + var solids = makeSolids(); + _fittings.Add(hash, new List { new RepresentationInstance(new SolidRepresentation(solids), fitting.Material) }); + } + fitting.RepresentationInstances = _fittings[hash]; + + fitting.Transform = fitting.GetRotatedTransform().Concatenated(new Transform(fitting.Transform.Origin)); + } + } +} \ No newline at end of file diff --git a/Elements.MEP/src/Fittings/IComponent.cs b/Elements.MEP/src/Fittings/IComponent.cs index ee167c164..74a2c76a0 100644 --- a/Elements.MEP/src/Fittings/IComponent.cs +++ b/Elements.MEP/src/Fittings/IComponent.cs @@ -13,6 +13,7 @@ namespace Elements.Fittings public abstract partial class ComponentBase : IComponent { + public static bool UseRepresentationInstances = false; /// /// The component that is towards the trunk of the tree. /// @@ -348,10 +349,10 @@ public static double GetLength(this ComponentBase component) return ps.Length(); case Terminal t: var heightDelta = Math.Abs(t.Transform.Origin.Z - t.Port.Position.Z); - + var terminalTransformOrigin = t.Transform.Origin.Project(Plane.XY); var terminalPortPosition = t.Port.Position.Project(Plane.XY); - + return terminalTransformOrigin.IsAlmostEqualTo(terminalPortPosition) ? heightDelta : heightDelta + terminalTransformOrigin.DistanceTo(terminalPortPosition); diff --git a/Elements.MEP/src/Fittings/Manifold.cs b/Elements.MEP/src/Fittings/Manifold.cs index 584d87162..537785119 100644 --- a/Elements.MEP/src/Fittings/Manifold.cs +++ b/Elements.MEP/src/Fittings/Manifold.cs @@ -39,7 +39,7 @@ public override List BranchSidePorts() public override Port[] GetPorts() { - return new[] {Trunk}.Concat(Branches).ToArray(); + return new[] { Trunk }.Concat(Branches).ToArray(); } public override Port TrunkSidePort() diff --git a/Elements.MEP/src/Fittings/Port.cs b/Elements.MEP/src/Fittings/Port.cs index 4ac31a3ee..e85a8cbcb 100644 --- a/Elements.MEP/src/Fittings/Port.cs +++ b/Elements.MEP/src/Fittings/Port.cs @@ -56,10 +56,9 @@ public bool IsComplimentaryConnector(Port other, double positionTolerance = Vect { return false; } - var angle = Direction.AngleTo(other.Direction); - return angle.ApproximatelyEquals(180, angleTolerance); + return angle.ApproximatelyEquals(180, angleTolerance); } public bool IsIdenticalConnector(Port other, double positionTolerance = Vector3.EPSILON, double angleTolerance = 0.5) @@ -68,22 +67,23 @@ public bool IsIdenticalConnector(Port other, double positionTolerance = Vector3. { return false; } - var angle = Direction.AngleTo(other.Direction); return angle.ApproximatelyEquals(0, angleTolerance); } - public Sweep[] GetArrow(Vector3 relativeTo, double arrowLineLength = 0.1) + public Sweep[] GetArrow(Vector3 relativeTo, double arrowLineLength = 0.1, Transform fittingRotationTransform = null) { + var fittingRotationTransformInverted = fittingRotationTransform == null || !ComponentBase.UseRepresentationInstances ? new Transform() : fittingRotationTransform.Inverted(); + var arrayHeadLength = 0.01; if (ShowArrows) { - var transformedOrigin = Position - relativeTo; + var transformedPosition = fittingRotationTransformInverted.OfPoint(Position - relativeTo); var arrowProfile = new Circle(Vector3.Origin, 0.01).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); - var arrowLine = new Line(transformedOrigin, transformedOrigin + Direction * arrowLineLength); + var arrowLine = new Line(transformedPosition, transformedPosition + fittingRotationTransformInverted.OfPoint(Direction) * arrowLineLength); var headProfile = new Circle(Vector3.Origin, 0.02).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); - var headLine = new Line(transformedOrigin + Direction * arrowLineLength, transformedOrigin + Direction * (arrowLineLength + arrayHeadLength)); + var headLine = new Line(transformedPosition + fittingRotationTransformInverted.OfPoint(Direction) * arrowLineLength, transformedPosition + fittingRotationTransformInverted.OfPoint(Direction) * (arrowLineLength + arrayHeadLength)); var shaft = new Sweep(arrowProfile, arrowLine, 0, 0, 0, false); var head = new Sweep(headProfile, headLine, 0, 0, 0, false); return new Sweep[] { shaft, head }; diff --git a/Elements.MEP/src/Fittings/Reducer.cs b/Elements.MEP/src/Fittings/Reducer.cs index 2b48a7195..677270214 100644 --- a/Elements.MEP/src/Fittings/Reducer.cs +++ b/Elements.MEP/src/Fittings/Reducer.cs @@ -133,7 +133,7 @@ public void Move(Vector3 translation) } /// - /// Port with smaller diameter points to the +X axis. + /// Port with smaller diameter points to the +X axis. /// If there is eccentric transform, the smaller part will be shifted to the -Z axis. /// We point smaller diameter in the +X direction so that there is one reducer defined in the standard orientation, to which this transformation is then applied. /// This let's us just have one size 110/90 that is rotated into a 90/110 orientation when needed. diff --git a/Elements.MEP/src/Fittings/Wye.cs b/Elements.MEP/src/Fittings/Wye.cs index a590b08d7..40723d227 100644 --- a/Elements.MEP/src/Fittings/Wye.cs +++ b/Elements.MEP/src/Fittings/Wye.cs @@ -131,23 +131,45 @@ public override void UpdateRepresentations() var trunkPosition = Trunk.Position; var mainPosition = MainBranch.Position; var branchPosition = SideBranch.Position; - var origin = this.Transform.Origin; + var origin = Transform.Origin; - var trunkProfile = new Circle(new Vector3(), this.Trunk.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); + var trunkProfile = new Circle(new Vector3(), Trunk.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); var trunkLine = new Line(Vector3.Origin, trunkPosition - origin); + if (UseRepresentationInstances) + { + trunkLine = trunkLine.TransformedLine(GetRotatedTransform().Inverted()); + } var trunk = new Sweep(trunkProfile, trunkLine, 0, 0, 0, false); - var mainProfile = new Circle(new Vector3(), this.MainBranch.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); + var mainProfile = new Circle(new Vector3(), MainBranch.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); var mainLine = new Line(Vector3.Origin, mainPosition - origin); + if (UseRepresentationInstances) + { + mainLine = mainLine.TransformedLine(GetRotatedTransform().Inverted()); + } var main = new Sweep(mainProfile, mainLine, 0, 0, 0, false); - var branchProfile = new Circle(new Vector3(), this.SideBranch.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); + var branchProfile = new Circle(new Vector3(), SideBranch.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS); var branchLine = new Line(Vector3.Origin, branchPosition - origin); + if (UseRepresentationInstances) + { + branchLine = branchLine.TransformedLine(GetRotatedTransform().Inverted()); + } var branch = new Sweep(branchProfile, branchLine, 0, 0, 0, false); - var arrows = this.Trunk.GetArrow(this.Transform.Origin).Concat(this.SideBranch.GetArrow(this.Transform.Origin)).Concat(this.MainBranch.GetArrow(this.Transform.Origin)); + var arrows = new List(); + arrows.AddRange(Trunk.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform())); + arrows.AddRange(SideBranch.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform())); + arrows.AddRange(MainBranch.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform())); var solidOps = new List { trunk, main, branch }.Concat(arrows).Concat(GetExtensions()).ToList(); - this.Representation = new Geometry.Representation(solidOps); + if (UseRepresentationInstances) + { + FittingRepresentationStorage.SetFittingRepresentation(this, () => solidOps); + } + else + { + Representation = new Geometry.Representation(solidOps); + } } public override Port[] GetPorts() @@ -189,5 +211,20 @@ public override Transform GetRotatedTransform() var t = new Transform(Vector3.Origin, Trunk.Direction, zAxis); return t; } + + /// + public override string GetRepresentationHash() + { + var props = new double[] { + Trunk.Diameter, + (Trunk.Position - Transform.Origin).LengthSquared(), + MainBranch.Diameter, + (MainBranch.Position - Transform.Origin).LengthSquared(), + SideBranch.Diameter, + (SideBranch.Position - Transform.Origin).LengthSquared(), + Angle + }; + return $"{this.GetType().Name}-{String.Join("-", props.Select(p => p.ToString()))}"; + } } } \ No newline at end of file diff --git a/Elements.MEP/test/FittingsTests.cs b/Elements.MEP/test/FittingsTests.cs index 20d9de7af..323340031 100644 --- a/Elements.MEP/test/FittingsTests.cs +++ b/Elements.MEP/test/FittingsTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -25,7 +25,7 @@ public partial class FittingsTests [Fact] public void MakeWye() { - var model = new Model(); + ComponentBase.UseRepresentationInstances = true; var branchDirection = new Vector3(Math.Sqrt(2) / 2, 1, Math.Sqrt(2) / 2); var mainDir = new Vector3(0, 1, 0); var connectionPoint = new Vector3(1, 0, 1); @@ -42,10 +42,12 @@ public void MakeWye() var pipe2 = new StraightSegment(0, wye.SideBranch, new Port(wye.SideBranch.Position + branchDirection, branchDirection, wye.SideBranch.Diameter)); + var pipe3 = new StraightSegment(0, + new Port(wye.Trunk.Position + wye.Trunk.Direction * 2, branchDirection, wye.Trunk.Diameter), + wye.Trunk + ); - model.AddElements(new Element[] { pipe1, pipe2, wye }); - model.AddElement(new Mass(Polygon.Rectangle(0.1, 0.1), 0.1)); - model.ToGlTF(TestUtils.GetTestPath() + "wye.gltf", false); + SaveToGltf(nameof(MakeWye), new Element[] { pipe1, pipe2, pipe3, wye }); } [Fact] @@ -79,13 +81,32 @@ public void MakeReducer() [Fact] public void MakeElbow() { + ComponentBase.UseRepresentationInstances = true; Port.ShowArrows = true; var position = new Vector3(1, 0, 1); var endDirection = new Vector3(1, 0, 0); - var otherDirection = new Vector3(0, 1, 0); + var otherDirection = new Vector3(0, -1, 1); var elbow = new Elbow(position, endDirection, otherDirection, 0.2, 0.1, FittingTreeRouting.DefaultFittingMaterial); - SaveToGltf(nameof(MakeElbow), elbow); + var startReferencePipe = new StraightSegment(0, elbow.Start, new Port(elbow.Start.Position + elbow.Start.Direction, + elbow.Start.Direction.Negate(), + elbow.Start.Diameter)); + var endReferencePipe = new StraightSegment(0, elbow.End, new Port(elbow.End.Position + elbow.End.Direction, + elbow.End.Direction.Negate(), + elbow.End.Diameter)); + + position = (2, 2, 2); + otherDirection = (0, 1, 0); + + var elbow2 = new Elbow(position, endDirection, otherDirection, 0.2, 0.1, FittingTreeRouting.DefaultFittingMaterial); + var startReferencePipe2 = new StraightSegment(0, elbow2.Start, new Port(elbow2.Start.Position + elbow2.Start.Direction, + elbow2.Start.Direction.Negate(), + elbow2.Start.Diameter)); + var endReferencePipe2 = new StraightSegment(0, elbow2.End, new Port(elbow2.End.Position + elbow2.End.Direction, + elbow2.End.Direction.Negate(), + elbow2.End.Diameter)); + + SaveToGltf(nameof(MakeElbow), new Element[] { elbow, startReferencePipe, endReferencePipe, elbow2, startReferencePipe2, endReferencePipe2 }); } [Fact]