1 package org.simantics.plant3d.scenegraph.controlpoint;
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
7 import java.util.Objects;
9 import javax.vecmath.AxisAngle4d;
10 import javax.vecmath.Matrix3d;
11 import javax.vecmath.Point3d;
12 import javax.vecmath.Quat4d;
13 import javax.vecmath.Tuple3d;
14 import javax.vecmath.Vector3d;
16 import org.simantics.g3d.math.MathTools;
17 import org.simantics.g3d.property.annotations.GetPropertyValue;
18 import org.simantics.g3d.scenegraph.G3DNode;
19 import org.simantics.g3d.scenegraph.base.INode;
20 import org.simantics.plant3d.scenegraph.IP3DNode;
21 import org.simantics.plant3d.scenegraph.Nozzle;
22 import org.simantics.plant3d.scenegraph.P3DRootNode;
23 import org.simantics.plant3d.scenegraph.PipeRun;
24 import org.simantics.plant3d.scenegraph.PipelineComponent;
26 import vtk.vtkRenderer;
29 public class PipeControlPoint extends G3DNode implements IP3DNode {
31 private static boolean DEBUG = false;
33 public enum PointType{INLINE,TURN,END};
34 public enum Direction{NEXT,PREVIOUS};
35 public enum PositionType {SPLIT,NEXT,PREVIOUS,PORT}
37 private PipelineComponent component;
39 private PointType type;
40 private boolean isFixed = true; // In-line: fixed-length Turn: fixed-angle
41 private boolean isMod = false; // Can user modify fixed value manually
42 private boolean isRotate = false; // rotates around path leg axis.
43 private boolean isReverse = false; // definition direction can be swapped
44 private boolean isDeletable = true; // can be removed by rules
45 private boolean isSizeChange = false; // changes size of the pipe. The next control point / component is on different PipeRun
46 private boolean isSub = false; // child point for offset / size change
48 private boolean disposed = false;
50 public PipeControlPoint(PipelineComponent component) {
51 this.component = component;
52 if (component.getPipeRun() != null)
53 component.getPipeRun().addChild(this);
57 public PipeControlPoint(PipelineComponent component, PipeRun piperun) {
58 this.component = component;
59 piperun.addChild(this);
63 public void update(vtkRenderer ren) {
65 PipingRules.requestUpdate(this);
66 } catch (Exception e) {
72 public PipeRun getPipeRun() {
73 return (PipeRun)getParent();
76 public PipelineComponent getPipelineComponent() {
80 public PointType getType() {
84 public void setType(PointType type) {
88 @GetPropertyValue(name="Fixed",tabId="Debug",value="fixed")
89 public boolean isFixed() {
93 public void setFixed(boolean fixed) {
97 @GetPropertyValue(name="Mod",tabId="Debug",value="mod")
98 public boolean isMod() {
102 public void setMod(boolean isMod) {
106 @GetPropertyValue(name="Rotate",tabId="Debug",value="rotate")
107 public boolean isRotate() {
111 public void setRotate(boolean rotate) {
112 this.isRotate = rotate;
115 @GetPropertyValue(name="Reverse",tabId="Debug",value="reverse")
116 public boolean isReverse() {
120 public void setReverse(boolean reverse) {
121 this.isReverse = reverse;
124 public void setSub(boolean sub) {
128 @GetPropertyValue(name="Deletable",tabId="Debug",value="deletable")
129 public boolean isDeletable() {
133 public void setDeletable(boolean deletable) {
134 this.isDeletable = deletable;
137 public boolean isPathLegEnd() {
138 return type != PointType.INLINE;
141 public boolean isEnd() {
142 return type == PointType.END;
145 public boolean isTurn() {
146 return type == PointType.TURN;
149 public boolean isInline() {
150 return type == PointType.INLINE;
153 public boolean asPathLegEnd() {
154 // Ends and Turns are path leg ends by default, but also unconnected inline are path leg ends.
155 return isPathLegEnd() || getNext() == null || getPrevious() == null;
159 * True for end components, if control point defines absolute position direction, which rules cannot modify.
160 * This is typical for nozzles.
163 public boolean isDirected() {
164 return isFixed && isEnd();
168 * True for end components, if control is opposite to directed, and rules can modify position and orientation.
169 * This is typical for caps, and other end components.
172 public boolean isNonDirected() {
173 return !isFixed && isEnd();
176 public boolean isVariableLength() {
177 return !isFixed && isInline();
181 * Fixed length in-line component is such that piping rules cannot modify the length.
184 public boolean isFixedLength() {
185 return isFixed && isInline();
188 public boolean isVariableAngle() {
189 return !isFixed && isTurn();
193 * Fixed angle turn component is such that piping rules cannot modify the angle.
196 public boolean isFixedAngle() {
197 return isFixed && isTurn();
201 * Does the turn behave like fixed angle?
202 * For variable angle turns, the turn angle is defined by connected components, and without them, we must handle the component as fixed angle.
205 public boolean asFixedAngle() {
206 return isTurn() && (isFixed || next == null || previous == null);
209 public boolean isBranchEnd() {
210 return isDeletable && isEnd();
213 public boolean isOffset() {
214 return offset != null;
217 public boolean isDualSub() {
218 return parent != null && isSub;
221 public boolean isDualInline() {
222 return children.size() == 1 && children.get(0).isDualSub();
225 public boolean isAxial() {
226 return isInline() && !isDualInline();
229 public boolean isSizeChange() {
231 // if (children.size() == 0)
233 // if (!isDualInline())
235 // return getPipeRun() != children.get(0).getPipeRun();
238 public void setSizeChange(boolean isSizeChange) {
239 this.isSizeChange = isSizeChange;
243 private PipeControlPoint next;
244 private PipeControlPoint previous;
246 public PipeControlPoint getNext() {
250 public PipeControlPoint getPrevious() {
254 public void setNext(PipeControlPoint next) {
256 getParentPoint().setNext(next);
259 if (next != null && next.isDualSub())
261 if (_setNext(next)) {
262 for (PipeControlPoint pcp : children) {
270 public void setPrevious(PipeControlPoint prev) {
272 getParentPoint().setPrevious(prev);
275 if (prev != null && prev.isDualInline())
276 prev = prev.children.get(0);
277 if (_setPrevious(prev)) {
278 for (PipeControlPoint pcp : children) {
280 pcp._setPrevious(prev);
286 protected boolean _setNext(PipeControlPoint next) {
287 if (isEnd() && previous != null && next != null)
288 throw new RuntimeException("End control points are allowed to have only one connection");
290 throw new RuntimeException("Cannot connect to self");
291 if (this.next == next)
293 if (DEBUG) System.out.println(this + " next " + next);
294 if (next == null && isVariableAngle() && previous != null && !isRemoved()) {
295 convertVariableAngleToFixed(Direction.NEXT);
298 if (component != null) {
299 if (parent == null || isSub)
300 component.setNext(next != null ? next.component : null);
302 component.setBranch0(next != null ? next.component : null);
308 protected boolean _setPrevious(PipeControlPoint previous) {
309 if (isEnd() && next != null && previous != null)
310 throw new RuntimeException("End control points are allowed to have only one connection");
311 if (previous == this)
312 throw new RuntimeException("Cannot connect to self");
313 if (this.previous == previous)
315 if (DEBUG) System.out.println(this + " previous " + previous);
316 if (previous == null && isVariableAngle() && next != null && !isRemoved()) {
317 convertVariableAngleToFixed(Direction.PREVIOUS);
319 this.previous = previous;
320 if (component != null) {
321 if (parent == null || isSub)
322 component.setPrevious(previous != null ? previous.component : null);
324 component.setBranch0(previous != null ? previous.component : null);
330 private void convertVariableAngleToFixed(Direction direction) {
331 // We are removing reference, which transforms variable angle to fixed angle.
332 // Since fixed angle is defined differently, we need to calculate fixed angle parameters based on current data
333 // We need to calculate turnAngle and rotationAngle
334 Vector3d dirOut = getPathLegDirection(direction == Direction.NEXT ? Direction.NEXT : Direction.PREVIOUS);
335 Vector3d dir = getPathLegDirection(direction == Direction.NEXT ? Direction.PREVIOUS : Direction.NEXT);
336 if (dir == null || dirOut == null)
339 double angle = dir.angle(dirOut);
340 //super._setNext(null);
341 if (direction == Direction.NEXT)
345 setRotationAngle(0.0);
346 setReversed(direction == Direction.NEXT ? false : true);
347 Vector3d dirOutN = getPathLegDirection(direction == Direction.NEXT ? Direction.NEXT : Direction.PREVIOUS);
349 AxisAngle4d aa = new AxisAngle4d();
350 if (MathTools.createRotation(dirOutN, dirOut, dir, aa)) {
351 setRotationAngle(aa.angle);
353 if (DEBUG) System.out.println("convertToFixed " + dir + " " + dirOut + " " +dirOutN + " " +angle + " "+ aa.angle);
357 public PipeControlPoint parent;
358 public List<PipeControlPoint> children = new ArrayList<PipeControlPoint>();
360 public List<PipeControlPoint> getChildPoints() {
364 public PipeControlPoint getParentPoint() {
369 private double length;
370 private Double turnAngle;
371 private Vector3d turnAxis;
373 private Double offset;
374 private Double rotationAngle;
375 private Boolean reversed;
377 @GetPropertyValue(name="Length",tabId="Debug",value="length")
378 public double getLength() {
382 public void setLength(double l) {
383 if (this.length == l)
385 if (Double.isInfinite(l) || Double.isNaN(l))
387 if (Math.abs(this.length-l) < MathTools.NEAR_ZERO)
390 firePropertyChanged("length");
392 getDualSub().setLength(l);
395 @GetPropertyValue(name="Turn Angle",tabId="Debug",value="turnAngle")
396 public Double getTurnAngle() {
400 @GetPropertyValue(name="Turn Axis",tabId="Debug",value="turnAxis")
401 public Vector3d getTurnAxis() {
405 @GetPropertyValue(name="Offset",tabId="Debug",value="offset")
406 public Double getOffset() {
410 @GetPropertyValue(name="Rotation Angle",tabId="Debug",value="rotationAngle")
411 public Double getRotationAngle() {
412 if (isRotate || asFixedAngle())
413 return rotationAngle;
417 @GetPropertyValue(name="Reversed",tabId="Debug",value="reversed")
418 public Boolean getReversed() {
422 public boolean _getReversed() {
423 if (reversed == null)
428 public void setTurnAngle(Double turnAngle) {
429 if (turnAngle == null || Double.isInfinite(turnAngle) || Double.isNaN(turnAngle)) {
432 if (this.turnAngle != null && Math.abs(this.turnAngle-turnAngle) < MathTools.NEAR_ZERO)
434 if (Objects.equals(this.turnAngle, turnAngle))
436 this.turnAngle = turnAngle;
437 firePropertyChanged("turnAngle");
440 public void setTurnAxis(Vector3d turnAxis) {
441 if (this.turnAxis != null && MathTools.equals(turnAxis, this.turnAxis))
443 this.turnAxis = turnAxis;
444 firePropertyChanged("turnAxis");
447 public void setOffset(Double offset) {
448 if (Double.isInfinite(offset) || Double.isNaN(offset)) {
451 if (this.offset != null && Math.abs(this.offset-offset) < MathTools.NEAR_ZERO)
453 if (Objects.equals(this.offset, offset))
455 this.offset = offset;
456 firePropertyChanged("offset");
459 public void setRotationAngle(Double rotationAngle) {
460 if (Double.isInfinite(rotationAngle) || Double.isNaN(rotationAngle)) {
463 if (this.rotationAngle != null && Math.abs(this.rotationAngle-rotationAngle) < MathTools.NEAR_ZERO)
465 if (Objects.equals(this.rotationAngle, rotationAngle))
467 this.rotationAngle = rotationAngle;
468 firePropertyChanged("rotationAngle");
471 public void setReversed(Boolean reversed) {
472 if (this.reversed == reversed)
474 this.reversed = reversed;
475 firePropertyChanged("reversed");
478 public Vector3d getSizeChangeOffsetVector(Vector3d dir) {
480 if (rotationAngle == null)
481 q = getControlPointOrientationQuat(dir, 0.0);
483 q = getControlPointOrientationQuat(dir, rotationAngle);
484 Vector3d v = new Vector3d(0.0,offset,0.0);
485 Vector3d offset = new Vector3d();
486 MathTools.rotate(q, v, offset);
490 public Vector3d getSizeChangeOffsetVector() {
492 if (rotationAngle == null)
493 q = getControlPointOrientationQuat(0.0);
495 q = getControlPointOrientationQuat(rotationAngle);
496 Vector3d v = new Vector3d(0.0,offset,0.0);
497 Vector3d offset = new Vector3d();
498 MathTools.rotate(q, v, offset);
502 @GetPropertyValue(name="Next",tabId="Debug",value="next")
503 private String getNextString() {
506 return next.toString();
509 @GetPropertyValue(name="Previous",tabId="Debug",value="previous")
510 private String getPrevString() {
511 if (previous == null)
513 return previous.toString();
516 @GetPropertyValue(name="Sub",tabId="Debug",value="sub")
517 private String getSubString() {
518 if (children.size() == 0)
520 return Arrays.toString(children.toArray());
523 @GetPropertyValue(name="Type",tabId="Debug",value="type")
524 public String getTypeString() {
528 public Vector3d getPathLegEndpointVector() {
529 PipeControlPoint a = findPreviousEnd();
530 PipeControlPoint b = findNextEnd();
532 if (a == null || b == null) {
533 return getPathLegDirection();
536 Vector3d p1 = a.getWorldPosition();
537 Vector3d p2 = b.getWorldPosition();
539 double l = p2.length();
545 return getPathLegDirection();
549 public Vector3d getPathLegDirection() {
550 if (turnAxis == null) {
551 return getPathLegDirection(Direction.NEXT);
553 Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
554 if (dir != null) dir.negate();
559 public Quat4d getControlPointOrientationQuat(double angle) {
560 Vector3d dir = getPathLegDirection();
561 if (turnAxis == null) {
562 return getControlPointOrientationQuat(dir, angle);
564 return getControlPointOrientationQuat(dir, turnAxis, angle);
568 public Quat4d getControlPointOrientationQuat(Vector3d dir, double angle, boolean reversed) {
569 if (turnAxis == null) {
570 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
572 Quat4d q = getControlPointOrientationQuat(dir, angle);
574 Quat4d q2 = new Quat4d();
575 q2.set(new AxisAngle4d(MathTools.Y_AXIS, Math.PI));
580 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
582 return getControlPointOrientationQuat(dir, turnAxis, angle);
586 public Quat4d getControlPointOrientationQuat(double angle, boolean reversed) {
587 Vector3d dir = getPathLegDirection();
588 return getControlPointOrientationQuat(dir, angle, reversed);
591 public Quat4d getControlPointOrientationQuat(Vector3d dir, double angle) {
592 if (dir == null || dir.lengthSquared() < MathTools.NEAR_ZERO)
593 return MathTools.getIdentityQuat();
595 final P3DRootNode root = getRoot();
596 Vector3d up = root != null ? new Vector3d(root.getUpVector()) : new Vector3d(0.0, 1.0, 0.0);
597 final Vector3d legDir = getPathLegEndpointVector();
598 double a = up.angle(legDir);
599 if (a < 0.1 || (Math.PI - a) < 0.1) {
601 up.set(up.getY(), up.getZ(), up.getX());
604 // Project up vector into a normal of the leg direction before applying to 'dir'
605 MathTools.mad(up, legDir, -legDir.dot(up)/legDir.lengthSquared());
608 return getControlPointOrientationQuat(dir, up, angle);
611 public P3DRootNode getRoot() {
612 INode n = getParent();
613 while (n != null && !(n instanceof P3DRootNode))
615 return (P3DRootNode) n;
618 public static Quat4d getControlPointOrientationQuat(Vector3d dir, Vector3d up, double angle) {
619 if (dir == null || dir.lengthSquared() < MathTools.NEAR_ZERO)
620 return MathTools.getIdentityQuat();
622 final Vector3d front = new Vector3d(1.0,0.0,0.0);
624 Quat4d q1 = new Quat4d();
627 Vector3d right = new Vector3d();
629 up = new Vector3d(up);
630 right.cross(dir, up);
631 up.cross(right, dir);
635 Matrix3d m = new Matrix3d();
646 //q1.set(m); MathTools contains more stable conversion
647 MathTools.getQuat(m, q1);
649 // if (DEBUG) System.out.println("PipingTools.getPipeComponentOrientationQuat() " + dir+ " " + up + " " + right);
651 Quat4d q2 = new Quat4d();
652 q2.set(new AxisAngle4d(front, angle));
657 public void insert(PipeControlPoint previous, PipeControlPoint next) {
658 // inserting an offsetpoint is error,
660 throw new RuntimeException("Dual sub points cannot be inserted.");
661 // size change control point cannot be inserted this way, because it ends PipeRun
662 // if (isSizeChange())
663 // throw new RuntimeException("Size change points cannot be inserted.");
664 PipeRun piperun = previous.getPipeRun();
665 // and just to make sure that control point structure is not corrupted
666 if (getPipeRun() != null) {
667 if (piperun != getPipeRun() || piperun != next.getPipeRun())
668 throw new RuntimeException("All controls points must be located on the same pipe run");
670 piperun.addChild(this);
673 // insert new BranchControlPoint between straight's control points
674 PipeControlPoint previousNext = previous.getNext();
675 PipeControlPoint previousPrevious = previous.getPrevious();
677 PipeControlPoint offsetCP = null;
679 offsetCP = getDualSub();
681 if (previousNext != null && previousNext == next) {
682 if (previous.isDualInline()) {
683 throw new RuntimeException();
685 if (next.isDualSub()) {
686 throw new RuntimeException();
688 previous.setNext(this);
689 this.setPrevious(previous);
690 if (previous.isDualSub()) {
691 previous.getParentPoint().setNext(this);
695 if (offsetCP == null) {
696 next.setPrevious(this);
698 next.setPrevious(offsetCP);
699 offsetCP.setNext(next);
700 offsetCP.setPrevious(previous);
703 if (next.isDualInline()) {
704 next.getDualSub().setPrevious(this);
706 } else if (previousPrevious != null && previousPrevious == next) {
707 // control point were given in reverse order
708 if (next.isDualInline())
709 throw new RuntimeException();
710 if (previous.isDualSub())
711 throw new RuntimeException();
713 this.setNext(previous);
714 if (offsetCP == null) {
715 previous.setNext(this);
717 previous.setPrevious(offsetCP);
718 offsetCP.setNext(previous);
719 offsetCP.setPrevious(next);
721 if (previous.isDualInline()) {
722 previous.getDualSub().setPrevious(this);
724 this.setPrevious(next);
726 if (next.isDualSub()) {
727 next.getParentPoint().setNext(this);
731 throw new RuntimeException();
734 PipingRules.validate(piperun);
739 public void insert(PipeControlPoint pcp, Direction direction) {
741 throw new RuntimeException();
742 if (direction == Direction.NEXT) {
743 // if direction is next, user must have given OffsetPoint
744 if (pcp.isDualInline())
745 throw new RuntimeException();
746 // basic next/prev links
748 this.setPrevious(pcp);
749 // and last take care of sizechange / offset points
750 if (pcp.isDualSub()) {
751 pcp.getParentPoint().setNext(this);
754 // if direction is previous, user must have given sizechange
756 throw new RuntimeException();
757 // previous direction is more complicated, since if newCP is SizeChangeControlPoint,
758 // we must link pcp to newCP's OffsetPoint
759 PipeControlPoint nocp = null;
760 if (isDualInline()) {
765 pcp.setPrevious(this);
767 pcp.setPrevious(nocp);
770 if (pcp.isDualInline()) {
771 PipeControlPoint ocp = pcp.getDualSub();
773 ocp.setPrevious(this);
775 ocp.setPrevious(nocp);
779 PipingRules.validate(getPipeRun());
782 public Vector3d getDirectedControlPointDirection() {
783 assert (isDirected());
784 Vector3d dir = new Vector3d();
785 MathTools.rotate(getWorldOrientation(), new Vector3d(1.0, 0.0, 0.0), dir);
791 * Returns direction vector pointing towards an adjacent component for
792 * directed control points or turn control points with one open end.
794 * Always returns an outwards pointing vector.
796 * For any other type of component, the return value is null.
798 * For turn components this only return a non-null value for the unconnected
799 * end of the component.
802 * @return normalized vector, or null
804 public Vector3d getDirection(Direction direction) {
806 return getDirectedControlPointDirection();
807 if (isTurn() && asFixedAngle()) {
808 if (direction == Direction.NEXT) {
809 if (previous != null) {
810 PipeControlPoint pcp = this;
811 Vector3d dir = new Vector3d();
812 dir.sub(pcp.getWorldPosition(),previous.getWorldPosition());
813 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
817 Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
818 AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
819 Quat4d q2 = MathTools.getQuat(aa);
820 Vector3d v = new Vector3d(1.,0.,0.);
821 Vector3d offset = new Vector3d();
822 MathTools.rotate(q2, v, offset);
823 MathTools.rotate(q, offset, dir);
829 PipeControlPoint pcp = this;
830 Vector3d dir = new Vector3d();
831 dir.sub(next.getWorldPosition(),pcp.getWorldPosition());
832 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
836 Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
837 AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
838 Quat4d q2 = MathTools.getQuat(aa);
839 Vector3d v = new Vector3d(1.,0.,0.);
840 Vector3d offset = new Vector3d();
841 MathTools.rotate(q2, v, offset);
842 MathTools.rotate(q, offset, dir);
853 * Returns path leg direction of the control point.
855 * This method differs from getDirection by also returning inward pointing vectors for directed control points.
860 public Vector3d getPathLegDirection(Direction direction) {
861 if (direction == Direction.NEXT) {
862 return getPathLegDirectionNext();
864 return getPathLegDirectionPrevious();
868 public Vector3d getPathLegDirectionPrevious() {
869 if (previous != null) {
870 PipeControlPoint pcp = this;
872 pcp = getParentPoint();
873 Vector3d v = new Vector3d();
874 v.sub(previous.getWorldPosition(),pcp.getWorldPosition());
875 if (v.lengthSquared() > MathTools.NEAR_ZERO)
880 } else if (isDirected()) {
881 Vector3d v = getDirectedControlPointDirection();
884 } else if (next == null) {
885 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
886 } else if (isVariableAngle() && !asFixedAngle()) {
887 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
888 } else if (isInline() || isEnd()) {
889 Vector3d v = getPathLegDirectionNext();
890 if (v != null) v.negate();
892 } else if (isTurn() && asFixedAngle() && _getReversed()) {
893 return getDirection(Direction.PREVIOUS);
895 throw new RuntimeException("Missing implementation " + this);
899 public Vector3d getPathLegDirectionNext() {
901 PipeControlPoint pcp = this;
902 if (pcp.isDualInline()) {
903 pcp = pcp.getDualSub();
905 Vector3d v = new Vector3d();
906 v.sub(next.getWorldPosition(),pcp.getWorldPosition());
907 if (v.lengthSquared() > MathTools.NEAR_ZERO)
912 } else if (isDirected()) {
913 return getDirectedControlPointDirection();
914 } else if (previous == null) {
915 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
916 } else if (isVariableAngle() && !asFixedAngle()) {
917 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
918 } else if (isInline() || isEnd()) {
919 Vector3d v = getPathLegDirectionPrevious();
920 if (v != null) v.negate();
922 } else if (isTurn() && asFixedAngle() && !_getReversed()) {
923 return getDirection(Direction.NEXT);
925 throw new RuntimeException("Missing implementation " + this);
929 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) {
932 PipeControlPoint sub = isAxial() ? this : getDualSub();
933 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
934 Vector3d dir = sub.getInlineDir();
936 dir.scale(length * 0.5);
943 public void getControlPointEnds(Tuple3d p1, Tuple3d p2) {
944 PipeControlPoint sub = isAxial() || isDirected() || isTurn() ? this : getChildPoints().get(0);
945 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
950 dir2 = getInlineDir();
951 dir2.scale(length * 0.5);
952 dir1 = new Vector3d(dir2);
955 dir1 = getPathLegDirection(Direction.PREVIOUS);
956 dir2 = sub.getPathLegDirection(Direction.NEXT);
967 * Get both path leg directions, with (0,0,0) if no connection exists. The returned vectors are not normalized.
969 * @param v1 Set to the direction towards the previous control point on output
970 * @param v2 Set to the direction towards the next control point on output
972 public void getEndDirections(Tuple3d v1, Tuple3d v2) {
973 PipeControlPoint sub = isAxial() ? this : getDualSub();
975 Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
976 Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
989 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) {
992 Vector3d pos = getWorldPosition();
993 dir.set(getInlineDir());
995 dir.scale(length * 0.5);
1002 public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) {
1003 assert (isInline());
1005 Vector3d pos = getWorldPosition();
1007 dir.set(getInlineDir());
1009 dir.scale(length * 0.5);
1016 public Vector3d getInlineDir() {
1017 Vector3d dir = getPathLegDirection(Direction.NEXT);
1019 dir = getPathLegDirection(Direction.PREVIOUS);
1021 // Use reverse direction
1024 // Control point is not connected at all, use current orientation
1025 dir = new Vector3d(1,0,0);
1026 MathTools.rotate(getWorldOrientation(), dir, dir);
1032 public double getInlineLength() {
1033 if (type == PointType.TURN)
1035 else if (type == PointType.INLINE)
1036 return length * 0.5;
1041 * Return the position indicated by the argument. If the indicated direction is not connected, the
1042 * control point's wolrd position is returned instead.
1044 * @param type A selector for the position to be returned
1045 * @return The selected position
1047 public Vector3d getRealPosition(PositionType type) {
1048 Vector3d pos = getWorldPosition();
1051 double length = getInlineLength();
1054 dir = getInlineDir();
1056 dir = getPathLegDirection(Direction.NEXT);
1063 double length = getInlineLength();
1066 dir = getInlineDir();
1069 dir = getPathLegDirection(Direction.PREVIOUS);
1076 // IEntity portDir = pcp.getSingleRelatedObject(ProcessResource.plant3Dresource.HasDirection);
1077 // TODO : how we calculated needed space for a port; does it has an offset from control point's position or not?
1087 public void getInlineMovement(Tuple3d start, Tuple3d end) {
1088 // FIXME : check type of neighbor components and allow movement on top of variable length components,
1089 // find proper range for movement (pcp's position is not)
1090 PipeControlPoint p = previous.getPrevious();
1091 PipeControlPoint n = next.getNext();
1092 start.set(p.getWorldPosition());
1093 end.set(n.getWorldPosition());
1096 public PipeControlPoint findNextEnd() {
1097 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1098 return findNextEnd( t);
1101 public PipeControlPoint findPreviousEnd() {
1102 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1103 return findPreviousEnd(t);
1106 public PipeControlPoint findNextEnd(List<PipeControlPoint> nextList) {
1108 PipeControlPoint pcp = null;
1109 PipeControlPoint p = null;
1110 if (nextList.size() == 0)
1114 p = nextList.get(nextList.size() - 1);
1119 if (nextList.size() > 0)
1120 nextList.remove(nextList.size() - 1);
1121 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1125 if (pcp.isPathLegEnd()) {
1126 //if (DEBUG) System.out.println(" " + pcp.getResource());
1130 // if (DEBUG) System.out.print(" " + pcp.getResource());
1135 public PipeControlPoint findPreviousEnd(List<PipeControlPoint> prevList) {
1137 PipeControlPoint pcp = null;
1138 PipeControlPoint p = null;
1139 if (prevList.size() == 0)
1143 p = prevList.get(prevList.size() - 1);
1145 pcp = p.getPrevious();
1148 if (prevList.size() > 0)
1149 prevList.remove(prevList.size() - 1);
1150 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1153 if (pcp.isPathLegEnd()) {
1154 // if (DEBUG) System.out.println(" " + pcp.getResource());
1158 // if (DEBUG)System.out.print(" " + pcp.getResource());
1163 public void _remove() {
1168 public PipeControlPoint getDualSub() {
1170 return getChildPoints().get(0);
1172 throw new IllegalStateException("Current control point is not dual inline");
1176 public void _remove(boolean renconnect) {
1180 if (DEBUG) System.out.println(this + " Remove " + renconnect);
1182 if (getParentPoint() != null) {
1183 getParentPoint()._remove(renconnect);
1186 PipeRun pipeRun = getPipeRun();
1187 // PipeRUn removal has been changed, so pipeRun may be null.
1188 // if (pipeRun == null)
1191 PipeControlPoint additionalRemove = null;
1192 if (!PipingRules.isEnabled()) {
1198 PipeControlPoint currentPrev = previous;
1199 PipeControlPoint currentNext = next;
1200 if (currentNext == null && currentPrev == null) {
1202 if (pipeRun != null) {
1203 pipeRun.remChild(this);
1204 checkRemove(pipeRun);
1208 if (currentNext != null && currentPrev != null) {
1209 boolean link = renconnect;
1210 if (currentNext.isBranchEnd()) {
1212 currentNext.remove();
1216 if (currentPrev.isBranchEnd()) {
1218 currentPrev.remove();
1223 if (currentPrev.isDirected() && currentNext.isDirected())
1225 else if (this.isDualSub()) {
1226 throw new RuntimeException("_remove() is called for parent point, somehow got to child point. " + this);
1229 if (currentNext == null) {
1231 } else if (currentNext.isDualInline()) {
1232 PipeControlPoint sccp = currentNext;
1233 PipeControlPoint ocp = currentNext.getDualSub();
1235 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1238 sccp.setPrevious(currentPrev);
1239 //ocp.setPrevious(currentPrev);
1240 assert(ocp.getPrevious() == currentPrev);
1242 sccp.setPrevious(null);
1243 //ocp.setPrevious(null);
1244 assert(ocp.getPrevious() == null);
1247 } else if (currentNext.isDualSub()) {
1248 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1249 } else if (currentNext.previous == this) {
1251 currentNext.setPrevious(currentPrev);
1253 currentNext.setPrevious(null);
1256 } else if (isDualInline()) {
1257 if (currentNext.previous != getDualSub()) {
1258 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1261 currentNext.setPrevious(currentPrev);
1263 currentNext.setPrevious(null);
1267 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1269 if (currentPrev == null) {
1271 } else if (currentPrev.isDualInline()) {
1272 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1273 } else if (currentPrev.isDualSub()) {
1274 PipeControlPoint ocp = currentPrev;
1275 PipeControlPoint sccp = currentPrev.getParentPoint();
1277 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1279 //ocp.setNext(currentNext);
1280 sccp.setNext(currentNext);
1281 assert(ocp.getNext() == currentNext);
1283 //ocp.setNext(null);
1285 assert(ocp.getNext() == null);
1288 } else if (currentPrev.next == this) {
1290 currentPrev.setNext(currentNext);
1292 currentPrev.setNext(null);
1296 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged");
1299 if (currentNext.isVariableLength() && currentPrev.isVariableLength()) {
1300 // we have to join them into single variable length component.
1301 additionalRemove = currentPrev;
1302 // combine lengths and set the location of remaining control point to the center.
1303 Point3d ps = new Point3d();
1304 Point3d pe = new Point3d();
1305 Point3d ns = new Point3d();
1306 Point3d ne = new Point3d();
1307 currentPrev.getInlineControlPointEnds(ps, pe);
1308 currentNext.getInlineControlPointEnds(ns, ne);
1309 double l = currentPrev.getLength() + currentNext.getLength();
1310 Vector3d cp = new Vector3d();
1313 currentNext.setLength(l);
1314 currentNext.setWorldPosition(cp);
1317 // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous.
1319 } else if (currentNext != null) {
1320 if (currentNext.isDualInline()) {
1321 PipeControlPoint sccp = currentNext;
1322 PipeControlPoint ocp = currentNext.getDualSub();
1324 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1326 sccp.setPrevious(null);
1327 assert(ocp.getPrevious() == null);
1328 //ocp.setPrevious(null);
1329 } else if (currentNext.isDualSub()) {
1330 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1331 } else if (currentNext.previous == this) {
1332 currentNext.setPrevious(null);
1333 } else if (isDualInline()) {
1334 if (currentNext.previous != getDualSub()) {
1335 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1337 currentNext.setPrevious(null);
1339 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1342 } else { //(previous != null)
1343 if(currentPrev.isDualInline()) {
1344 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1345 } else if (currentPrev.isDualSub()) {
1346 PipeControlPoint ocp = currentPrev;
1347 PipeControlPoint sccp = currentPrev.getParentPoint();
1349 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1352 assert(ocp.getNext() == null);
1353 } else if (currentPrev.next == this) {
1354 currentPrev.setNext(null);
1356 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1360 if (children.size() > 0 ) {
1362 } else if (parent!= null) {
1363 removeParentPoint();
1369 if (pipeRun != null) {
1370 pipeRun.remChild(this);
1371 checkRemove(pipeRun);
1372 if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
1373 PipingRules.validate(pipeRun);
1375 if (additionalRemove != null)
1376 additionalRemove.remove();
1381 * Removes control point and attempts to reconnect next/prev
1383 * If this point is size change (PipeRuns are different on both sides), then reconnection cannot be made.
1385 public void remove() {
1386 PipeControlPoint currentPrev = previous;
1387 PipeControlPoint currentNext = next;
1390 if (currentNext != null)
1391 if (!currentNext.checkRemove())
1392 PipingRules.requestUpdate(currentNext);
1393 if (currentPrev != null)
1394 if (!currentPrev.checkRemove())
1395 PipingRules.requestUpdate(currentPrev);
1396 } catch (Exception e) {
1397 e.printStackTrace();
1403 * Removes control point without attempting to reconnect next/prev.
1404 * This usually leads to creation of another PipeRun for the control points after this point.
1406 public void removeAndSplit() {
1407 PipeControlPoint currentPrev = previous;
1408 PipeControlPoint currentNext = next;
1410 if (next != null && previous != null) {
1411 P3DRootNode root = (P3DRootNode)getPipelineComponent().getRootNode();
1412 PipeRun nextPipeRun = new PipeRun();
1413 nextPipeRun.setName(root.getUniqueName("PipeRun"));
1414 root.addChild(nextPipeRun);
1416 PipeRun previousRun = previous.getPipeRun();
1417 nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
1418 nextPipeRun.setPipeThickness(previousRun.getPipeThickness());
1419 nextPipeRun.setTurnRadiusArray(previousRun.getTurnRadiusArray());
1421 PipelineComponent n = next.getPipelineComponent();
1423 if (n.getPipeRun() != previousRun)
1425 if (! (n instanceof Nozzle)) {
1427 nextPipeRun.addChild(n);
1429 n.setPipeRun(nextPipeRun);
1435 if (currentNext != null)
1436 if (!currentNext.checkRemove())
1437 PipingRules.requestUpdate(currentNext);
1438 if (currentPrev != null)
1439 if (!currentPrev.checkRemove())
1440 PipingRules.requestUpdate(currentPrev);
1441 } catch (Exception e) {
1442 e.printStackTrace();
1447 * This is called when adjacent control point is removed.
1449 * This call should remove the give point, if the point cannot exist alone.
1450 * At the moment there is one such case: branch.
1454 protected boolean checkRemove() {
1455 if (getParentPoint() != null) {
1456 return getParentPoint().checkRemove();
1458 if (getPipelineComponent() == null)
1459 return true; // already removed
1460 if (getPipelineComponent().getType().equals("Plant3D.URIs.Builtin_BranchSplitComponent")) {
1461 if (getChildPoints().get(0).getNext() == null && getChildPoints().get(0).getPrevious() == null) {
1466 return checkRemove(getPipeRun());
1470 private boolean checkRemove(PipeRun pipeRun) {
1471 if (pipeRun == null)
1473 Collection<PipeControlPoint> points = pipeRun.getControlPoints();
1474 if (points.size() == 0) {
1477 } else if (points.size() == 1) {
1478 PipeControlPoint pcp = points.iterator().next();
1479 if (pcp.isDeletable() && pcp.getNext() == null && pcp.getPrevious() == null) {
1480 pcp._remove(); // This call will recursively call also this method...
1483 } else if (points.size() == 2) {
1489 private void removeSubPoints() {
1490 for (PipeControlPoint p : children) {
1494 PipeControlPoint currentNext = p.getNext();
1495 PipeControlPoint currentPrev = p.getPrevious();
1497 p._setPrevious(null);
1498 PipeRun run = p.getPipeRun();
1503 if (currentNext != null)
1504 if (!currentNext.checkRemove())
1505 PipingRules.requestUpdate(currentNext);
1506 if (currentPrev != null)
1507 if (!currentPrev.checkRemove())
1508 PipingRules.requestUpdate(currentPrev);
1514 private void removeParentPoint() {
1515 throw new RuntimeException("Child points cannot be removed directly");
1518 public boolean isRemoved() {
1519 return component == null;
1522 private void removeComponent() {
1523 if (component == null)
1525 PipelineComponent next = component.getNext();
1526 PipelineComponent prev = component.getPrevious();
1527 PipelineComponent br0 = component.getBranch0();
1528 component.setNext(null);
1529 component.setPrevious(null);
1530 component.setBranch0(null);
1532 if (next.getNext() == component)
1534 else if (next.getPrevious() == component)
1535 next.setPrevious(null);
1536 else if (next.getBranch0() == component)
1537 next.setBranch0(null);
1540 if (prev.getNext() == component)
1542 else if (prev.getPrevious() == component)
1543 prev.setPrevious(null);
1544 else if (prev.getBranch0() == component)
1545 prev.setBranch0(null);
1548 if (br0.getNext() == component)
1550 else if (br0.getPrevious() == component)
1551 br0.setPrevious(null);
1552 else if (br0.getBranch0() == component)
1553 br0.setBranch0(null);
1555 PipelineComponent comp = component;
1562 public void setOrientation(Quat4d orientation) {
1563 if (MathTools.equals(orientation, getOrientation()))
1565 if (getPipelineComponent() != null && (getPipelineComponent() instanceof Nozzle))
1566 System.out.println();
1567 super.setOrientation(orientation);
1568 if (getParentPoint() == null && component != null)
1569 component._setWorldOrientation(getWorldOrientation());
1574 public void setPosition(Vector3d position) {
1575 if (MathTools.equals(position, getPosition()))
1577 if (Double.isNaN(position.x) || Double.isNaN(position.y) || Double.isNaN(position.z))
1578 throw new IllegalArgumentException("NaN is not supported");
1579 super.setPosition(position);
1580 if (getParentPoint() == null && component != null)
1581 component._setWorldPosition(getWorldPosition());
1585 private void updateSubPoint() {
1587 if (next == null && previous == null) {
1588 for (PipeControlPoint sub : getChildPoints()) {
1589 sub.setWorldPosition(getWorldPosition());
1590 sub.setWorldOrientation(getWorldOrientation());
1594 for (PipeControlPoint sub : getChildPoints()) {
1595 Vector3d wp = getWorldPosition();
1596 wp.add(getSizeChangeOffsetVector());
1597 sub.setWorldPosition(wp);
1598 sub.setWorldOrientation(getWorldOrientation());
1601 for (PipeControlPoint sub : getChildPoints()) {
1602 sub.setWorldPosition(getWorldPosition());
1603 sub.setWorldOrientation(getWorldOrientation());
1609 public void _setWorldPosition(Vector3d position) {
1610 Vector3d localPos = getLocalPosition(position);
1611 super.setPosition(localPos);
1615 public void _setWorldOrientation(Quat4d orientation) {
1616 Quat4d localOr = getLocalOrientation(orientation);
1617 super.setOrientation(localOr);
1621 public void orientToDirection(Vector3d dir) {
1622 Double angleO = getRotationAngle();
1626 boolean reversed = _getReversed();
1629 q = getControlPointOrientationQuat(dir, angle, reversed);
1631 q = getControlPointOrientationQuat(angle, reversed);
1633 setWorldOrientation(q);
1637 public String toString() {
1638 return getClass().getName() + "@" + Integer.toHexString(hashCode());