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(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);
753 if (isDualInline()) {
754 getDualSub().setPrevious(this);
757 // if direction is previous, user must have given sizechange
759 throw new RuntimeException();
760 // previous direction is more complicated, since if newCP is SizeChangeControlPoint,
761 // we must link pcp to newCP's OffsetPoint
762 PipeControlPoint nocp = null;
763 if (isDualInline()) {
768 pcp.setPrevious(this);
770 pcp.setPrevious(nocp);
773 if (pcp.isDualInline()) {
774 PipeControlPoint ocp = pcp.getDualSub();
776 ocp.setPrevious(this);
778 ocp.setPrevious(nocp);
782 PipingRules.validate(getPipeRun());
785 public Vector3d getDirectedControlPointDirection() {
786 assert (isDirected());
787 Vector3d dir = new Vector3d();
788 MathTools.rotate(getWorldOrientation(), new Vector3d(1.0, 0.0, 0.0), dir);
794 * Returns direction vector.
796 * For directed control points, always returns outwards pointing vector.
799 * @return normalized vector, or null
801 public Vector3d getDirection(Direction direction) {
803 return getDirectedControlPointDirection();
804 if (isTurn() && asFixedAngle()) {
805 if (direction == Direction.NEXT) {
806 if (previous != null) {
807 PipeControlPoint pcp = this;
808 Vector3d dir = new Vector3d();
809 dir.sub(pcp.getWorldPosition(),previous.getWorldPosition());
810 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
814 Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
815 AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
816 Quat4d q2 = MathTools.getQuat(aa);
817 Vector3d v = new Vector3d(1.,0.,0.);
818 Vector3d offset = new Vector3d();
819 MathTools.rotate(q2, v, offset);
820 MathTools.rotate(q, offset, dir);
826 PipeControlPoint pcp = this;
827 Vector3d dir = new Vector3d();
828 dir.sub(next.getWorldPosition(),pcp.getWorldPosition());
829 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
833 Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
834 AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
835 Quat4d q2 = MathTools.getQuat(aa);
836 Vector3d v = new Vector3d(1.,0.,0.);
837 Vector3d offset = new Vector3d();
838 MathTools.rotate(q2, v, offset);
839 MathTools.rotate(q, offset, dir);
849 * Returns path leg direction of the control point.
851 * This method differs from getDirection by also returning inward pointing vectors for directed control points.
856 public Vector3d getPathLegDirection(Direction direction) {
857 if (direction == Direction.NEXT) {
859 PipeControlPoint pcp = this;
860 if (pcp.isDualInline()) {
861 pcp = pcp.getDualSub();
863 Vector3d v = new Vector3d();
864 v.sub(next.getWorldPosition(),pcp.getWorldPosition());
865 if (v.lengthSquared() > MathTools.NEAR_ZERO)
871 if (previous == null) {
873 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
874 return getDirectedControlPointDirection();
877 if (isVariableAngle() && !asFixedAngle())
878 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
880 PipeControlPoint pcp = this;
881 if (pcp.isDualSub()) {
882 pcp = pcp.getParentPoint();
884 Vector3d v = new Vector3d();
885 v.sub(pcp.getWorldPosition(),previous.getWorldPosition());
886 if (v.lengthSquared() > MathTools.NEAR_ZERO)
891 } else if (isDirected()) {
892 return getDirectedControlPointDirection();
893 } else if (isEnd()) {
894 Vector3d v = new Vector3d();
895 v.sub(getWorldPosition(),previous.getWorldPosition());
896 if (v.lengthSquared() > MathTools.NEAR_ZERO)
901 } else if (isTurn() && asFixedAngle() && !_getReversed()) {
902 return getDirection(Direction.NEXT);
904 throw new RuntimeException("Missing implementation " + this);
908 if (previous != null) {
909 PipeControlPoint pcp = this;
911 pcp = getParentPoint();
912 Vector3d v = new Vector3d();
913 v.sub(previous.getWorldPosition(),pcp.getWorldPosition());
914 if (v.lengthSquared() > MathTools.NEAR_ZERO)
922 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
923 Vector3d v = getDirectedControlPointDirection();
927 if (isVariableAngle() && !asFixedAngle())
928 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
930 PipeControlPoint pcp = this;
931 if (pcp.isDualInline()) {
932 pcp = pcp.getDualSub();
934 Vector3d v = new Vector3d();
935 v.sub(pcp.getWorldPosition(),next.getWorldPosition());
936 if (v.lengthSquared() > MathTools.NEAR_ZERO)
941 } else if (isDirected()) {
942 Vector3d v = getDirectedControlPointDirection();
945 } else if (isEnd()) {
946 Vector3d v = new Vector3d();
947 v.sub(getWorldPosition(),next.getWorldPosition());
948 if (v.lengthSquared() > MathTools.NEAR_ZERO)
953 } else if (isTurn() && asFixedAngle() && _getReversed()) {
954 return getDirection(Direction.PREVIOUS);
956 throw new RuntimeException("Missing implementation " + this);
962 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) {
965 PipeControlPoint sub = isAxial() ? this : getDualSub();
966 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
967 Vector3d dir = sub.getInlineDir();
969 dir.scale(length * 0.5);
976 public void getControlPointEnds(Tuple3d p1, Tuple3d p2) {
977 PipeControlPoint sub = isAxial() || isDirected() || isTurn() ? this : getChildPoints().get(0);
978 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
983 dir2 = getInlineDir();
984 dir2.scale(length * 0.5);
985 dir1 = new Vector3d(dir2);
988 dir1 = getPathLegDirection(Direction.PREVIOUS);
989 dir2 = sub.getPathLegDirection(Direction.NEXT);
1000 * Get both path leg directions, with (0,0,0) if no connection exists. The returned vectors are not normalized.
1002 * @param v1 Set to the direction towards the previous control point on output
1003 * @param v2 Set to the direction towards the next control point on output
1005 public void getEndDirections(Tuple3d v1, Tuple3d v2) {
1006 PipeControlPoint sub = isAxial() ? this : getDualSub();
1008 Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
1009 Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
1022 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) {
1023 assert (isInline());
1025 Vector3d pos = getWorldPosition();
1026 dir.set(getInlineDir());
1028 dir.scale(length * 0.5);
1035 public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) {
1036 assert (isInline());
1038 Vector3d pos = getWorldPosition();
1040 dir.set(getInlineDir());
1042 dir.scale(length * 0.5);
1049 public Vector3d getInlineDir() {
1050 Vector3d dir = getPathLegDirection(Direction.NEXT);
1052 dir = getPathLegDirection(Direction.PREVIOUS);
1054 // Use reverse direction
1057 // Control point is not connected at all, use current orientation
1058 dir = new Vector3d(1,0,0);
1059 MathTools.rotate(getWorldOrientation(), dir, dir);
1065 public double getInlineLength() {
1066 if (type == PointType.TURN)
1068 else if (type == PointType.INLINE)
1069 return length * 0.5;
1074 * Return the position indicated by the argument. If the indicated direction is not connected, the
1075 * control point's wolrd position is returned instead.
1077 * @param type A selector for the position to be returned
1078 * @return The selected position
1080 public Vector3d getRealPosition(PositionType type) {
1081 Vector3d pos = getWorldPosition();
1084 double length = getInlineLength();
1087 dir = getInlineDir();
1089 dir = getPathLegDirection(Direction.NEXT);
1096 double length = getInlineLength();
1099 dir = getInlineDir();
1102 dir = getPathLegDirection(Direction.PREVIOUS);
1109 // IEntity portDir = pcp.getSingleRelatedObject(ProcessResource.plant3Dresource.HasDirection);
1110 // TODO : how we calculated needed space for a port; does it has an offset from control point's position or not?
1120 public void getInlineMovement(Tuple3d start, Tuple3d end) {
1121 // FIXME : check type of neighbor components and allow movement on top of variable length components,
1122 // find proper range for movement (pcp's position is not)
1123 PipeControlPoint p = previous.getPrevious();
1124 PipeControlPoint n = next.getNext();
1125 start.set(p.getWorldPosition());
1126 end.set(n.getWorldPosition());
1129 public PipeControlPoint findNextEnd() {
1130 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1131 return findNextEnd( t);
1134 public PipeControlPoint findPreviousEnd() {
1135 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1136 return findPreviousEnd(t);
1139 public PipeControlPoint findNextEnd(List<PipeControlPoint> nextList) {
1141 PipeControlPoint pcp = null;
1142 PipeControlPoint p = null;
1143 if (nextList.size() == 0)
1147 p = nextList.get(nextList.size() - 1);
1152 if (nextList.size() > 0)
1153 nextList.remove(nextList.size() - 1);
1154 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1158 if (pcp.isPathLegEnd()) {
1159 //if (DEBUG) System.out.println(" " + pcp.getResource());
1163 // if (DEBUG) System.out.print(" " + pcp.getResource());
1168 public PipeControlPoint findPreviousEnd(List<PipeControlPoint> prevList) {
1170 PipeControlPoint pcp = null;
1171 PipeControlPoint p = null;
1172 if (prevList.size() == 0)
1176 p = prevList.get(prevList.size() - 1);
1178 pcp = p.getPrevious();
1181 if (prevList.size() > 0)
1182 prevList.remove(prevList.size() - 1);
1183 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1186 if (pcp.isPathLegEnd()) {
1187 // if (DEBUG) System.out.println(" " + pcp.getResource());
1191 // if (DEBUG)System.out.print(" " + pcp.getResource());
1196 public void _remove() {
1201 public PipeControlPoint getDualSub() {
1203 return getChildPoints().get(0);
1205 throw new IllegalStateException("Current control point is not dual inline");
1209 public void _remove(boolean renconnect) {
1213 if (DEBUG) System.out.println(this + " Remove " + renconnect);
1215 if (getParentPoint() != null) {
1216 getParentPoint()._remove(renconnect);
1219 PipeRun pipeRun = getPipeRun();
1220 // PipeRUn removal has been changed, so pipeRun may be null.
1221 // if (pipeRun == null)
1224 PipeControlPoint additionalRemove = null;
1225 if (!PipingRules.isEnabled()) {
1231 PipeControlPoint currentPrev = previous;
1232 PipeControlPoint currentNext = next;
1233 if (currentNext == null && currentPrev == null) {
1235 if (pipeRun != null) {
1236 pipeRun.remChild(this);
1237 checkRemove(pipeRun);
1241 if (currentNext != null && currentPrev != null) {
1242 boolean link = renconnect;
1243 if (currentNext.isBranchEnd()) {
1245 currentNext.remove();
1249 if (currentPrev.isBranchEnd()) {
1251 currentPrev.remove();
1256 if (currentPrev.isDirected() && currentNext.isDirected())
1258 else if (this.isDualSub()) {
1259 throw new RuntimeException("_remove() is called for parent point, somehow got to child point. " + this);
1262 if (currentNext == null) {
1264 } else if (currentNext.isDualInline()) {
1265 PipeControlPoint sccp = currentNext;
1266 PipeControlPoint ocp = currentNext.getDualSub();
1268 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1271 sccp.setPrevious(currentPrev);
1272 //ocp.setPrevious(currentPrev);
1273 assert(ocp.getPrevious() == currentPrev);
1275 sccp.setPrevious(null);
1276 //ocp.setPrevious(null);
1277 assert(ocp.getPrevious() == null);
1280 } else if (currentNext.isDualSub()) {
1281 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1282 } else if (currentNext.previous == this) {
1284 currentNext.setPrevious(currentPrev);
1286 currentNext.setPrevious(null);
1289 } else if (isDualInline()) {
1290 if (currentNext.previous != getDualSub()) {
1291 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1294 currentNext.setPrevious(currentPrev);
1296 currentNext.setPrevious(null);
1300 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1302 if (currentPrev == null) {
1304 } else if (currentPrev.isDualInline()) {
1305 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1306 } else if (currentPrev.isDualSub()) {
1307 PipeControlPoint ocp = currentPrev;
1308 PipeControlPoint sccp = currentPrev.getParentPoint();
1310 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1312 //ocp.setNext(currentNext);
1313 sccp.setNext(currentNext);
1314 assert(ocp.getNext() == currentNext);
1316 //ocp.setNext(null);
1318 assert(ocp.getNext() == null);
1321 } else if (currentPrev.next == this) {
1323 currentPrev.setNext(currentNext);
1325 currentPrev.setNext(null);
1329 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged");
1332 if (currentNext.isVariableLength() && currentPrev.isVariableLength()) {
1333 // we have to join them into single variable length component.
1334 additionalRemove = currentPrev;
1335 // combine lengths and set the location of remaining control point to the center.
1336 Point3d ps = new Point3d();
1337 Point3d pe = new Point3d();
1338 Point3d ns = new Point3d();
1339 Point3d ne = new Point3d();
1340 currentPrev.getInlineControlPointEnds(ps, pe);
1341 currentNext.getInlineControlPointEnds(ns, ne);
1342 double l = currentPrev.getLength() + currentNext.getLength();
1343 Vector3d cp = new Vector3d();
1346 currentNext.setLength(l);
1347 currentNext.setWorldPosition(cp);
1350 // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous.
1352 } else if (currentNext != null) {
1353 if (currentNext.isDualInline()) {
1354 PipeControlPoint sccp = currentNext;
1355 PipeControlPoint ocp = currentNext.getDualSub();
1357 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1359 sccp.setPrevious(null);
1360 assert(ocp.getPrevious() == null);
1361 //ocp.setPrevious(null);
1362 } else if (currentNext.isDualSub()) {
1363 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1364 } else if (currentNext.previous == this) {
1365 currentNext.setPrevious(null);
1366 } else if (isDualInline()) {
1367 if (currentNext.previous != getDualSub()) {
1368 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1370 currentNext.setPrevious(null);
1372 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1375 } else { //(previous != null)
1376 if(currentPrev.isDualInline()) {
1377 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1378 } else if (currentPrev.isDualSub()) {
1379 PipeControlPoint ocp = currentPrev;
1380 PipeControlPoint sccp = currentPrev.getParentPoint();
1382 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1385 assert(ocp.getNext() == null);
1386 } else if (currentPrev.next == this) {
1387 currentPrev.setNext(null);
1389 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1393 if (children.size() > 0 ) {
1395 } else if (parent!= null) {
1396 removeParentPoint();
1402 if (pipeRun != null) {
1403 pipeRun.remChild(this);
1404 checkRemove(pipeRun);
1405 if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
1406 PipingRules.validate(pipeRun);
1408 if (additionalRemove != null)
1409 additionalRemove.remove();
1414 * Removes control point and attempts to reconnect next/prev
1416 * If this point is size change (PipeRuns are different on both sides), then reconnection cannot be made.
1418 public void remove() {
1419 PipeControlPoint currentPrev = previous;
1420 PipeControlPoint currentNext = next;
1423 if (currentNext != null)
1424 if (!currentNext.checkRemove())
1425 PipingRules.requestUpdate(currentNext);
1426 if (currentPrev != null)
1427 if (!currentPrev.checkRemove())
1428 PipingRules.requestUpdate(currentPrev);
1429 } catch (Exception e) {
1430 e.printStackTrace();
1436 * Removes control point without attempting to reconnect next/prev.
1437 * This usually leads to creation of another PipeRun for the control points after this point.
1439 public void removeAndSplit() {
1440 PipeControlPoint currentPrev = previous;
1441 PipeControlPoint currentNext = next;
1443 if (next != null && previous != null) {
1444 P3DRootNode root = (P3DRootNode)getPipelineComponent().getRootNode();
1445 PipeRun nextPipeRun = new PipeRun();
1446 nextPipeRun.setName(root.getUniqueName("PipeRun"));
1447 root.addChild(nextPipeRun);
1449 PipeRun previousRun = previous.getPipeRun();
1450 nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
1451 nextPipeRun.setTurnRadiusArray(previousRun.getTurnRadiusArray());
1453 PipelineComponent n = next.getPipelineComponent();
1455 if (n.getPipeRun() != previousRun)
1457 if (! (n instanceof Nozzle)) {
1459 nextPipeRun.addChild(n);
1461 n.setPipeRun(nextPipeRun);
1467 if (currentNext != null)
1468 if (!currentNext.checkRemove())
1469 PipingRules.requestUpdate(currentNext);
1470 if (currentPrev != null)
1471 if (!currentPrev.checkRemove())
1472 PipingRules.requestUpdate(currentPrev);
1473 } catch (Exception e) {
1474 e.printStackTrace();
1479 * This is called when adjacent control point is removed.
1481 * This call should remove the give point, if the point cannot exist alone.
1482 * At the moment there is one such case: branch.
1486 protected boolean checkRemove() {
1487 if (getParentPoint() != null) {
1488 return getParentPoint().checkRemove();
1490 if (getPipelineComponent() == null)
1491 return true; // already removed
1492 if (getPipelineComponent().getType().equals("Plant3D.URIs.Builtin_BranchSplitComponent")) {
1493 if (getChildPoints().get(0).getNext() == null && getChildPoints().get(0).getPrevious() == null) {
1498 return checkRemove(getPipeRun());
1502 private boolean checkRemove(PipeRun pipeRun) {
1503 if (pipeRun == null)
1505 Collection<PipeControlPoint> points = pipeRun.getControlPoints();
1506 if (points.size() == 0) {
1509 } else if (points.size() == 1) {
1510 PipeControlPoint pcp = points.iterator().next();
1511 if (pcp.isDeletable() && pcp.getNext() == null && pcp.getPrevious() == null) {
1512 pcp._remove(); // This call will recursively call also this method...
1515 } else if (points.size() == 2) {
1521 private void removeSubPoints() {
1522 for (PipeControlPoint p : children) {
1526 PipeControlPoint currentNext = p.getNext();
1527 PipeControlPoint currentPrev = p.getPrevious();
1529 p._setPrevious(null);
1530 PipeRun run = p.getPipeRun();
1535 if (currentNext != null)
1536 if (!currentNext.checkRemove())
1537 PipingRules.requestUpdate(currentNext);
1538 if (currentPrev != null)
1539 if (!currentPrev.checkRemove())
1540 PipingRules.requestUpdate(currentPrev);
1546 private void removeParentPoint() {
1547 throw new RuntimeException("Child points cannot be removed directly");
1550 public boolean isRemoved() {
1551 return component == null;
1554 private void removeComponent() {
1555 if (component == null)
1557 PipelineComponent next = component.getNext();
1558 PipelineComponent prev = component.getPrevious();
1559 PipelineComponent br0 = component.getBranch0();
1560 component.setNext(null);
1561 component.setPrevious(null);
1562 component.setBranch0(null);
1564 if (next.getNext() == component)
1566 else if (next.getPrevious() == component)
1567 next.setPrevious(null);
1568 else if (next.getBranch0() == component)
1569 next.setBranch0(null);
1572 if (prev.getNext() == component)
1574 else if (prev.getPrevious() == component)
1575 prev.setPrevious(null);
1576 else if (prev.getBranch0() == component)
1577 prev.setBranch0(null);
1580 if (br0.getNext() == component)
1582 else if (br0.getPrevious() == component)
1583 br0.setPrevious(null);
1584 else if (br0.getBranch0() == component)
1585 br0.setBranch0(null);
1587 PipelineComponent comp = component;
1594 public void setOrientation(Quat4d orientation) {
1595 if (MathTools.equals(orientation, getOrientation()))
1597 if (getPipelineComponent() != null && (getPipelineComponent() instanceof Nozzle))
1598 System.out.println();
1599 super.setOrientation(orientation);
1600 if (getParentPoint() == null && component != null)
1601 component._setWorldOrientation(getWorldOrientation());
1606 public void setPosition(Vector3d position) {
1607 if (MathTools.equals(position, getPosition()))
1609 if (Double.isNaN(position.x) || Double.isNaN(position.y) || Double.isNaN(position.z))
1610 throw new IllegalArgumentException("NaN is not supported");
1611 super.setPosition(position);
1612 if (getParentPoint() == null && component != null)
1613 component._setWorldPosition(getWorldPosition());
1617 private void updateSubPoint() {
1619 if (next == null && previous == null) {
1620 for (PipeControlPoint sub : getChildPoints()) {
1621 sub.setWorldPosition(getWorldPosition());
1622 sub.setWorldOrientation(getWorldOrientation());
1626 for (PipeControlPoint sub : getChildPoints()) {
1627 Vector3d wp = getWorldPosition();
1628 wp.add(getSizeChangeOffsetVector());
1629 sub.setWorldPosition(wp);
1630 sub.setWorldOrientation(getWorldOrientation());
1633 for (PipeControlPoint sub : getChildPoints()) {
1634 sub.setWorldPosition(getWorldPosition());
1635 sub.setWorldOrientation(getWorldOrientation());
1641 public void _setWorldPosition(Vector3d position) {
1642 Vector3d localPos = getLocalPosition(position);
1643 super.setPosition(localPos);
1647 public void _setWorldOrientation(Quat4d orientation) {
1648 Quat4d localOr = getLocalOrientation(orientation);
1649 super.setOrientation(localOr);
1654 public String toString() {
1655 return getClass().getName() + "@" + Integer.toHexString(hashCode());