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);
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 pointing towards an adjacent component for
795 * directed control points or turn control points with one open end.
797 * Always returns an outwards pointing vector.
799 * For any other type of component, the return value is null.
801 * For turn components this only return a non-null value for the unconnected
802 * end of the component.
805 * @return normalized vector, or null
807 public Vector3d getDirection(Direction direction) {
809 return getDirectedControlPointDirection();
810 if (isTurn() && asFixedAngle()) {
811 if (direction == Direction.NEXT) {
812 if (previous != null) {
813 PipeControlPoint pcp = this;
814 Vector3d dir = new Vector3d();
815 dir.sub(pcp.getWorldPosition(),previous.getWorldPosition());
816 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
820 Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
821 AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
822 Quat4d q2 = MathTools.getQuat(aa);
823 Vector3d v = new Vector3d(1.,0.,0.);
824 Vector3d offset = new Vector3d();
825 MathTools.rotate(q2, v, offset);
826 MathTools.rotate(q, offset, dir);
832 PipeControlPoint pcp = this;
833 Vector3d dir = new Vector3d();
834 dir.sub(next.getWorldPosition(),pcp.getWorldPosition());
835 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
839 Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
840 AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
841 Quat4d q2 = MathTools.getQuat(aa);
842 Vector3d v = new Vector3d(1.,0.,0.);
843 Vector3d offset = new Vector3d();
844 MathTools.rotate(q2, v, offset);
845 MathTools.rotate(q, offset, dir);
856 * Returns path leg direction of the control point.
858 * This method differs from getDirection by also returning inward pointing vectors for directed control points.
863 public Vector3d getPathLegDirection(Direction direction) {
864 if (direction == Direction.NEXT) {
865 return getPathLegDirectionNext();
867 return getPathLegDirectionPrevious();
871 public Vector3d getPathLegDirectionPrevious() {
872 if (previous != null) {
873 PipeControlPoint pcp = this;
875 pcp = getParentPoint();
876 Vector3d v = new Vector3d();
877 v.sub(previous.getWorldPosition(),pcp.getWorldPosition());
878 if (v.lengthSquared() > MathTools.NEAR_ZERO)
883 } else if (isDirected()) {
884 Vector3d v = getDirectedControlPointDirection();
887 } else if (next == null) {
888 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
889 } else if (isVariableAngle() && !asFixedAngle()) {
890 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
891 } else if (isInline() || isEnd()) {
892 Vector3d v = getPathLegDirectionNext();
893 if (v != null) v.negate();
895 } else if (isTurn() && asFixedAngle() && _getReversed()) {
896 return getDirection(Direction.PREVIOUS);
898 throw new RuntimeException("Missing implementation " + this);
902 public Vector3d getPathLegDirectionNext() {
904 PipeControlPoint pcp = this;
905 if (pcp.isDualInline()) {
906 pcp = pcp.getDualSub();
908 Vector3d v = new Vector3d();
909 v.sub(next.getWorldPosition(),pcp.getWorldPosition());
910 if (v.lengthSquared() > MathTools.NEAR_ZERO)
915 } else if (isDirected()) {
916 return getDirectedControlPointDirection();
917 } else if (previous == null) {
918 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
919 } else if (isVariableAngle() && !asFixedAngle()) {
920 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
921 } else if (isInline() || isEnd()) {
922 Vector3d v = getPathLegDirectionPrevious();
923 if (v != null) v.negate();
925 } else if (isTurn() && asFixedAngle() && !_getReversed()) {
926 return getDirection(Direction.NEXT);
928 throw new RuntimeException("Missing implementation " + this);
932 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) {
935 PipeControlPoint sub = isAxial() ? this : getDualSub();
936 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
937 Vector3d dir = sub.getInlineDir();
939 dir.scale(length * 0.5);
946 public void getControlPointEnds(Tuple3d p1, Tuple3d p2) {
947 PipeControlPoint sub = isAxial() || isDirected() || isTurn() ? this : getChildPoints().get(0);
948 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
953 dir2 = getInlineDir();
954 dir2.scale(length * 0.5);
955 dir1 = new Vector3d(dir2);
958 dir1 = getPathLegDirection(Direction.PREVIOUS);
959 dir2 = sub.getPathLegDirection(Direction.NEXT);
970 * Get both path leg directions, with (0,0,0) if no connection exists. The returned vectors are not normalized.
972 * @param v1 Set to the direction towards the previous control point on output
973 * @param v2 Set to the direction towards the next control point on output
975 public void getEndDirections(Tuple3d v1, Tuple3d v2) {
976 PipeControlPoint sub = isAxial() ? this : getDualSub();
978 Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
979 Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
992 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) {
995 Vector3d pos = getWorldPosition();
996 dir.set(getInlineDir());
998 dir.scale(length * 0.5);
1005 public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) {
1006 assert (isInline());
1008 Vector3d pos = getWorldPosition();
1010 dir.set(getInlineDir());
1012 dir.scale(length * 0.5);
1019 public Vector3d getInlineDir() {
1020 Vector3d dir = getPathLegDirection(Direction.NEXT);
1022 dir = getPathLegDirection(Direction.PREVIOUS);
1024 // Use reverse direction
1027 // Control point is not connected at all, use current orientation
1028 dir = new Vector3d(1,0,0);
1029 MathTools.rotate(getWorldOrientation(), dir, dir);
1035 public double getInlineLength() {
1036 if (type == PointType.TURN)
1038 else if (type == PointType.INLINE)
1039 return length * 0.5;
1044 * Return the position indicated by the argument. If the indicated direction is not connected, the
1045 * control point's wolrd position is returned instead.
1047 * @param type A selector for the position to be returned
1048 * @return The selected position
1050 public Vector3d getRealPosition(PositionType type) {
1051 Vector3d pos = getWorldPosition();
1054 double length = getInlineLength();
1057 dir = getInlineDir();
1059 dir = getPathLegDirection(Direction.NEXT);
1066 double length = getInlineLength();
1069 dir = getInlineDir();
1072 dir = getPathLegDirection(Direction.PREVIOUS);
1079 // IEntity portDir = pcp.getSingleRelatedObject(ProcessResource.plant3Dresource.HasDirection);
1080 // TODO : how we calculated needed space for a port; does it has an offset from control point's position or not?
1090 public void getInlineMovement(Tuple3d start, Tuple3d end) {
1091 // FIXME : check type of neighbor components and allow movement on top of variable length components,
1092 // find proper range for movement (pcp's position is not)
1093 PipeControlPoint p = previous.getPrevious();
1094 PipeControlPoint n = next.getNext();
1095 start.set(p.getWorldPosition());
1096 end.set(n.getWorldPosition());
1099 public PipeControlPoint findNextEnd() {
1100 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1101 return findNextEnd( t);
1104 public PipeControlPoint findPreviousEnd() {
1105 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1106 return findPreviousEnd(t);
1109 public PipeControlPoint findNextEnd(List<PipeControlPoint> nextList) {
1111 PipeControlPoint pcp = null;
1112 PipeControlPoint p = null;
1113 if (nextList.size() == 0)
1117 p = nextList.get(nextList.size() - 1);
1122 if (nextList.size() > 0)
1123 nextList.remove(nextList.size() - 1);
1124 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1128 if (pcp.isPathLegEnd()) {
1129 //if (DEBUG) System.out.println(" " + pcp.getResource());
1133 // if (DEBUG) System.out.print(" " + pcp.getResource());
1138 public PipeControlPoint findPreviousEnd(List<PipeControlPoint> prevList) {
1140 PipeControlPoint pcp = null;
1141 PipeControlPoint p = null;
1142 if (prevList.size() == 0)
1146 p = prevList.get(prevList.size() - 1);
1148 pcp = p.getPrevious();
1151 if (prevList.size() > 0)
1152 prevList.remove(prevList.size() - 1);
1153 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1156 if (pcp.isPathLegEnd()) {
1157 // if (DEBUG) System.out.println(" " + pcp.getResource());
1161 // if (DEBUG)System.out.print(" " + pcp.getResource());
1166 public void _remove() {
1171 public PipeControlPoint getDualSub() {
1173 return getChildPoints().get(0);
1175 throw new IllegalStateException("Current control point is not dual inline");
1179 public void _remove(boolean renconnect) {
1183 if (DEBUG) System.out.println(this + " Remove " + renconnect);
1185 if (getParentPoint() != null) {
1186 getParentPoint()._remove(renconnect);
1189 PipeRun pipeRun = getPipeRun();
1190 // PipeRUn removal has been changed, so pipeRun may be null.
1191 // if (pipeRun == null)
1194 PipeControlPoint additionalRemove = null;
1195 if (!PipingRules.isEnabled()) {
1201 PipeControlPoint currentPrev = previous;
1202 PipeControlPoint currentNext = next;
1203 if (currentNext == null && currentPrev == null) {
1205 if (pipeRun != null) {
1206 pipeRun.remChild(this);
1207 checkRemove(pipeRun);
1211 if (currentNext != null && currentPrev != null) {
1212 boolean link = renconnect;
1213 if (currentNext.isBranchEnd()) {
1215 currentNext.remove();
1219 if (currentPrev.isBranchEnd()) {
1221 currentPrev.remove();
1226 if (currentPrev.isDirected() && currentNext.isDirected())
1228 else if (this.isDualSub()) {
1229 throw new RuntimeException("_remove() is called for parent point, somehow got to child point. " + this);
1232 if (currentNext == null) {
1234 } else if (currentNext.isDualInline()) {
1235 PipeControlPoint sccp = currentNext;
1236 PipeControlPoint ocp = currentNext.getDualSub();
1238 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1241 sccp.setPrevious(currentPrev);
1242 //ocp.setPrevious(currentPrev);
1243 assert(ocp.getPrevious() == currentPrev);
1245 sccp.setPrevious(null);
1246 //ocp.setPrevious(null);
1247 assert(ocp.getPrevious() == null);
1250 } else if (currentNext.isDualSub()) {
1251 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1252 } else if (currentNext.previous == this) {
1254 currentNext.setPrevious(currentPrev);
1256 currentNext.setPrevious(null);
1259 } else if (isDualInline()) {
1260 if (currentNext.previous != getDualSub()) {
1261 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1264 currentNext.setPrevious(currentPrev);
1266 currentNext.setPrevious(null);
1270 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1272 if (currentPrev == null) {
1274 } else if (currentPrev.isDualInline()) {
1275 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1276 } else if (currentPrev.isDualSub()) {
1277 PipeControlPoint ocp = currentPrev;
1278 PipeControlPoint sccp = currentPrev.getParentPoint();
1280 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1282 //ocp.setNext(currentNext);
1283 sccp.setNext(currentNext);
1284 assert(ocp.getNext() == currentNext);
1286 //ocp.setNext(null);
1288 assert(ocp.getNext() == null);
1291 } else if (currentPrev.next == this) {
1293 currentPrev.setNext(currentNext);
1295 currentPrev.setNext(null);
1299 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged");
1302 if (currentNext.isVariableLength() && currentPrev.isVariableLength()) {
1303 // we have to join them into single variable length component.
1304 additionalRemove = currentPrev;
1305 // combine lengths and set the location of remaining control point to the center.
1306 Point3d ps = new Point3d();
1307 Point3d pe = new Point3d();
1308 Point3d ns = new Point3d();
1309 Point3d ne = new Point3d();
1310 currentPrev.getInlineControlPointEnds(ps, pe);
1311 currentNext.getInlineControlPointEnds(ns, ne);
1312 double l = currentPrev.getLength() + currentNext.getLength();
1313 Vector3d cp = new Vector3d();
1316 currentNext.setLength(l);
1317 currentNext.setWorldPosition(cp);
1320 // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous.
1322 } else if (currentNext != null) {
1323 if (currentNext.isDualInline()) {
1324 PipeControlPoint sccp = currentNext;
1325 PipeControlPoint ocp = currentNext.getDualSub();
1327 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1329 sccp.setPrevious(null);
1330 assert(ocp.getPrevious() == null);
1331 //ocp.setPrevious(null);
1332 } else if (currentNext.isDualSub()) {
1333 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1334 } else if (currentNext.previous == this) {
1335 currentNext.setPrevious(null);
1336 } else if (isDualInline()) {
1337 if (currentNext.previous != getDualSub()) {
1338 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1340 currentNext.setPrevious(null);
1342 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1345 } else { //(previous != null)
1346 if(currentPrev.isDualInline()) {
1347 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1348 } else if (currentPrev.isDualSub()) {
1349 PipeControlPoint ocp = currentPrev;
1350 PipeControlPoint sccp = currentPrev.getParentPoint();
1352 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1355 assert(ocp.getNext() == null);
1356 } else if (currentPrev.next == this) {
1357 currentPrev.setNext(null);
1359 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1363 if (children.size() > 0 ) {
1365 } else if (parent!= null) {
1366 removeParentPoint();
1372 if (pipeRun != null) {
1373 pipeRun.remChild(this);
1374 checkRemove(pipeRun);
1375 if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
1376 PipingRules.validate(pipeRun);
1378 if (additionalRemove != null)
1379 additionalRemove.remove();
1384 * Removes control point and attempts to reconnect next/prev
1386 * If this point is size change (PipeRuns are different on both sides), then reconnection cannot be made.
1388 public void remove() {
1389 PipeControlPoint currentPrev = previous;
1390 PipeControlPoint currentNext = next;
1393 if (currentNext != null)
1394 if (!currentNext.checkRemove())
1395 PipingRules.requestUpdate(currentNext);
1396 if (currentPrev != null)
1397 if (!currentPrev.checkRemove())
1398 PipingRules.requestUpdate(currentPrev);
1399 } catch (Exception e) {
1400 e.printStackTrace();
1406 * Removes control point without attempting to reconnect next/prev.
1407 * This usually leads to creation of another PipeRun for the control points after this point.
1409 public void removeAndSplit() {
1410 PipeControlPoint currentPrev = previous;
1411 PipeControlPoint currentNext = next;
1413 if (next != null && previous != null) {
1414 P3DRootNode root = (P3DRootNode)getPipelineComponent().getRootNode();
1415 PipeRun nextPipeRun = new PipeRun();
1416 nextPipeRun.setName(root.getUniqueName("PipeRun"));
1417 root.addChild(nextPipeRun);
1419 PipeRun previousRun = previous.getPipeRun();
1420 nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
1421 nextPipeRun.setPipeThickness(previousRun.getPipeThickness());
1422 nextPipeRun.setTurnRadiusArray(previousRun.getTurnRadiusArray());
1424 PipelineComponent n = next.getPipelineComponent();
1426 if (n.getPipeRun() != previousRun)
1428 if (! (n instanceof Nozzle)) {
1430 nextPipeRun.addChild(n);
1432 n.setPipeRun(nextPipeRun);
1438 if (currentNext != null)
1439 if (!currentNext.checkRemove())
1440 PipingRules.requestUpdate(currentNext);
1441 if (currentPrev != null)
1442 if (!currentPrev.checkRemove())
1443 PipingRules.requestUpdate(currentPrev);
1444 } catch (Exception e) {
1445 e.printStackTrace();
1450 * This is called when adjacent control point is removed.
1452 * This call should remove the give point, if the point cannot exist alone.
1453 * At the moment there is one such case: branch.
1457 protected boolean checkRemove() {
1458 if (getParentPoint() != null) {
1459 return getParentPoint().checkRemove();
1461 if (getPipelineComponent() == null)
1462 return true; // already removed
1463 if (getPipelineComponent().getType().equals("Plant3D.URIs.Builtin_BranchSplitComponent")) {
1464 if (getChildPoints().get(0).getNext() == null && getChildPoints().get(0).getPrevious() == null) {
1469 return checkRemove(getPipeRun());
1473 private boolean checkRemove(PipeRun pipeRun) {
1474 if (pipeRun == null)
1476 Collection<PipeControlPoint> points = pipeRun.getControlPoints();
1477 if (points.size() == 0) {
1480 } else if (points.size() == 1) {
1481 PipeControlPoint pcp = points.iterator().next();
1482 if (pcp.isDeletable() && pcp.getNext() == null && pcp.getPrevious() == null) {
1483 pcp._remove(); // This call will recursively call also this method...
1486 } else if (points.size() == 2) {
1492 private void removeSubPoints() {
1493 for (PipeControlPoint p : children) {
1497 PipeControlPoint currentNext = p.getNext();
1498 PipeControlPoint currentPrev = p.getPrevious();
1500 p._setPrevious(null);
1501 PipeRun run = p.getPipeRun();
1506 if (currentNext != null)
1507 if (!currentNext.checkRemove())
1508 PipingRules.requestUpdate(currentNext);
1509 if (currentPrev != null)
1510 if (!currentPrev.checkRemove())
1511 PipingRules.requestUpdate(currentPrev);
1517 private void removeParentPoint() {
1518 throw new RuntimeException("Child points cannot be removed directly");
1521 public boolean isRemoved() {
1522 return component == null;
1525 private void removeComponent() {
1526 if (component == null)
1528 PipelineComponent next = component.getNext();
1529 PipelineComponent prev = component.getPrevious();
1530 PipelineComponent br0 = component.getBranch0();
1531 component.setNext(null);
1532 component.setPrevious(null);
1533 component.setBranch0(null);
1535 if (next.getNext() == component)
1537 else if (next.getPrevious() == component)
1538 next.setPrevious(null);
1539 else if (next.getBranch0() == component)
1540 next.setBranch0(null);
1543 if (prev.getNext() == component)
1545 else if (prev.getPrevious() == component)
1546 prev.setPrevious(null);
1547 else if (prev.getBranch0() == component)
1548 prev.setBranch0(null);
1551 if (br0.getNext() == component)
1553 else if (br0.getPrevious() == component)
1554 br0.setPrevious(null);
1555 else if (br0.getBranch0() == component)
1556 br0.setBranch0(null);
1558 PipelineComponent comp = component;
1565 public void setOrientation(Quat4d orientation) {
1566 if (MathTools.equals(orientation, getOrientation()))
1568 if (getPipelineComponent() != null && (getPipelineComponent() instanceof Nozzle))
1569 System.out.println();
1570 super.setOrientation(orientation);
1571 if (getParentPoint() == null && component != null)
1572 component._setWorldOrientation(getWorldOrientation());
1577 public void setPosition(Vector3d position) {
1578 if (MathTools.equals(position, getPosition()))
1580 if (Double.isNaN(position.x) || Double.isNaN(position.y) || Double.isNaN(position.z))
1581 throw new IllegalArgumentException("NaN is not supported");
1582 super.setPosition(position);
1583 if (getParentPoint() == null && component != null)
1584 component._setWorldPosition(getWorldPosition());
1588 private void updateSubPoint() {
1590 if (next == null && previous == null) {
1591 for (PipeControlPoint sub : getChildPoints()) {
1592 sub.setWorldPosition(getWorldPosition());
1593 sub.setWorldOrientation(getWorldOrientation());
1597 for (PipeControlPoint sub : getChildPoints()) {
1598 Vector3d wp = getWorldPosition();
1599 wp.add(getSizeChangeOffsetVector());
1600 sub.setWorldPosition(wp);
1601 sub.setWorldOrientation(getWorldOrientation());
1604 for (PipeControlPoint sub : getChildPoints()) {
1605 sub.setWorldPosition(getWorldPosition());
1606 sub.setWorldOrientation(getWorldOrientation());
1612 public void _setWorldPosition(Vector3d position) {
1613 Vector3d localPos = getLocalPosition(position);
1614 super.setPosition(localPos);
1618 public void _setWorldOrientation(Quat4d orientation) {
1619 Quat4d localOr = getLocalOrientation(orientation);
1620 super.setOrientation(localOr);
1624 public void orientToDirection(Vector3d dir) {
1625 Double angleO = getRotationAngle();
1629 boolean reversed = _getReversed();
1632 q = getControlPointOrientationQuat(dir, angle, reversed);
1634 q = getControlPointOrientationQuat(angle, reversed);
1636 setWorldOrientation(q);
1640 public String toString() {
1641 return getClass().getName() + "@" + Integer.toHexString(hashCode());