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);
954 } else if (isEnd()) {
955 dir2 = dir1 = new Vector3d();
957 dir1 = getPathLegDirection(Direction.PREVIOUS);
958 dir2 = sub.getPathLegDirection(Direction.NEXT);
969 * Get both path leg directions, with (0,0,0) if no connection exists. The returned vectors are not normalized.
971 * @param v1 Set to the direction towards the previous control point on output
972 * @param v2 Set to the direction towards the next control point on output
974 public void getEndDirections(Tuple3d v1, Tuple3d v2) {
975 PipeControlPoint sub = isAxial() ? this : getDualSub();
977 Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
978 Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
991 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) {
994 Vector3d pos = getWorldPosition();
995 dir.set(getInlineDir());
997 dir.scale(length * 0.5);
1004 public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) {
1005 assert (isInline());
1007 Vector3d pos = getWorldPosition();
1009 dir.set(getInlineDir());
1011 dir.scale(length * 0.5);
1018 public Vector3d getInlineDir() {
1019 Vector3d dir = getPathLegDirection(Direction.NEXT);
1021 dir = getPathLegDirection(Direction.PREVIOUS);
1023 // Use reverse direction
1026 // Control point is not connected at all, use current orientation
1027 dir = new Vector3d(1,0,0);
1028 MathTools.rotate(getWorldOrientation(), dir, dir);
1034 public double getInlineLength() {
1035 if (type == PointType.TURN)
1037 else if (type == PointType.INLINE)
1038 return length * 0.5;
1043 * Return the position indicated by the argument. If the indicated direction is not connected, the
1044 * control point's wolrd position is returned instead.
1046 * @param type A selector for the position to be returned
1047 * @return The selected position
1049 public Vector3d getRealPosition(PositionType type) {
1050 Vector3d pos = getWorldPosition();
1053 double length = getInlineLength();
1056 dir = getInlineDir();
1058 dir = getPathLegDirection(Direction.NEXT);
1065 double length = getInlineLength();
1068 dir = getInlineDir();
1071 dir = getPathLegDirection(Direction.PREVIOUS);
1078 // IEntity portDir = pcp.getSingleRelatedObject(ProcessResource.plant3Dresource.HasDirection);
1079 // TODO : how we calculated needed space for a port; does it has an offset from control point's position or not?
1089 public void getInlineMovement(Tuple3d start, Tuple3d end) {
1090 // FIXME : check type of neighbor components and allow movement on top of variable length components,
1091 // find proper range for movement (pcp's position is not)
1092 PipeControlPoint p = previous.getPrevious();
1093 PipeControlPoint n = next.getNext();
1094 start.set(p.getWorldPosition());
1095 end.set(n.getWorldPosition());
1098 public PipeControlPoint findNextEnd() {
1099 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1100 return findNextEnd( t);
1103 public PipeControlPoint findPreviousEnd() {
1104 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1105 return findPreviousEnd(t);
1108 public PipeControlPoint findNextEnd(List<PipeControlPoint> nextList) {
1110 PipeControlPoint pcp = null;
1111 PipeControlPoint p = null;
1112 if (nextList.size() == 0)
1116 p = nextList.get(nextList.size() - 1);
1121 if (nextList.size() > 0)
1122 nextList.remove(nextList.size() - 1);
1123 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1127 if (pcp.isPathLegEnd()) {
1128 //if (DEBUG) System.out.println(" " + pcp.getResource());
1132 // if (DEBUG) System.out.print(" " + pcp.getResource());
1137 public PipeControlPoint findPreviousEnd(List<PipeControlPoint> prevList) {
1139 PipeControlPoint pcp = null;
1140 PipeControlPoint p = null;
1141 if (prevList.size() == 0)
1145 p = prevList.get(prevList.size() - 1);
1147 pcp = p.getPrevious();
1150 if (prevList.size() > 0)
1151 prevList.remove(prevList.size() - 1);
1152 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1155 if (pcp.isPathLegEnd()) {
1156 // if (DEBUG) System.out.println(" " + pcp.getResource());
1160 // if (DEBUG)System.out.print(" " + pcp.getResource());
1165 public void _remove() {
1170 public PipeControlPoint getDualSub() {
1172 return getChildPoints().get(0);
1174 throw new IllegalStateException("Current control point is not dual inline");
1178 public void _remove(boolean renconnect) {
1182 if (DEBUG) System.out.println(this + " Remove " + renconnect);
1184 if (getParentPoint() != null) {
1185 getParentPoint()._remove(renconnect);
1188 PipeRun pipeRun = getPipeRun();
1189 // PipeRUn removal has been changed, so pipeRun may be null.
1190 // if (pipeRun == null)
1193 PipeControlPoint additionalRemove = null;
1194 if (!PipingRules.isEnabled()) {
1200 PipeControlPoint currentPrev = previous;
1201 PipeControlPoint currentNext = next;
1202 if (currentNext == null && currentPrev == null) {
1204 if (pipeRun != null) {
1205 pipeRun.remChild(this);
1206 checkRemove(pipeRun);
1210 if (currentNext != null && currentPrev != null) {
1211 boolean link = renconnect;
1212 if (currentNext.isBranchEnd()) {
1214 currentNext.remove();
1218 if (currentPrev.isBranchEnd()) {
1220 currentPrev.remove();
1225 if (currentPrev.isDirected() && currentNext.isDirected())
1227 else if (this.isDualSub()) {
1228 throw new RuntimeException("_remove() is called for parent point, somehow got to child point. " + this);
1231 if (currentNext == null) {
1233 } else if (currentNext.isDualInline()) {
1234 PipeControlPoint sccp = currentNext;
1235 PipeControlPoint ocp = currentNext.getDualSub();
1237 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1240 sccp.setPrevious(currentPrev);
1241 //ocp.setPrevious(currentPrev);
1242 assert(ocp.getPrevious() == currentPrev);
1244 sccp.setPrevious(null);
1245 //ocp.setPrevious(null);
1246 assert(ocp.getPrevious() == null);
1249 } else if (currentNext.isDualSub()) {
1250 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1251 } else if (currentNext.previous == this) {
1253 currentNext.setPrevious(currentPrev);
1255 currentNext.setPrevious(null);
1258 } else if (isDualInline()) {
1259 if (currentNext.previous != getDualSub()) {
1260 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1263 currentNext.setPrevious(currentPrev);
1265 currentNext.setPrevious(null);
1269 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1271 if (currentPrev == null) {
1273 } else if (currentPrev.isDualInline()) {
1274 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1275 } else if (currentPrev.isDualSub()) {
1276 PipeControlPoint ocp = currentPrev;
1277 PipeControlPoint sccp = currentPrev.getParentPoint();
1279 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1281 //ocp.setNext(currentNext);
1282 sccp.setNext(currentNext);
1283 assert(ocp.getNext() == currentNext);
1285 //ocp.setNext(null);
1287 assert(ocp.getNext() == null);
1290 } else if (currentPrev.next == this) {
1292 currentPrev.setNext(currentNext);
1294 currentPrev.setNext(null);
1298 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged");
1301 if (currentNext.isVariableLength() && currentPrev.isVariableLength()) {
1302 // we have to join them into single variable length component.
1303 additionalRemove = currentPrev;
1304 // combine lengths and set the location of remaining control point to the center.
1305 Point3d ps = new Point3d();
1306 Point3d pe = new Point3d();
1307 Point3d ns = new Point3d();
1308 Point3d ne = new Point3d();
1309 currentPrev.getInlineControlPointEnds(ps, pe);
1310 currentNext.getInlineControlPointEnds(ns, ne);
1311 double l = currentPrev.getLength() + currentNext.getLength();
1312 Vector3d cp = new Vector3d();
1315 currentNext.setLength(l);
1316 currentNext.setWorldPosition(cp);
1319 // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous.
1321 } else if (currentNext != null) {
1322 if (currentNext.isDualInline()) {
1323 PipeControlPoint sccp = currentNext;
1324 PipeControlPoint ocp = currentNext.getDualSub();
1326 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1328 sccp.setPrevious(null);
1329 assert(ocp.getPrevious() == null);
1330 //ocp.setPrevious(null);
1331 } else if (currentNext.isDualSub()) {
1332 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1333 } else if (currentNext.previous == this) {
1334 currentNext.setPrevious(null);
1335 } else if (isDualInline()) {
1336 if (currentNext.previous != getDualSub()) {
1337 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1339 currentNext.setPrevious(null);
1341 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1344 } else { //(previous != null)
1345 if(currentPrev.isDualInline()) {
1346 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1347 } else if (currentPrev.isDualSub()) {
1348 PipeControlPoint ocp = currentPrev;
1349 PipeControlPoint sccp = currentPrev.getParentPoint();
1351 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1354 assert(ocp.getNext() == null);
1355 } else if (currentPrev.next == this) {
1356 currentPrev.setNext(null);
1358 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1362 if (children.size() > 0 ) {
1364 } else if (parent!= null) {
1365 removeParentPoint();
1371 if (pipeRun != null) {
1372 pipeRun.remChild(this);
1373 checkRemove(pipeRun);
1374 if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
1375 PipingRules.validate(pipeRun);
1377 if (additionalRemove != null)
1378 additionalRemove.remove();
1383 * Removes control point and attempts to reconnect next/prev
1385 * If this point is size change (PipeRuns are different on both sides), then reconnection cannot be made.
1387 public void remove() {
1388 PipeControlPoint currentPrev = previous;
1389 PipeControlPoint currentNext = next;
1392 if (currentNext != null)
1393 if (!currentNext.checkRemove())
1394 PipingRules.requestUpdate(currentNext);
1395 if (currentPrev != null)
1396 if (!currentPrev.checkRemove())
1397 PipingRules.requestUpdate(currentPrev);
1398 } catch (Exception e) {
1399 e.printStackTrace();
1405 * Removes control point without attempting to reconnect next/prev.
1406 * This usually leads to creation of another PipeRun for the control points after this point.
1408 public void removeAndSplit() {
1409 PipeControlPoint currentPrev = previous;
1410 PipeControlPoint currentNext = next;
1412 if (next != null && previous != null) {
1413 P3DRootNode root = (P3DRootNode)getPipelineComponent().getRootNode();
1414 PipeRun nextPipeRun = new PipeRun();
1415 nextPipeRun.setName(root.getUniqueName("PipeRun"));
1416 root.addChild(nextPipeRun);
1418 PipeRun previousRun = previous.getPipeRun();
1419 nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
1420 nextPipeRun.setPipeThickness(previousRun.getPipeThickness());
1421 nextPipeRun.setTurnRadiusArray(previousRun.getTurnRadiusArray());
1423 PipelineComponent n = next.getPipelineComponent();
1425 if (n.getPipeRun() != previousRun)
1427 if (! (n instanceof Nozzle)) {
1429 nextPipeRun.addChild(n);
1431 n.setPipeRun(nextPipeRun);
1437 if (currentNext != null)
1438 if (!currentNext.checkRemove())
1439 PipingRules.requestUpdate(currentNext);
1440 if (currentPrev != null)
1441 if (!currentPrev.checkRemove())
1442 PipingRules.requestUpdate(currentPrev);
1443 } catch (Exception e) {
1444 e.printStackTrace();
1449 * This is called when adjacent control point is removed.
1451 * This call should remove the give point, if the point cannot exist alone.
1452 * At the moment there is one such case: branch.
1456 protected boolean checkRemove() {
1457 if (getParentPoint() != null) {
1458 return getParentPoint().checkRemove();
1460 if (getPipelineComponent() == null)
1461 return true; // already removed
1462 if (getPipelineComponent().getType().equals("Plant3D.URIs.Builtin_BranchSplitComponent")) {
1463 if (getChildPoints().get(0).getNext() == null && getChildPoints().get(0).getPrevious() == null) {
1468 return checkRemove(getPipeRun());
1472 private boolean checkRemove(PipeRun pipeRun) {
1473 if (pipeRun == null)
1475 Collection<PipeControlPoint> points = pipeRun.getControlPoints();
1476 if (points.size() == 0) {
1479 } else if (points.size() == 1) {
1480 PipeControlPoint pcp = points.iterator().next();
1481 if (pcp.isDeletable() && pcp.getNext() == null && pcp.getPrevious() == null) {
1482 pcp._remove(); // This call will recursively call also this method...
1485 } else if (points.size() == 2) {
1491 private void removeSubPoints() {
1492 for (PipeControlPoint p : children) {
1496 PipeControlPoint currentNext = p.getNext();
1497 PipeControlPoint currentPrev = p.getPrevious();
1499 p._setPrevious(null);
1500 PipeRun run = p.getPipeRun();
1505 if (currentNext != null)
1506 if (!currentNext.checkRemove())
1507 PipingRules.requestUpdate(currentNext);
1508 if (currentPrev != null)
1509 if (!currentPrev.checkRemove())
1510 PipingRules.requestUpdate(currentPrev);
1516 private void removeParentPoint() {
1517 throw new RuntimeException("Child points cannot be removed directly");
1520 public boolean isRemoved() {
1521 return component == null;
1524 private void removeComponent() {
1525 if (component == null)
1527 PipelineComponent next = component.getNext();
1528 PipelineComponent prev = component.getPrevious();
1529 PipelineComponent br0 = component.getBranch0();
1530 component.setNext(null);
1531 component.setPrevious(null);
1532 component.setBranch0(null);
1534 if (next.getNext() == component)
1536 else if (next.getPrevious() == component)
1537 next.setPrevious(null);
1538 else if (next.getBranch0() == component)
1539 next.setBranch0(null);
1542 if (prev.getNext() == component)
1544 else if (prev.getPrevious() == component)
1545 prev.setPrevious(null);
1546 else if (prev.getBranch0() == component)
1547 prev.setBranch0(null);
1550 if (br0.getNext() == component)
1552 else if (br0.getPrevious() == component)
1553 br0.setPrevious(null);
1554 else if (br0.getBranch0() == component)
1555 br0.setBranch0(null);
1557 PipelineComponent comp = component;
1564 public void setOrientation(Quat4d orientation) {
1565 if (MathTools.equals(orientation, getOrientation()))
1567 if (getPipelineComponent() != null && (getPipelineComponent() instanceof Nozzle))
1568 System.out.println();
1569 super.setOrientation(orientation);
1570 if (getParentPoint() == null && component != null)
1571 component._setWorldOrientation(getWorldOrientation());
1576 public void setPosition(Vector3d position) {
1577 if (MathTools.equals(position, getPosition()))
1579 if (Double.isNaN(position.x) || Double.isNaN(position.y) || Double.isNaN(position.z))
1580 throw new IllegalArgumentException("NaN is not supported");
1581 super.setPosition(position);
1582 if (getParentPoint() == null && component != null)
1583 component._setWorldPosition(getWorldPosition());
1587 private void updateSubPoint() {
1589 if (next == null && previous == null) {
1590 for (PipeControlPoint sub : getChildPoints()) {
1591 sub.setWorldPosition(getWorldPosition());
1592 sub.setWorldOrientation(getWorldOrientation());
1596 for (PipeControlPoint sub : getChildPoints()) {
1597 Vector3d wp = getWorldPosition();
1598 wp.add(getSizeChangeOffsetVector());
1599 sub.setWorldPosition(wp);
1600 sub.setWorldOrientation(getWorldOrientation());
1603 for (PipeControlPoint sub : getChildPoints()) {
1604 sub.setWorldPosition(getWorldPosition());
1605 sub.setWorldOrientation(getWorldOrientation());
1611 public void _setWorldPosition(Vector3d position) {
1612 Vector3d localPos = getLocalPosition(position);
1613 super.setPosition(localPos);
1617 public void _setWorldOrientation(Quat4d orientation) {
1618 Quat4d localOr = getLocalOrientation(orientation);
1619 super.setOrientation(localOr);
1623 public void orientToDirection(Vector3d dir) {
1624 Double angleO = getRotationAngle();
1628 boolean reversed = _getReversed();
1631 q = getControlPointOrientationQuat(dir, angle, reversed);
1633 q = getControlPointOrientationQuat(angle, reversed);
1635 setWorldOrientation(q);
1639 public String toString() {
1640 return getClass().getName() + "@" + Integer.toHexString(hashCode());