1 package org.simantics.plant3d.scenegraph.controlpoint;
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
8 import javax.vecmath.AxisAngle4d;
9 import javax.vecmath.Matrix3d;
10 import javax.vecmath.Point3d;
11 import javax.vecmath.Quat4d;
12 import javax.vecmath.Tuple3d;
13 import javax.vecmath.Vector3d;
15 import org.simantics.g3d.math.MathTools;
16 import org.simantics.g3d.property.annotations.GetPropertyValue;
17 import org.simantics.g3d.scenegraph.G3DNode;
18 import org.simantics.g3d.scenegraph.base.INode;
19 import org.simantics.plant3d.scenegraph.IP3DNode;
20 import org.simantics.plant3d.scenegraph.Nozzle;
21 import org.simantics.plant3d.scenegraph.P3DRootNode;
22 import org.simantics.plant3d.scenegraph.PipeRun;
23 import org.simantics.plant3d.scenegraph.PipelineComponent;
25 import vtk.vtkRenderer;
28 public class PipeControlPoint extends G3DNode implements IP3DNode {
30 private static boolean DEBUG = false;
32 public enum PointType{INLINE,TURN,END};
33 public enum Direction{NEXT,PREVIOUS};
34 public enum PositionType {SPLIT,NEXT,PREVIOUS,PORT}
36 private PipelineComponent component;
38 private PointType type;
39 private boolean isFixed = true; // In-line: fixed-length Turn: fixed-angle
40 private boolean isMod = false; // Can user modify fixed value manually
41 private boolean isRotate = false; // rotates around path leg axis.
42 private boolean isReverse = false; // definition direction can be swapped
43 private boolean isDeletable = true; // can be removed by rules
44 private boolean isSizeChange = false; // changes size of the pipe. The next control point / component is on different PipeRun
45 private boolean isSub = false; // child point for offset / size change
47 private boolean disposed = false;
49 public PipeControlPoint(PipelineComponent component) {
50 this.component = component;
51 if (component.getPipeRun() != null)
52 component.getPipeRun().addChild(this);
56 public PipeControlPoint(PipelineComponent component, PipeRun piperun) {
57 this.component = component;
58 piperun.addChild(this);
62 public void update(vtkRenderer ren) {
64 PipingRules.requestUpdate(this);
65 } catch (Exception e) {
71 public PipeRun getPipeRun() {
72 return (PipeRun)getParent();
75 public PipelineComponent getPipelineComponent() {
79 public PointType getType() {
83 public void setType(PointType type) {
87 @GetPropertyValue(name="Fixed",tabId="Debug",value="fixed")
88 public boolean isFixed() {
92 public void setFixed(boolean fixed) {
96 @GetPropertyValue(name="Mod",tabId="Debug",value="mod")
97 public boolean isMod() {
101 public void setMod(boolean isMod) {
105 @GetPropertyValue(name="Rotate",tabId="Debug",value="rotate")
106 public boolean isRotate() {
110 public void setRotate(boolean rotate) {
111 this.isRotate = rotate;
114 @GetPropertyValue(name="Reverse",tabId="Debug",value="reverse")
115 public boolean isReverse() {
119 public void setReverse(boolean reverse) {
120 this.isReverse = reverse;
123 public void setSub(boolean sub) {
127 @GetPropertyValue(name="Deletable",tabId="Debug",value="deletable")
128 public boolean isDeletable() {
132 public void setDeletable(boolean deletable) {
133 this.isDeletable = deletable;
136 public boolean isPathLegEnd() {
137 return type != PointType.INLINE;
140 public boolean isEnd() {
141 return type == PointType.END;
144 public boolean isTurn() {
145 return type == PointType.TURN;
148 public boolean isInline() {
149 return type == PointType.INLINE;
152 public boolean asPathLegEnd() {
153 // Ends and Turns are path leg ends by default, but also unconnected inline are path leg ends.
154 return isPathLegEnd() || getNext() == null || getPrevious() == null;
158 * True for end components, if control point defines absolute position direction, which rules cannot modify.
159 * This is typical for nozzles.
162 public boolean isDirected() {
163 return isFixed && isEnd();
167 * True for end components, if control is opposite to directed, and rules can modify position and orientation.
168 * This is typical for caps, and other end components.
171 public boolean isNonDirected() {
172 return !isFixed && isEnd();
175 public boolean isVariableLength() {
176 return !isFixed && isInline();
180 * Fixed length in-line component is such that piping rules cannot modify the length.
183 public boolean isFixedLength() {
184 return isFixed && isInline();
187 public boolean isVariableAngle() {
188 return !isFixed && isTurn();
192 * Fixed angle turn component is such that piping rules cannot modify the angle.
195 public boolean isFixedAngle() {
196 return isFixed && isTurn();
200 * Does the turn behave like fixed angle?
201 * For variable angle turns, the turn angle is defined by connected components, and without them, we must handle the component as fixed angle.
204 public boolean asFixedAngle() {
205 return isTurn() && (isFixed || next == null || previous == null);
208 public boolean isBranchEnd() {
209 return isDeletable && isEnd();
212 public boolean isOffset() {
213 return offset != null;
216 public boolean isDualSub() {
217 return parent != null && isSub;
220 public boolean isDualInline() {
221 return children.size() == 1 && children.get(0).isDualSub();
224 public boolean isAxial() {
225 return isInline() && !isDualInline();
228 public boolean isSizeChange() {
230 // if (children.size() == 0)
232 // if (!isDualInline())
234 // return getPipeRun() != children.get(0).getPipeRun();
237 public void setSizeChange(boolean isSizeChange) {
238 this.isSizeChange = isSizeChange;
242 private PipeControlPoint next;
243 private PipeControlPoint previous;
245 public PipeControlPoint getNext() {
249 public PipeControlPoint getPrevious() {
253 public void setNext(PipeControlPoint next) {
255 getParentPoint().setNext(next);
258 if (next != null && next.isDualSub())
260 if (_setNext(next)) {
261 for (PipeControlPoint pcp : children) {
269 public void setPrevious(PipeControlPoint prev) {
271 getParentPoint().setPrevious(prev);
274 if (prev != null && prev.isDualInline())
275 prev = prev.children.get(0);
276 if (_setPrevious(prev)) {
277 for (PipeControlPoint pcp : children) {
279 pcp._setPrevious(prev);
285 protected boolean _setNext(PipeControlPoint next) {
286 if (isEnd() && previous != null && next != null)
287 throw new RuntimeException("End control points are allowed to have only one connection");
289 throw new RuntimeException("Cannot connect to self");
290 if (this.next == next)
292 if (DEBUG) System.out.println(this + " next " + next);
293 if (next == null && isVariableAngle() && previous != null && !isRemoved()) {
294 convertVariableAngleToFixed(Direction.NEXT);
297 if (component != null) {
298 if (parent == null || isSub)
299 component.setNext(next != null ? next.component : null);
301 component.setBranch0(next != null ? next.component : null);
307 protected boolean _setPrevious(PipeControlPoint previous) {
308 if (isEnd() && next != null && previous != null)
309 throw new RuntimeException("End control points are allowed to have only one connection");
310 if (previous == this)
311 throw new RuntimeException("Cannot connect to self");
312 if (this.previous == previous)
314 if (DEBUG) System.out.println(this + " previous " + previous);
315 if (previous == null && isVariableAngle() && next != null && !isRemoved()) {
316 convertVariableAngleToFixed(Direction.PREVIOUS);
318 this.previous = previous;
319 if (component != null) {
320 if (parent == null || isSub)
321 component.setPrevious(previous != null ? previous.component : null);
323 component.setBranch0(previous != null ? previous.component : null);
329 private void convertVariableAngleToFixed(Direction direction) {
330 // We are removing reference, which transforms variable angle to fixed angle.
331 // Since fixed angle is defined differently, we need to calculate fixed angle parameters based on current data
332 // We need to calculate turnAngle and rotationAngle
333 Vector3d dirOut = getPathLegDirection(direction == Direction.NEXT ? Direction.NEXT : Direction.PREVIOUS);
334 Vector3d dir = getPathLegDirection(direction == Direction.NEXT ? Direction.PREVIOUS : Direction.NEXT);
335 if (dir == null || dirOut == null)
338 double angle = dir.angle(dirOut);
339 //super._setNext(null);
340 if (direction == Direction.NEXT)
344 setRotationAngle(0.0);
345 setReversed(direction == Direction.NEXT ? false : true);
346 Vector3d dirOutN = getPathLegDirection(direction == Direction.NEXT ? Direction.NEXT : Direction.PREVIOUS);
348 AxisAngle4d aa = new AxisAngle4d();
349 if (MathTools.createRotation(dirOutN, dirOut, dir, aa)) {
350 setRotationAngle(aa.angle);
352 if (DEBUG) System.out.println("convertToFixed " + dir + " " + dirOut + " " +dirOutN + " " +angle + " "+ aa.angle);
356 public PipeControlPoint parent;
357 public List<PipeControlPoint> children = new ArrayList<PipeControlPoint>();
359 public List<PipeControlPoint> getChildPoints() {
363 public PipeControlPoint getParentPoint() {
368 private double length;
369 private Double turnAngle;
370 private Vector3d turnAxis;
372 private Double offset;
373 private Double rotationAngle;
374 private Boolean reversed;
376 @GetPropertyValue(name="Length",tabId="Debug",value="length")
377 public double getLength() {
381 public void setLength(double l) {
382 if (Double.isInfinite(l) || Double.isNaN(l)) {
385 if (Math.abs(this.length-l) < MathTools.NEAR_ZERO)
388 firePropertyChanged("length");
390 getDualSub().setLength(l);
393 @GetPropertyValue(name="Turn Angle",tabId="Debug",value="turnAngle")
394 public Double getTurnAngle() {
398 @GetPropertyValue(name="Turn Axis",tabId="Debug",value="turnAxis")
399 public Vector3d getTurnAxis() {
403 @GetPropertyValue(name="Offset",tabId="Debug",value="offset")
404 public Double getOffset() {
408 @GetPropertyValue(name="Rotation Angle",tabId="Debug",value="rotationAngle")
409 public Double getRotationAngle() {
410 if (isRotate || asFixedAngle())
411 return rotationAngle;
415 @GetPropertyValue(name="Reversed",tabId="Debug",value="reversed")
416 public Boolean getReversed() {
420 public boolean _getReversed() {
421 if (reversed == null)
426 public void setTurnAngle(Double turnAngle) {
427 if (turnAngle == null || Double.isInfinite(turnAngle) || Double.isNaN(turnAngle)) {
430 if (this.turnAngle != null && Math.abs(this.turnAngle-turnAngle) < MathTools.NEAR_ZERO)
432 this.turnAngle = turnAngle;
433 firePropertyChanged("turnAngle");
436 public void setTurnAxis(Vector3d turnAxis) {
437 if (this.turnAxis != null && MathTools.equals(turnAxis, this.turnAxis))
439 this.turnAxis = turnAxis;
440 firePropertyChanged("turnAxis");
443 public void setOffset(Double offset) {
444 if (Double.isInfinite(offset) || Double.isNaN(offset)) {
447 if (this.offset != null && Math.abs(this.offset-offset) < MathTools.NEAR_ZERO)
449 this.offset = offset;
450 firePropertyChanged("offset");
453 public void setRotationAngle(Double rotationAngle) {
454 if (Double.isInfinite(rotationAngle) || Double.isNaN(rotationAngle)) {
457 if (this.rotationAngle != null && Math.abs(this.rotationAngle-rotationAngle) < MathTools.NEAR_ZERO)
459 this.rotationAngle = rotationAngle;
460 firePropertyChanged("rotationAngle");
463 public void setReversed(Boolean reversed) {
464 this.reversed = reversed;
465 firePropertyChanged("reversed");
468 public Vector3d getSizeChangeOffsetVector(Vector3d dir) {
470 if (rotationAngle == null)
471 q = getControlPointOrientationQuat(dir, 0.0);
473 q = getControlPointOrientationQuat(dir, rotationAngle);
474 Vector3d v = new Vector3d(0.0,offset,0.0);
475 Vector3d offset = new Vector3d();
476 MathTools.rotate(q, v, offset);
480 public Vector3d getSizeChangeOffsetVector() {
482 if (rotationAngle == null)
483 q = getControlPointOrientationQuat(0.0);
485 q = getControlPointOrientationQuat(rotationAngle);
486 Vector3d v = new Vector3d(0.0,offset,0.0);
487 Vector3d offset = new Vector3d();
488 MathTools.rotate(q, v, offset);
492 @GetPropertyValue(name="Next",tabId="Debug",value="next")
493 private String getNextString() {
496 return next.toString();
499 @GetPropertyValue(name="Previous",tabId="Debug",value="previous")
500 private String getPrevString() {
501 if (previous == null)
503 return previous.toString();
506 @GetPropertyValue(name="Sub",tabId="Debug",value="sub")
507 private String getSubString() {
508 if (children.size() == 0)
510 return Arrays.toString(children.toArray());
513 @GetPropertyValue(name="Type",tabId="Debug",value="type")
514 public String getTypeString() {
518 public Vector3d getPathLegEndpointVector() {
519 PipeControlPoint a = findPreviousEnd();
520 PipeControlPoint b = findNextEnd();
522 if (a == null || b == null) {
523 return getPathLegDirection();
526 Vector3d p1 = a.getWorldPosition();
527 Vector3d p2 = b.getWorldPosition();
529 double l = p2.length();
535 return getPathLegDirection();
539 public Vector3d getPathLegDirection() {
540 if (turnAxis == null) {
541 return getPathLegDirection(Direction.NEXT);
543 Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
544 if (dir != null) dir.negate();
549 public Quat4d getControlPointOrientationQuat(double angle) {
550 Vector3d dir = getPathLegDirection();
551 if (turnAxis == null) {
552 return getControlPointOrientationQuat(dir, angle);
554 return getControlPointOrientationQuat(dir, turnAxis, angle);
558 public Quat4d getControlPointOrientationQuat(Vector3d dir, double angle, boolean reversed) {
559 if (turnAxis == null) {
560 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
562 Quat4d q = getControlPointOrientationQuat(dir, angle);
564 Quat4d q2 = new Quat4d();
565 q2.set(new AxisAngle4d(MathTools.Y_AXIS, Math.PI));
570 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
572 return getControlPointOrientationQuat(dir, turnAxis, angle);
576 public Quat4d getControlPointOrientationQuat(double angle, boolean reversed) {
577 Vector3d dir = getPathLegDirection();
578 return getControlPointOrientationQuat(dir, angle, reversed);
581 public Quat4d getControlPointOrientationQuat(Vector3d dir, double angle) {
582 if (dir == null || dir.lengthSquared() < MathTools.NEAR_ZERO)
583 return MathTools.getIdentityQuat();
585 final P3DRootNode root = getRoot();
586 Vector3d up = root != null ? new Vector3d(root.getUpVector()) : new Vector3d(0.0, 1.0, 0.0);
587 final Vector3d legDir = getPathLegEndpointVector();
588 double a = up.angle(legDir);
589 if (a < 0.1 || (Math.PI - a) < 0.1) {
591 up.set(up.getY(), up.getZ(), up.getX());
594 // Project up vector into a normal of the leg direction before applying to 'dir'
595 MathTools.mad(up, legDir, -legDir.dot(up)/legDir.lengthSquared());
598 return getControlPointOrientationQuat(dir, up, angle);
601 public P3DRootNode getRoot() {
602 INode n = getParent();
603 while (n != null && !(n instanceof P3DRootNode))
605 return (P3DRootNode) n;
608 public static Quat4d getControlPointOrientationQuat(Vector3d dir, Vector3d up, double angle) {
609 if (dir == null || dir.lengthSquared() < MathTools.NEAR_ZERO)
610 return MathTools.getIdentityQuat();
612 final Vector3d front = new Vector3d(1.0,0.0,0.0);
614 Quat4d q1 = new Quat4d();
617 Vector3d right = new Vector3d();
619 up = new Vector3d(up);
620 right.cross(dir, up);
621 up.cross(right, dir);
625 Matrix3d m = new Matrix3d();
636 //q1.set(m); MathTools contains more stable conversion
637 MathTools.getQuat(m, q1);
639 // if (DEBUG) System.out.println("PipingTools.getPipeComponentOrientationQuat() " + dir+ " " + up + " " + right);
641 Quat4d q2 = new Quat4d();
642 q2.set(new AxisAngle4d(front, angle));
647 public void insert(PipeControlPoint previous, PipeControlPoint next) {
648 // inserting an offsetpoint is error,
650 throw new RuntimeException("Dual sub points cannot be inserted.");
651 // size change control point cannot be inserted this way, because it ends PipeRun
652 // if (isSizeChange())
653 // throw new RuntimeException("Size change points cannot be inserted.");
654 PipeRun piperun = previous.getPipeRun();
655 // and just to make sure that control point structure is not corrupted
656 if (getPipeRun() != null) {
657 if (piperun != getPipeRun() || piperun != next.getPipeRun())
658 throw new RuntimeException("All controls points must be located on the same pipe run");
660 piperun.addChild(this);
663 // insert new BranchControlPoint between straight's control points
664 PipeControlPoint previousNext = previous.getNext();
665 PipeControlPoint previousPrevious = previous.getPrevious();
667 PipeControlPoint offsetCP = null;
669 offsetCP = getDualSub();
671 if (previousNext != null && previousNext == next) {
672 if (previous.isDualInline()) {
673 throw new RuntimeException();
675 if (next.isDualSub()) {
676 throw new RuntimeException();
678 previous.setNext(this);
679 this.setPrevious(previous);
680 if (previous.isDualSub()) {
681 previous.getParentPoint().setNext(this);
685 if (offsetCP == null) {
686 next.setPrevious(this);
688 next.setPrevious(offsetCP);
689 offsetCP.setNext(next);
690 offsetCP.setPrevious(previous);
693 if (next.isDualInline()) {
694 next.getDualSub().setPrevious(this);
696 } else if (previousPrevious != null && previousPrevious == next) {
697 // control point were given in reverse order
698 if (next.isDualInline())
699 throw new RuntimeException();
700 if (previous.isDualSub())
701 throw new RuntimeException();
703 this.setNext(previous);
704 if (offsetCP == null) {
705 previous.setNext(this);
707 previous.setPrevious(offsetCP);
708 offsetCP.setNext(previous);
709 offsetCP.setPrevious(next);
711 if (previous.isDualInline()) {
712 previous.getDualSub().setPrevious(this);
714 this.setPrevious(next);
716 if (next.isDualSub()) {
717 next.getParentPoint().setNext(this);
721 throw new RuntimeException();
724 PipingRules.validate(piperun);
729 public void insert(PipeControlPoint pcp, Direction direction) {
731 throw new RuntimeException();
732 if (direction == Direction.NEXT) {
733 // if direction is next, user must have given OffsetPoint
734 if (pcp.isDualInline())
735 throw new RuntimeException();
736 // basic next/prev links
738 this.setPrevious(pcp);
739 // and last take care of sizechange / offset points
740 if (pcp.isDualSub()) {
741 pcp.getParentPoint().setNext(this);
743 if (isDualInline()) {
744 getDualSub().setPrevious(this);
747 // if direction is previous, user must have given sizechange
749 throw new RuntimeException();
750 // previous direction is more complicated, since if newCP is SizeChangeControlPoint,
751 // we must link pcp to newCP's OffsetPoint
752 PipeControlPoint nocp = null;
753 if (isDualInline()) {
758 pcp.setPrevious(this);
760 pcp.setPrevious(nocp);
763 if (pcp.isDualInline()) {
764 PipeControlPoint ocp = pcp.getDualSub();
766 ocp.setPrevious(this);
768 ocp.setPrevious(nocp);
772 PipingRules.validate(getPipeRun());
775 public Vector3d getDirectedControlPointDirection() {
776 assert (isDirected());
777 Vector3d dir = new Vector3d();
778 MathTools.rotate(getWorldOrientation(), new Vector3d(1.0, 0.0, 0.0), dir);
784 * Returns direction vector.
786 * For directed control points, always returns outwards pointing vector.
789 * @return normalized vector, or null
791 public Vector3d getDirection(Direction direction) {
793 return getDirectedControlPointDirection();
794 if (isTurn() && asFixedAngle()) {
795 if (direction == Direction.NEXT) {
796 if (previous != null) {
797 PipeControlPoint pcp = this;
798 Vector3d dir = new Vector3d();
799 dir.sub(pcp.getWorldPosition(),previous.getWorldPosition());
800 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
804 Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
805 AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
806 Quat4d q2 = MathTools.getQuat(aa);
807 Vector3d v = new Vector3d(1.,0.,0.);
808 Vector3d offset = new Vector3d();
809 MathTools.rotate(q2, v, offset);
810 MathTools.rotate(q, offset, dir);
816 PipeControlPoint pcp = this;
817 Vector3d dir = new Vector3d();
818 dir.sub(next.getWorldPosition(),pcp.getWorldPosition());
819 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
823 Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
824 AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
825 Quat4d q2 = MathTools.getQuat(aa);
826 Vector3d v = new Vector3d(1.,0.,0.);
827 Vector3d offset = new Vector3d();
828 MathTools.rotate(q2, v, offset);
829 MathTools.rotate(q, offset, dir);
839 * Returns path leg direction of the control point.
841 * This method differs from getDirection by also returning inward pointing vectors for directed control points.
846 public Vector3d getPathLegDirection(Direction direction) {
847 if (direction == Direction.NEXT) {
849 PipeControlPoint pcp = this;
850 if (pcp.isDualInline()) {
851 pcp = pcp.getDualSub();
853 Vector3d v = new Vector3d();
854 v.sub(next.getWorldPosition(),pcp.getWorldPosition());
855 if (v.lengthSquared() > MathTools.NEAR_ZERO)
861 if (previous == null) {
863 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
864 return getDirectedControlPointDirection();
867 if (isVariableAngle() && !asFixedAngle())
868 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
870 PipeControlPoint pcp = this;
871 if (pcp.isDualSub()) {
872 pcp = pcp.getParentPoint();
874 Vector3d v = new Vector3d();
875 v.sub(pcp.getWorldPosition(),previous.getWorldPosition());
876 if (v.lengthSquared() > MathTools.NEAR_ZERO)
881 } else if (isDirected()) {
882 return getDirectedControlPointDirection();
883 } else if (isEnd()) {
884 Vector3d v = new Vector3d();
885 v.sub(getWorldPosition(),previous.getWorldPosition());
886 if (v.lengthSquared() > MathTools.NEAR_ZERO)
891 } else if (isTurn() && asFixedAngle() && !_getReversed()) {
892 return getDirection(Direction.NEXT);
894 throw new RuntimeException("Missing implementation " + this);
898 if (previous != null) {
899 PipeControlPoint pcp = this;
901 pcp = getParentPoint();
902 Vector3d v = new Vector3d();
903 v.sub(previous.getWorldPosition(),pcp.getWorldPosition());
904 if (v.lengthSquared() > MathTools.NEAR_ZERO)
912 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
913 Vector3d v = getDirectedControlPointDirection();
917 if (isVariableAngle() && !asFixedAngle())
918 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
920 PipeControlPoint pcp = this;
921 if (pcp.isDualInline()) {
922 pcp = pcp.getDualSub();
924 Vector3d v = new Vector3d();
925 v.sub(pcp.getWorldPosition(),next.getWorldPosition());
926 if (v.lengthSquared() > MathTools.NEAR_ZERO)
931 } else if (isDirected()) {
932 Vector3d v = getDirectedControlPointDirection();
935 } else if (isEnd()) {
936 Vector3d v = new Vector3d();
937 v.sub(getWorldPosition(),next.getWorldPosition());
938 if (v.lengthSquared() > MathTools.NEAR_ZERO)
943 } else if (isTurn() && asFixedAngle() && _getReversed()) {
944 return getDirection(Direction.PREVIOUS);
946 throw new RuntimeException("Missing implementation " + this);
952 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) {
955 PipeControlPoint sub = isAxial() ? this : getDualSub();
956 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
957 Vector3d dir = sub.getInlineDir();
959 dir.scale(length * 0.5);
966 public void getControlPointEnds(Tuple3d p1, Tuple3d p2) {
967 PipeControlPoint sub = isAxial() || isDirected() || isTurn() ? this : getChildPoints().get(0);
968 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
973 dir2 = getInlineDir();
974 dir2.scale(length * 0.5);
975 dir1 = new Vector3d(dir2);
978 dir1 = getPathLegDirection(Direction.PREVIOUS);
979 dir2 = sub.getPathLegDirection(Direction.NEXT);
990 * Get both path leg directions, with (0,0,0) if no connection exists. The returned vectors are not normalized.
992 * @param v1 Set to the direction towards the previous control point on output
993 * @param v2 Set to the direction towards the next control point on output
995 public void getEndDirections(Tuple3d v1, Tuple3d v2) {
996 PipeControlPoint sub = isAxial() ? this : getDualSub();
998 Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
999 Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
1012 public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) {
1013 assert (isInline());
1015 Vector3d pos = getWorldPosition();
1016 dir.set(getInlineDir());
1018 dir.scale(length * 0.5);
1025 public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) {
1026 assert (isInline());
1028 Vector3d pos = getWorldPosition();
1030 dir.set(getInlineDir());
1032 dir.scale(length * 0.5);
1039 public Vector3d getInlineDir() {
1040 Vector3d dir = getPathLegDirection(Direction.NEXT);
1042 dir = getPathLegDirection(Direction.PREVIOUS);
1044 // Use reverse direction
1047 // Control point is not connected at all, use current orientation
1048 dir = new Vector3d(1,0,0);
1049 MathTools.rotate(getWorldOrientation(), dir, dir);
1055 public double getInlineLength() {
1056 if (type == PointType.TURN)
1058 else if (type == PointType.INLINE)
1059 return length * 0.5;
1064 * Return the position indicated by the argument. If the indicated direction is not connected, the
1065 * control point's wolrd position is returned instead.
1067 * @param type A selector for the position to be returned
1068 * @return The selected position
1070 public Vector3d getRealPosition(PositionType type) {
1071 Vector3d pos = getWorldPosition();
1074 double length = getInlineLength();
1077 dir = getInlineDir();
1079 dir = getPathLegDirection(Direction.NEXT);
1086 double length = getInlineLength();
1089 dir = getInlineDir();
1092 dir = getPathLegDirection(Direction.PREVIOUS);
1099 // IEntity portDir = pcp.getSingleRelatedObject(ProcessResource.plant3Dresource.HasDirection);
1100 // TODO : how we calculated needed space for a port; does it has an offset from control point's position or not?
1110 public void getInlineMovement(Tuple3d start, Tuple3d end) {
1111 // FIXME : check type of neighbor components and allow movement on top of variable length components,
1112 // find proper range for movement (pcp's position is not)
1113 PipeControlPoint p = previous.getPrevious();
1114 PipeControlPoint n = next.getNext();
1115 start.set(p.getWorldPosition());
1116 end.set(n.getWorldPosition());
1119 public PipeControlPoint findNextEnd() {
1120 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1121 return findNextEnd( t);
1124 public PipeControlPoint findPreviousEnd() {
1125 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
1126 return findPreviousEnd(t);
1129 public PipeControlPoint findNextEnd(List<PipeControlPoint> nextList) {
1131 PipeControlPoint pcp = null;
1132 PipeControlPoint p = null;
1133 if (nextList.size() == 0)
1137 p = nextList.get(nextList.size() - 1);
1142 if (nextList.size() > 0)
1143 nextList.remove(nextList.size() - 1);
1144 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1148 if (pcp.isPathLegEnd()) {
1149 //if (DEBUG) System.out.println(" " + pcp.getResource());
1153 // if (DEBUG) System.out.print(" " + pcp.getResource());
1158 public PipeControlPoint findPreviousEnd(List<PipeControlPoint> prevList) {
1160 PipeControlPoint pcp = null;
1161 PipeControlPoint p = null;
1162 if (prevList.size() == 0)
1166 p = prevList.get(prevList.size() - 1);
1168 pcp = p.getPrevious();
1171 if (prevList.size() > 0)
1172 prevList.remove(prevList.size() - 1);
1173 // if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1176 if (pcp.isPathLegEnd()) {
1177 // if (DEBUG) System.out.println(" " + pcp.getResource());
1181 // if (DEBUG)System.out.print(" " + pcp.getResource());
1186 public void _remove() {
1191 public PipeControlPoint getDualSub() {
1193 return getChildPoints().get(0);
1195 throw new IllegalStateException("Current control point is not dual inline");
1199 public void _remove(boolean renconnect) {
1203 if (DEBUG) System.out.println(this + " Remove " + renconnect);
1205 if (getParentPoint() != null) {
1206 getParentPoint()._remove(renconnect);
1209 PipeRun pipeRun = getPipeRun();
1210 // PipeRUn removal has been changed, so pipeRun may be null.
1211 // if (pipeRun == null)
1214 PipeControlPoint additionalRemove = null;
1215 if (!PipingRules.isEnabled()) {
1221 PipeControlPoint currentPrev = previous;
1222 PipeControlPoint currentNext = next;
1223 if (currentNext == null && currentPrev == null) {
1225 if (pipeRun != null) {
1226 pipeRun.remChild(this);
1227 checkRemove(pipeRun);
1231 if (currentNext != null && currentPrev != null) {
1232 boolean link = renconnect;
1233 if (currentNext.isBranchEnd()) {
1235 currentNext.remove();
1239 if (currentPrev.isBranchEnd()) {
1241 currentPrev.remove();
1246 if (currentPrev.isDirected() && currentNext.isDirected())
1248 else if (this.isDualSub()) {
1249 throw new RuntimeException("_remove() is called for parent point, somehow got to child point. " + this);
1252 if (currentNext == null) {
1254 } else if (currentNext.isDualInline()) {
1255 PipeControlPoint sccp = currentNext;
1256 PipeControlPoint ocp = currentNext.getDualSub();
1258 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1261 sccp.setPrevious(currentPrev);
1262 //ocp.setPrevious(currentPrev);
1263 assert(ocp.getPrevious() == currentPrev);
1265 sccp.setPrevious(null);
1266 //ocp.setPrevious(null);
1267 assert(ocp.getPrevious() == null);
1270 } else if (currentNext.isDualSub()) {
1271 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1272 } else if (currentNext.previous == this) {
1274 currentNext.setPrevious(currentPrev);
1276 currentNext.setPrevious(null);
1279 } else if (isDualInline()) {
1280 if (currentNext.previous != getDualSub()) {
1281 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1284 currentNext.setPrevious(currentPrev);
1286 currentNext.setPrevious(null);
1290 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1292 if (currentPrev == null) {
1294 } else if (currentPrev.isDualInline()) {
1295 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1296 } else if (currentPrev.isDualSub()) {
1297 PipeControlPoint ocp = currentPrev;
1298 PipeControlPoint sccp = currentPrev.getParentPoint();
1300 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1302 //ocp.setNext(currentNext);
1303 sccp.setNext(currentNext);
1304 assert(ocp.getNext() == currentNext);
1306 //ocp.setNext(null);
1308 assert(ocp.getNext() == null);
1311 } else if (currentPrev.next == this) {
1313 currentPrev.setNext(currentNext);
1315 currentPrev.setNext(null);
1319 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged");
1322 if (currentNext.isVariableLength() && currentPrev.isVariableLength()) {
1323 // we have to join them into single variable length component.
1324 additionalRemove = currentPrev;
1325 // combine lengths and set the location of remaining control point to the center.
1326 Point3d ps = new Point3d();
1327 Point3d pe = new Point3d();
1328 Point3d ns = new Point3d();
1329 Point3d ne = new Point3d();
1330 currentPrev.getInlineControlPointEnds(ps, pe);
1331 currentNext.getInlineControlPointEnds(ns, ne);
1332 double l = currentPrev.getLength() + currentNext.getLength();
1333 Vector3d cp = new Vector3d();
1336 currentNext.setLength(l);
1337 currentNext.setWorldPosition(cp);
1340 // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous.
1342 } else if (currentNext != null) {
1343 if (currentNext.isDualInline()) {
1344 PipeControlPoint sccp = currentNext;
1345 PipeControlPoint ocp = currentNext.getDualSub();
1347 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1349 sccp.setPrevious(null);
1350 assert(ocp.getPrevious() == null);
1351 //ocp.setPrevious(null);
1352 } else if (currentNext.isDualSub()) {
1353 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1354 } else if (currentNext.previous == this) {
1355 currentNext.setPrevious(null);
1356 } else if (isDualInline()) {
1357 if (currentNext.previous != getDualSub()) {
1358 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1360 currentNext.setPrevious(null);
1362 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1365 } else { //(previous != null)
1366 if(currentPrev.isDualInline()) {
1367 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1368 } else if (currentPrev.isDualSub()) {
1369 PipeControlPoint ocp = currentPrev;
1370 PipeControlPoint sccp = currentPrev.getParentPoint();
1372 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1375 assert(ocp.getNext() == null);
1376 } else if (currentPrev.next == this) {
1377 currentPrev.setNext(null);
1379 throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1383 if (children.size() > 0 ) {
1385 } else if (parent!= null) {
1386 removeParentPoint();
1392 if (pipeRun != null) {
1393 pipeRun.remChild(this);
1394 checkRemove(pipeRun);
1395 if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
1396 PipingRules.validate(pipeRun);
1398 if (additionalRemove != null)
1399 additionalRemove.remove();
1404 * Removes control point and attempts to reconnect next/prev
1406 * If this point is size change (PipeRuns are different on both sides), then reconnection cannot be made.
1408 public void remove() {
1409 PipeControlPoint currentPrev = previous;
1410 PipeControlPoint currentNext = next;
1413 if (currentNext != null)
1414 if (!currentNext.checkRemove())
1415 PipingRules.requestUpdate(currentNext);
1416 if (currentPrev != null)
1417 if (!currentPrev.checkRemove())
1418 PipingRules.requestUpdate(currentPrev);
1419 } catch (Exception e) {
1420 e.printStackTrace();
1426 * Removes control point without attempting to reconnect next/prev.
1427 * This usually leads to creation of another PipeRun for the control points after this point.
1429 public void removeAndSplit() {
1430 PipeControlPoint currentPrev = previous;
1431 PipeControlPoint currentNext = next;
1433 if (next != null && previous != null) {
1434 P3DRootNode root = (P3DRootNode)getPipelineComponent().getRootNode();
1435 PipeRun nextPipeRun = new PipeRun();
1436 nextPipeRun.setName(root.getUniqueName("PipeRun"));
1437 root.addChild(nextPipeRun);
1439 PipeRun previousRun = previous.getPipeRun();
1440 nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
1441 nextPipeRun.setTurnRadiusArray(previousRun.getTurnRadiusArray());
1443 PipelineComponent n = next.getPipelineComponent();
1445 if (n.getPipeRun() != previousRun)
1447 if (! (n instanceof Nozzle)) {
1449 nextPipeRun.addChild(n);
1451 n.setPipeRun(nextPipeRun);
1457 if (currentNext != null)
1458 if (!currentNext.checkRemove())
1459 PipingRules.requestUpdate(currentNext);
1460 if (currentPrev != null)
1461 if (!currentPrev.checkRemove())
1462 PipingRules.requestUpdate(currentPrev);
1463 } catch (Exception e) {
1464 e.printStackTrace();
1469 * This is called when adjacent control point is removed.
1471 * This call should remove the give point, if the point cannot exist alone.
1472 * At the moment there is one such case: branch.
1476 protected boolean checkRemove() {
1477 if (getParentPoint() != null) {
1478 return getParentPoint().checkRemove();
1480 if (getPipelineComponent() == null)
1481 return true; // already removed
1482 if (getPipelineComponent().getType().equals("Plant3D.URIs.Builtin_BranchSplitComponent")) {
1483 if (getChildPoints().get(0).getNext() == null && getChildPoints().get(0).getPrevious() == null) {
1488 return checkRemove(getPipeRun());
1492 private boolean checkRemove(PipeRun pipeRun) {
1493 if (pipeRun == null)
1495 Collection<PipeControlPoint> points = pipeRun.getControlPoints();
1496 if (points.size() == 0) {
1499 } else if (points.size() == 1) {
1500 PipeControlPoint pcp = points.iterator().next();
1501 if (pcp.isDeletable() && pcp.getNext() == null && pcp.getPrevious() == null) {
1502 pcp._remove(); // This call will recursively call also this method...
1505 } else if (points.size() == 2) {
1511 private void removeSubPoints() {
1512 for (PipeControlPoint p : children) {
1516 PipeControlPoint currentNext = p.getNext();
1517 PipeControlPoint currentPrev = p.getPrevious();
1519 p._setPrevious(null);
1520 PipeRun run = p.getPipeRun();
1525 if (currentNext != null)
1526 if (!currentNext.checkRemove())
1527 PipingRules.requestUpdate(currentNext);
1528 if (currentPrev != null)
1529 if (!currentPrev.checkRemove())
1530 PipingRules.requestUpdate(currentPrev);
1536 private void removeParentPoint() {
1537 throw new RuntimeException("Child points cannot be removed directly");
1540 public boolean isRemoved() {
1541 return component == null;
1544 private void removeComponent() {
1545 if (component == null)
1547 PipelineComponent next = component.getNext();
1548 PipelineComponent prev = component.getPrevious();
1549 PipelineComponent br0 = component.getBranch0();
1550 component.setNext(null);
1551 component.setPrevious(null);
1552 component.setBranch0(null);
1554 if (next.getNext() == component)
1556 else if (next.getPrevious() == component)
1557 next.setPrevious(null);
1558 else if (next.getBranch0() == component)
1559 next.setBranch0(null);
1562 if (prev.getNext() == component)
1564 else if (prev.getPrevious() == component)
1565 prev.setPrevious(null);
1566 else if (prev.getBranch0() == component)
1567 prev.setBranch0(null);
1570 if (br0.getNext() == component)
1572 else if (br0.getPrevious() == component)
1573 br0.setPrevious(null);
1574 else if (br0.getBranch0() == component)
1575 br0.setBranch0(null);
1577 PipelineComponent comp = component;
1584 public void setOrientation(Quat4d orientation) {
1585 if (MathTools.equals(orientation, getOrientation()))
1587 if (getPipelineComponent() != null && (getPipelineComponent() instanceof Nozzle))
1588 System.out.println();
1589 super.setOrientation(orientation);
1590 if (getParentPoint() == null && component != null)
1591 component._setWorldOrientation(getWorldOrientation());
1596 public void setPosition(Vector3d position) {
1597 if (MathTools.equals(position, getPosition()))
1599 if (Double.isNaN(position.x) || Double.isNaN(position.y) || Double.isNaN(position.z))
1600 throw new IllegalArgumentException("NaN is not supported");
1601 super.setPosition(position);
1602 if (getParentPoint() == null && component != null)
1603 component._setWorldPosition(getWorldPosition());
1607 private void updateSubPoint() {
1609 if (next == null && previous == null) {
1610 for (PipeControlPoint sub : getChildPoints()) {
1611 sub.setWorldPosition(getWorldPosition());
1612 sub.setWorldOrientation(getWorldOrientation());
1616 for (PipeControlPoint sub : getChildPoints()) {
1617 Vector3d wp = getWorldPosition();
1618 wp.add(getSizeChangeOffsetVector());
1619 sub.setWorldPosition(wp);
1620 sub.setWorldOrientation(getWorldOrientation());
1623 for (PipeControlPoint sub : getChildPoints()) {
1624 sub.setWorldPosition(getWorldPosition());
1625 sub.setWorldOrientation(getWorldOrientation());
1631 public void _setWorldPosition(Vector3d position) {
1632 Vector3d localPos = getLocalPosition(position);
1633 super.setPosition(localPos);
1637 public void _setWorldOrientation(Quat4d orientation) {
1638 Quat4d localOr = getLocalOrientation(orientation);
1639 super.setOrientation(localOr);
1644 public String toString() {
1645 return getClass().getName() + "@" + Integer.toHexString(hashCode());