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.
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) {
858 return getPathLegDirectionNext();
860 return getPathLegDirectionPrevious();
864 public Vector3d getPathLegDirectionPrevious() {
865 if (previous != null) {
866 PipeControlPoint pcp = this;
868 pcp = getParentPoint();
869 Vector3d v = new Vector3d();
870 v.sub(previous.getWorldPosition(),pcp.getWorldPosition());
871 if (v.lengthSquared() > MathTools.NEAR_ZERO)
876 } else if (isDirected()) {
877 Vector3d v = getDirectedControlPointDirection();
880 } else if (next == null) {
881 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
882 } else if (isVariableAngle() && !asFixedAngle()) {
883 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
884 } else if (isInline() || isEnd()) {
885 Vector3d v = getPathLegDirectionNext();
886 if (v != null) v.negate();
888 } else if (isTurn() && asFixedAngle() && _getReversed()) {
889 return getDirection(Direction.PREVIOUS);
891 throw new RuntimeException("Missing implementation " + this);
895 public Vector3d getPathLegDirectionNext() {
897 PipeControlPoint pcp = this;
898 if (pcp.isDualInline()) {
899 pcp = pcp.getDualSub();
901 Vector3d v = new Vector3d();
902 v.sub(next.getWorldPosition(),pcp.getWorldPosition());
903 if (v.lengthSquared() > MathTools.NEAR_ZERO)
908 } else if (isDirected()) {
909 return getDirectedControlPointDirection();
910 } else if (previous == null) {
911 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
912 } else if (isVariableAngle() && !asFixedAngle()) {
913 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
914 } else if (isInline() || isEnd()) {
915 Vector3d v = getPathLegDirectionPrevious();
916 if (v != null) v.negate();
918 } else if (isTurn() && asFixedAngle() && !_getReversed()) {
919 return getDirection(Direction.NEXT);
921 throw new RuntimeException("Missing implementation " + this);
925 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) {
928 PipeControlPoint sub = isAxial() ? this : getDualSub();
929 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
930 Vector3d dir = sub.getInlineDir();
932 dir.scale(length * 0.5);
939 public void getControlPointEnds(Tuple3d p1, Tuple3d p2) {
940 PipeControlPoint sub = isAxial() || isDirected() || isTurn() ? this : getChildPoints().get(0);
941 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
946 dir2 = getInlineDir();
947 dir2.scale(length * 0.5);
948 dir1 = new Vector3d(dir2);
951 dir1 = getPathLegDirection(Direction.PREVIOUS);
952 dir2 = sub.getPathLegDirection(Direction.NEXT);
963 * Get both path leg directions, with (0,0,0) if no connection exists. The returned vectors are not normalized.
965 * @param v1 Set to the direction towards the previous control point on output
966 * @param v2 Set to the direction towards the next control point on output
968 public void getEndDirections(Tuple3d v1, Tuple3d v2) {
969 PipeControlPoint sub = isAxial() ? this : getDualSub();
971 Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
972 Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
985 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) {
988 Vector3d pos = getWorldPosition();
989 dir.set(getInlineDir());
991 dir.scale(length * 0.5);
998 public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) {
1001 Vector3d pos = getWorldPosition();
1003 dir.set(getInlineDir());
1005 dir.scale(length * 0.5);
1012 public Vector3d getInlineDir() {
1013 Vector3d dir = getPathLegDirection(Direction.NEXT);
1015 dir = getPathLegDirection(Direction.PREVIOUS);
1017 // Use reverse direction
1020 // Control point is not connected at all, use current orientation
1021 dir = new Vector3d(1,0,0);
1022 MathTools.rotate(getWorldOrientation(), dir, dir);
1028 public double getInlineLength() {
1029 if (type == PointType.TURN)
1031 else if (type == PointType.INLINE)
1032 return length * 0.5;
1037 * Return the position indicated by the argument. If the indicated direction is not connected, the
1038 * control point's wolrd position is returned instead.
1040 * @param type A selector for the position to be returned
1041 * @return The selected position
1043 public Vector3d getRealPosition(PositionType type) {
1044 Vector3d pos = getWorldPosition();
1047 double length = getInlineLength();
1050 dir = getInlineDir();
1052 dir = getPathLegDirection(Direction.NEXT);
1059 double length = getInlineLength();
1062 dir = getInlineDir();
1065 dir = getPathLegDirection(Direction.PREVIOUS);
1072 // IEntity portDir = pcp.getSingleRelatedObject(ProcessResource.plant3Dresource.HasDirection);
1073 // TODO : how we calculated needed space for a port; does it has an offset from control point's position or not?
1083 public void getInlineMovement(Tuple3d start, Tuple3d end) {
1084 // FIXME : check type of neighbor components and allow movement on top of variable length components,
1085 // find proper range for movement (pcp's position is not)
1086 PipeControlPoint p = previous.getPrevious();
1087 PipeControlPoint n = next.getNext();
1088 start.set(p.getWorldPosition());
1089 end.set(n.getWorldPosition());
1092 public PipeControlPoint findNextEnd() {
1093 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1094 return findNextEnd( t);
1097 public PipeControlPoint findPreviousEnd() {
1098 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1099 return findPreviousEnd(t);
1102 public PipeControlPoint findNextEnd(List<PipeControlPoint> nextList) {
1104 PipeControlPoint pcp = null;
1105 PipeControlPoint p = null;
1106 if (nextList.size() == 0)
1110 p = nextList.get(nextList.size() - 1);
1115 if (nextList.size() > 0)
1116 nextList.remove(nextList.size() - 1);
1117 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1121 if (pcp.isPathLegEnd()) {
1122 //if (DEBUG) System.out.println(" " + pcp.getResource());
1126 // if (DEBUG) System.out.print(" " + pcp.getResource());
1131 public PipeControlPoint findPreviousEnd(List<PipeControlPoint> prevList) {
1133 PipeControlPoint pcp = null;
1134 PipeControlPoint p = null;
1135 if (prevList.size() == 0)
1139 p = prevList.get(prevList.size() - 1);
1141 pcp = p.getPrevious();
1144 if (prevList.size() > 0)
1145 prevList.remove(prevList.size() - 1);
1146 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1149 if (pcp.isPathLegEnd()) {
1150 // if (DEBUG) System.out.println(" " + pcp.getResource());
1154 // if (DEBUG)System.out.print(" " + pcp.getResource());
1159 public void _remove() {
1164 public PipeControlPoint getDualSub() {
1166 return getChildPoints().get(0);
1168 throw new IllegalStateException("Current control point is not dual inline");
1172 public void _remove(boolean renconnect) {
1176 if (DEBUG) System.out.println(this + " Remove " + renconnect);
1178 if (getParentPoint() != null) {
1179 getParentPoint()._remove(renconnect);
1182 PipeRun pipeRun = getPipeRun();
1183 // PipeRUn removal has been changed, so pipeRun may be null.
1184 // if (pipeRun == null)
1187 PipeControlPoint additionalRemove = null;
1188 if (!PipingRules.isEnabled()) {
1194 PipeControlPoint currentPrev = previous;
1195 PipeControlPoint currentNext = next;
1196 if (currentNext == null && currentPrev == null) {
1198 if (pipeRun != null) {
1199 pipeRun.remChild(this);
1200 checkRemove(pipeRun);
1204 if (currentNext != null && currentPrev != null) {
1205 boolean link = renconnect;
1206 if (currentNext.isBranchEnd()) {
1208 currentNext.remove();
1212 if (currentPrev.isBranchEnd()) {
1214 currentPrev.remove();
1219 if (currentPrev.isDirected() && currentNext.isDirected())
1221 else if (this.isDualSub()) {
1222 throw new RuntimeException("_remove() is called for parent point, somehow got to child point. " + this);
1225 if (currentNext == null) {
1227 } else if (currentNext.isDualInline()) {
1228 PipeControlPoint sccp = currentNext;
1229 PipeControlPoint ocp = currentNext.getDualSub();
1231 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1234 sccp.setPrevious(currentPrev);
1235 //ocp.setPrevious(currentPrev);
1236 assert(ocp.getPrevious() == currentPrev);
1238 sccp.setPrevious(null);
1239 //ocp.setPrevious(null);
1240 assert(ocp.getPrevious() == null);
1243 } else if (currentNext.isDualSub()) {
1244 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1245 } else if (currentNext.previous == this) {
1247 currentNext.setPrevious(currentPrev);
1249 currentNext.setPrevious(null);
1252 } else if (isDualInline()) {
1253 if (currentNext.previous != getDualSub()) {
1254 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1257 currentNext.setPrevious(currentPrev);
1259 currentNext.setPrevious(null);
1263 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1265 if (currentPrev == null) {
1267 } else if (currentPrev.isDualInline()) {
1268 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1269 } else if (currentPrev.isDualSub()) {
1270 PipeControlPoint ocp = currentPrev;
1271 PipeControlPoint sccp = currentPrev.getParentPoint();
1273 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1275 //ocp.setNext(currentNext);
1276 sccp.setNext(currentNext);
1277 assert(ocp.getNext() == currentNext);
1279 //ocp.setNext(null);
1281 assert(ocp.getNext() == null);
1284 } else if (currentPrev.next == this) {
1286 currentPrev.setNext(currentNext);
1288 currentPrev.setNext(null);
1292 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged");
1295 if (currentNext.isVariableLength() && currentPrev.isVariableLength()) {
1296 // we have to join them into single variable length component.
1297 additionalRemove = currentPrev;
1298 // combine lengths and set the location of remaining control point to the center.
1299 Point3d ps = new Point3d();
1300 Point3d pe = new Point3d();
1301 Point3d ns = new Point3d();
1302 Point3d ne = new Point3d();
1303 currentPrev.getInlineControlPointEnds(ps, pe);
1304 currentNext.getInlineControlPointEnds(ns, ne);
1305 double l = currentPrev.getLength() + currentNext.getLength();
1306 Vector3d cp = new Vector3d();
1309 currentNext.setLength(l);
1310 currentNext.setWorldPosition(cp);
1313 // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous.
1315 } else if (currentNext != null) {
1316 if (currentNext.isDualInline()) {
1317 PipeControlPoint sccp = currentNext;
1318 PipeControlPoint ocp = currentNext.getDualSub();
1320 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1322 sccp.setPrevious(null);
1323 assert(ocp.getPrevious() == null);
1324 //ocp.setPrevious(null);
1325 } else if (currentNext.isDualSub()) {
1326 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1327 } else if (currentNext.previous == this) {
1328 currentNext.setPrevious(null);
1329 } else if (isDualInline()) {
1330 if (currentNext.previous != getDualSub()) {
1331 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1333 currentNext.setPrevious(null);
1335 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1338 } else { //(previous != null)
1339 if(currentPrev.isDualInline()) {
1340 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1341 } else if (currentPrev.isDualSub()) {
1342 PipeControlPoint ocp = currentPrev;
1343 PipeControlPoint sccp = currentPrev.getParentPoint();
1345 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1348 assert(ocp.getNext() == null);
1349 } else if (currentPrev.next == this) {
1350 currentPrev.setNext(null);
1352 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1356 if (children.size() > 0 ) {
1358 } else if (parent!= null) {
1359 removeParentPoint();
1365 if (pipeRun != null) {
1366 pipeRun.remChild(this);
1367 checkRemove(pipeRun);
1368 if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
1369 PipingRules.validate(pipeRun);
1371 if (additionalRemove != null)
1372 additionalRemove.remove();
1377 * Removes control point and attempts to reconnect next/prev
1379 * If this point is size change (PipeRuns are different on both sides), then reconnection cannot be made.
1381 public void remove() {
1382 PipeControlPoint currentPrev = previous;
1383 PipeControlPoint currentNext = next;
1386 if (currentNext != null)
1387 if (!currentNext.checkRemove())
1388 PipingRules.requestUpdate(currentNext);
1389 if (currentPrev != null)
1390 if (!currentPrev.checkRemove())
1391 PipingRules.requestUpdate(currentPrev);
1392 } catch (Exception e) {
1393 e.printStackTrace();
1399 * Removes control point without attempting to reconnect next/prev.
1400 * This usually leads to creation of another PipeRun for the control points after this point.
1402 public void removeAndSplit() {
1403 PipeControlPoint currentPrev = previous;
1404 PipeControlPoint currentNext = next;
1406 if (next != null && previous != null) {
1407 P3DRootNode root = (P3DRootNode)getPipelineComponent().getRootNode();
1408 PipeRun nextPipeRun = new PipeRun();
1409 nextPipeRun.setName(root.getUniqueName("PipeRun"));
1410 root.addChild(nextPipeRun);
1412 PipeRun previousRun = previous.getPipeRun();
1413 nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
1414 nextPipeRun.setPipeThickness(previousRun.getPipeThickness());
1415 nextPipeRun.setTurnRadiusArray(previousRun.getTurnRadiusArray());
1417 PipelineComponent n = next.getPipelineComponent();
1419 if (n.getPipeRun() != previousRun)
1421 if (! (n instanceof Nozzle)) {
1423 nextPipeRun.addChild(n);
1425 n.setPipeRun(nextPipeRun);
1431 if (currentNext != null)
1432 if (!currentNext.checkRemove())
1433 PipingRules.requestUpdate(currentNext);
1434 if (currentPrev != null)
1435 if (!currentPrev.checkRemove())
1436 PipingRules.requestUpdate(currentPrev);
1437 } catch (Exception e) {
1438 e.printStackTrace();
1443 * This is called when adjacent control point is removed.
1445 * This call should remove the give point, if the point cannot exist alone.
1446 * At the moment there is one such case: branch.
1450 protected boolean checkRemove() {
1451 if (getParentPoint() != null) {
1452 return getParentPoint().checkRemove();
1454 if (getPipelineComponent() == null)
1455 return true; // already removed
1456 if (getPipelineComponent().getType().equals("Plant3D.URIs.Builtin_BranchSplitComponent")) {
1457 if (getChildPoints().get(0).getNext() == null && getChildPoints().get(0).getPrevious() == null) {
1462 return checkRemove(getPipeRun());
1466 private boolean checkRemove(PipeRun pipeRun) {
1467 if (pipeRun == null)
1469 Collection<PipeControlPoint> points = pipeRun.getControlPoints();
1470 if (points.size() == 0) {
1473 } else if (points.size() == 1) {
1474 PipeControlPoint pcp = points.iterator().next();
1475 if (pcp.isDeletable() && pcp.getNext() == null && pcp.getPrevious() == null) {
1476 pcp._remove(); // This call will recursively call also this method...
1479 } else if (points.size() == 2) {
1485 private void removeSubPoints() {
1486 for (PipeControlPoint p : children) {
1490 PipeControlPoint currentNext = p.getNext();
1491 PipeControlPoint currentPrev = p.getPrevious();
1493 p._setPrevious(null);
1494 PipeRun run = p.getPipeRun();
1499 if (currentNext != null)
1500 if (!currentNext.checkRemove())
1501 PipingRules.requestUpdate(currentNext);
1502 if (currentPrev != null)
1503 if (!currentPrev.checkRemove())
1504 PipingRules.requestUpdate(currentPrev);
1510 private void removeParentPoint() {
1511 throw new RuntimeException("Child points cannot be removed directly");
1514 public boolean isRemoved() {
1515 return component == null;
1518 private void removeComponent() {
1519 if (component == null)
1521 PipelineComponent next = component.getNext();
1522 PipelineComponent prev = component.getPrevious();
1523 PipelineComponent br0 = component.getBranch0();
1524 component.setNext(null);
1525 component.setPrevious(null);
1526 component.setBranch0(null);
1528 if (next.getNext() == component)
1530 else if (next.getPrevious() == component)
1531 next.setPrevious(null);
1532 else if (next.getBranch0() == component)
1533 next.setBranch0(null);
1536 if (prev.getNext() == component)
1538 else if (prev.getPrevious() == component)
1539 prev.setPrevious(null);
1540 else if (prev.getBranch0() == component)
1541 prev.setBranch0(null);
1544 if (br0.getNext() == component)
1546 else if (br0.getPrevious() == component)
1547 br0.setPrevious(null);
1548 else if (br0.getBranch0() == component)
1549 br0.setBranch0(null);
1551 PipelineComponent comp = component;
1558 public void setOrientation(Quat4d orientation) {
1559 if (MathTools.equals(orientation, getOrientation()))
1561 if (getPipelineComponent() != null && (getPipelineComponent() instanceof Nozzle))
1562 System.out.println();
1563 super.setOrientation(orientation);
1564 if (getParentPoint() == null && component != null)
1565 component._setWorldOrientation(getWorldOrientation());
1570 public void setPosition(Vector3d position) {
1571 if (MathTools.equals(position, getPosition()))
1573 if (Double.isNaN(position.x) || Double.isNaN(position.y) || Double.isNaN(position.z))
1574 throw new IllegalArgumentException("NaN is not supported");
1575 super.setPosition(position);
1576 if (getParentPoint() == null && component != null)
1577 component._setWorldPosition(getWorldPosition());
1581 private void updateSubPoint() {
1583 if (next == null && previous == null) {
1584 for (PipeControlPoint sub : getChildPoints()) {
1585 sub.setWorldPosition(getWorldPosition());
1586 sub.setWorldOrientation(getWorldOrientation());
1590 for (PipeControlPoint sub : getChildPoints()) {
1591 Vector3d wp = getWorldPosition();
1592 wp.add(getSizeChangeOffsetVector());
1593 sub.setWorldPosition(wp);
1594 sub.setWorldOrientation(getWorldOrientation());
1597 for (PipeControlPoint sub : getChildPoints()) {
1598 sub.setWorldPosition(getWorldPosition());
1599 sub.setWorldOrientation(getWorldOrientation());
1605 public void _setWorldPosition(Vector3d position) {
1606 Vector3d localPos = getLocalPosition(position);
1607 super.setPosition(localPos);
1611 public void _setWorldOrientation(Quat4d orientation) {
1612 Quat4d localOr = getLocalOrientation(orientation);
1613 super.setOrientation(localOr);
1617 public void orientToDirection(Vector3d dir) {
1618 Double angleO = getRotationAngle();
1622 boolean reversed = _getReversed();
1625 q = getControlPointOrientationQuat(dir, angle, reversed);
1627 q = getControlPointOrientationQuat(angle, reversed);
1629 setWorldOrientation(q);
1633 public String toString() {
1634 return getClass().getName() + "@" + Integer.toHexString(hashCode());