]> gerrit.simantics Code Review - simantics/3d.git/blob - org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipeControlPoint.java
Removing components left orphan piperuns in the plant
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / scenegraph / controlpoint / PipeControlPoint.java
1 package org.simantics.plant3d.scenegraph.controlpoint;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.List;
7
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;
14
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.plant3d.scenegraph.IP3DNode;
19 import org.simantics.plant3d.scenegraph.Nozzle;
20 import org.simantics.plant3d.scenegraph.P3DRootNode;
21 import org.simantics.plant3d.scenegraph.PipeRun;
22 import org.simantics.plant3d.scenegraph.PipelineComponent;
23
24 import vtk.vtkRenderer;
25
26
27 public class PipeControlPoint extends G3DNode implements IP3DNode {
28
29         private static boolean DEBUG = false;
30
31         public enum PointType{INLINE,TURN,END};
32         public enum Direction{NEXT,PREVIOUS};
33         public enum PositionType {SPLIT,NEXT,PREVIOUS,PORT}
34
35         private PipelineComponent component;
36
37         private PointType type;
38         private boolean isFixed = true;        // In-line: fixed-length Turn: fixed-angle
39         private boolean isRotate = false;      // rotates around path leg axis.
40         private boolean isReverse = false;     // definition direction can be swapped
41         private boolean isDeletable = true;    // can be removed by rules
42         private boolean isSizeChange = false;  // changes size of the pipe. The next control point / component is on different PipeRun
43         private boolean isSub = false;         // child point for offset / size change
44
45         public PipeControlPoint(PipelineComponent component) {
46                 this.component = component;
47                 if (component.getPipeRun() != null)
48                         component.getPipeRun().addChild(this);
49
50         }
51
52         public PipeControlPoint(PipelineComponent component, PipeRun piperun) {
53                 this.component = component;
54                 piperun.addChild(this);
55         }
56
57         @Override
58         public void update(vtkRenderer ren) {
59                 try {
60                         PipingRules.requestUpdate(this);
61                 } catch (Exception e) {
62                         e.printStackTrace();
63                 }
64
65         }
66
67         public PipeRun getPipeRun() {
68                 return (PipeRun)getParent();
69         }
70
71         public PipelineComponent getPipelineComponent() {
72                 return component;
73         }
74
75         public PointType getType() {
76                 return type;
77         }
78
79         public void setType(PointType type) {
80                 this.type = type;
81         }
82
83         @GetPropertyValue(name="Fixed",tabId="Debug",value="fixed")
84         public boolean isFixed() {
85                 return isFixed;
86         }
87
88         public void setFixed(boolean fixed) {
89                 this.isFixed = fixed;
90         }
91
92         @GetPropertyValue(name="Rotate",tabId="Debug",value="rotate")
93         public boolean isRotate() {
94                 return isRotate;
95         }
96
97         public void setRotate(boolean rotate) {
98                 this.isRotate = rotate;
99         }
100
101         @GetPropertyValue(name="Reverse",tabId="Debug",value="reverse")
102         public boolean isReverse() {
103                 return isReverse;
104         }
105
106         public void setReverse(boolean reverse) {
107                 this.isReverse = reverse;
108         }
109
110         public void setSub(boolean sub) {
111                 this.isSub = sub;
112         }
113
114         @GetPropertyValue(name="Deletable",tabId="Debug",value="deletable")
115         public boolean isDeletable() {
116                 return isDeletable;
117         }
118
119         public void setDeletable(boolean deletable) {
120                 this.isDeletable = deletable;
121         }
122
123         public boolean isPathLegEnd() {
124                 return type != PointType.INLINE;
125         }
126
127         public boolean isEnd() {
128                 return type == PointType.END;
129         }
130
131         public boolean isTurn() {
132                 return type == PointType.TURN;
133         }
134
135         public boolean isInline() {
136                 return type == PointType.INLINE;
137         }
138
139         /**
140          * True for end components, if control point defines absolute position direction, which rules cannot modify. 
141          * This is typical for nozzles.
142          * @return
143          */
144         public boolean isDirected() {
145                 return isFixed && isEnd();
146         }
147
148         /**
149      * True for end components, if control is opposite to directed, and rules can modify position and orientation.
150      * This is typical for caps, and other end components.
151      * @return
152      */
153         public boolean isNonDirected() {
154                 return !isFixed && isEnd();
155         }
156
157         public boolean isVariableLength() {
158                 return !isFixed && isInline();
159         }
160         
161         /**
162          * Fixed length in-line component is such that piping rules cannot modify the length.
163          * @return
164          */
165         public boolean isFixedLength() {
166         return isFixed && isInline();
167     }
168
169         public boolean isVariableAngle() {
170                 return !isFixed && isTurn();
171         }
172         
173         /**
174          * Fixed angle turn component is such that piping rules cannot modify the angle.
175          * @return
176          */
177         public boolean isFixedAngle() {
178         return isFixed && isTurn();
179     }
180         
181         /**
182          * Does the turn behave like fixed angle?
183          * For variable angle turns, the turn angle is defined by connected components, and without them, we must handle the component as fixed angle. 
184          * @return
185          */
186         public boolean asFixedAngle() {
187         return isTurn() && (isFixed || next == null || previous == null);
188     }
189
190         public boolean isBranchEnd() {
191                 return isDeletable && isEnd();
192         }
193
194         public boolean isOffset() {
195                 return offset != null;
196         }
197
198         public boolean isDualSub() {
199                 return parent != null && isSub;
200         }
201
202         public boolean isDualInline() {
203                 return children.size() == 1 && children.get(0).isDualSub();
204         }
205
206         public boolean isAxial() {
207                 return isInline() && !isDualInline();
208         }
209
210         public boolean isSizeChange() {
211                 return isSizeChange;
212                 //              if (children.size() == 0)
213                 //                      return false;
214                 //              if (!isDualInline())
215                 //                      return false;
216                 //              return getPipeRun() != children.get(0).getPipeRun();
217         }
218
219         public void setSizeChange(boolean isSizeChange) {
220                 this.isSizeChange = isSizeChange;
221         }
222
223
224         private PipeControlPoint next;
225         private PipeControlPoint previous;
226
227         public PipeControlPoint getNext() {
228                 return next;
229         }
230
231         public PipeControlPoint getPrevious() {
232                 return previous;
233         }
234
235         public void setNext(PipeControlPoint next) {
236             if (isSub) {
237                 getParentPoint().setNext(next);
238                 return;
239             }
240             if (next != null && next.isDualSub())
241                 next = next.parent;
242             if (_setNext(next)) {
243                 for (PipeControlPoint pcp : children) {
244                 if (pcp.isSub)
245                     pcp._setNext(next);
246             }
247                 updateSubPoint();
248             }
249         }
250         
251         public void setPrevious(PipeControlPoint prev) {
252         if (isSub) {
253             getParentPoint().setPrevious(prev);
254             return;
255         }
256         if (prev != null && prev.isDualInline())
257             prev = prev.children.get(0);
258         if (_setPrevious(prev)) {
259             for (PipeControlPoint pcp : children) {
260                 if (pcp.isSub)
261                     pcp._setPrevious(prev);
262             }
263             updateSubPoint();
264         }
265     }
266         
267         protected boolean _setNext(PipeControlPoint next) {
268                 if (isEnd() && previous != null && next != null)
269                         throw new RuntimeException("End control points are allowed to have only one connection");
270                 if (next == this)
271                         throw new RuntimeException("Cannot connect to self");
272                 if (this.next == next)
273                         return false;
274                 if (DEBUG) System.out.println(this + " next " + next);
275                 if (next == null && isVariableAngle() && previous != null && !isRemoved()) {
276                     convertVariableAngleToFixed(Direction.NEXT);
277                 }
278                 this.next = next;
279                 if (component != null) {
280                         if (parent == null || isSub)
281                                 component.setNext(next != null ? next.component : null);
282                         else
283                                 component.setBranch0(next != null ? next.component : null);
284                         
285                 }
286                 return true;
287         }
288
289         protected boolean _setPrevious(PipeControlPoint previous) {
290                 if (isEnd() && next != null && previous != null)
291                         throw new RuntimeException("End control points are allowed to have only one connection");
292                 if (previous == this)
293                         throw new RuntimeException("Cannot connect to self");
294                 if (this.previous == previous)
295                         return false;
296                 if (DEBUG) System.out.println(this + " previous " + previous);
297                 if (previous == null && isVariableAngle() && next != null && !isRemoved()) {
298             convertVariableAngleToFixed(Direction.PREVIOUS);
299         }
300                 this.previous = previous;
301                 if (component != null) {
302                         if (parent == null || isSub)
303                                 component.setPrevious(previous != null ? previous.component : null);
304                         else
305                                 component.setBranch0(previous != null ? previous.component : null);
306                         updateSubPoint();
307                 }
308                 return true;
309         }
310         
311         private void convertVariableAngleToFixed(Direction direction) {
312             // We are removing reference, which transforms variable angle to fixed angle.
313         // Since fixed angle is defined differently, we need to calculate fixed angle parameters based on current data
314         // We need to calculate turnAngle and rotationAngle
315             Vector3d dirOut = getPathLegDirection(direction == Direction.NEXT ? Direction.NEXT : Direction.PREVIOUS);
316         Vector3d dir = getPathLegDirection(direction == Direction.NEXT ? Direction.PREVIOUS : Direction.NEXT);
317         dir.negate();
318         dirOut.normalize();
319         dir.normalize();
320         double angle = dir.angle(dirOut);
321         //super._setNext(null);
322         if (direction == Direction.NEXT)
323             next = null;
324         else
325             previous = null;
326         setRotationAngle(0.0);
327         setReversed(direction == Direction.NEXT ? false : true);
328         Vector3d dirOutN = getPathLegDirection(direction == Direction.NEXT ? Direction.NEXT : Direction.PREVIOUS);
329         dirOutN.normalize();
330         AxisAngle4d aa = new AxisAngle4d();
331         if (MathTools.createRotation(dirOutN, dirOut, dir, aa)) {
332             setRotationAngle(aa.angle);
333             setTurnAngle(angle);
334         }
335         }
336
337         public PipeControlPoint parent;
338         public List<PipeControlPoint> children = new ArrayList<PipeControlPoint>();
339
340         public List<PipeControlPoint> getChildPoints() {
341                 return children;
342         }
343
344         public PipeControlPoint getParentPoint() {
345                 return parent;
346         }
347
348
349         private double length;
350         private Double turnAngle;
351         private Vector3d turnAxis;
352
353         private Double offset;
354         private Double rotationAngle;
355         private Boolean reversed;
356
357         @GetPropertyValue(name="Length",tabId="Debug",value="length")
358         public double getLength() {
359                 return length;
360         }
361
362         public void setLength(double l) {
363                 if (Double.isInfinite(l) || Double.isNaN(l)) {
364                         return;
365                 }
366                 if (Math.abs(this.length-l) < MathTools.NEAR_ZERO)
367                         return;
368                 this.length = l;
369                 firePropertyChanged("length");
370                 if (isDualInline())
371                     getDualSub().setLength(l);
372         }
373
374         @GetPropertyValue(name="Turn Angle",tabId="Debug",value="turnAngle")
375         public Double getTurnAngle() {
376                 return turnAngle;
377         }
378
379         @GetPropertyValue(name="Turn Axis",tabId="Debug",value="turnAxis")
380         public Vector3d getTurnAxis() {
381                 return turnAxis;
382         }
383
384         @GetPropertyValue(name="Offset",tabId="Debug",value="offset")
385         public Double getOffset() {
386                 return offset;
387         }
388
389         @GetPropertyValue(name="Rotation Angle",tabId="Debug",value="rotationAngle")
390         public Double getRotationAngle() {
391             if (asFixedAngle())
392                 return rotationAngle;
393             return null;
394         }
395
396         @GetPropertyValue(name="Reversed",tabId="Debug",value="reversed")
397         public Boolean getReversed() {
398                 return reversed;
399         }
400
401         public boolean _getReversed() {
402                 if (reversed == null)
403                         return false;
404                 return reversed;
405         }
406
407         public void setTurnAngle(Double turnAngle) {
408                 if (turnAngle == null || Double.isInfinite(turnAngle) || Double.isNaN(turnAngle)) {
409                         return;
410                 }
411                 if (this.turnAngle != null && Math.abs(this.turnAngle-turnAngle) < MathTools.NEAR_ZERO)
412                         return;
413                 this.turnAngle = turnAngle;
414                 firePropertyChanged("turnAngle");
415         }
416
417         public void setTurnAxis(Vector3d turnAxis) {
418                 if (this.turnAxis != null && MathTools.equals(turnAxis, this.turnAxis))
419                         return;
420                 this.turnAxis = turnAxis;
421                 firePropertyChanged("turnAxis");
422         }
423
424         public void setOffset(Double offset) {
425                 if (Double.isInfinite(offset) || Double.isNaN(offset)) {
426                         return;
427                 }
428                 if (this.offset != null && Math.abs(this.offset-offset) < MathTools.NEAR_ZERO)
429                         return;
430                 this.offset = offset;
431                 firePropertyChanged("offset");
432         }
433
434         public void setRotationAngle(Double rotationAngle) {
435                 if (Double.isInfinite(rotationAngle) || Double.isNaN(rotationAngle)) {
436                         return;
437                 }
438                 if (this.rotationAngle != null && Math.abs(this.rotationAngle-rotationAngle) < MathTools.NEAR_ZERO)
439                         return;
440                 this.rotationAngle = rotationAngle;
441                 firePropertyChanged("rotationAngle");
442         }
443
444         public void setReversed(Boolean reversed) {
445                 this.reversed = reversed;
446                 firePropertyChanged("reversed");
447         }
448
449         public Vector3d getSizeChangeOffsetVector(Vector3d dir) {
450                 Quat4d q;
451                 if (rotationAngle == null)
452                         q = getControlPointOrientationQuat(dir, 0.0);
453                 else
454                         q = getControlPointOrientationQuat(dir, rotationAngle);
455                 Vector3d v = new Vector3d(0.0,offset,0.0);
456                 Vector3d offset = new Vector3d();
457                 MathTools.rotate(q, v, offset);
458                 return offset;
459         }
460
461         public Vector3d getSizeChangeOffsetVector() {
462                 Quat4d q;
463                 if (rotationAngle == null)
464                         q = getControlPointOrientationQuat(0.0);
465                 else
466                         q = getControlPointOrientationQuat(rotationAngle);
467                 Vector3d v = new Vector3d(0.0,offset,0.0);
468                 Vector3d offset = new Vector3d();
469                 MathTools.rotate(q, v, offset);
470                 return offset;
471         }
472
473         @GetPropertyValue(name="Next",tabId="Debug",value="next")
474         private String getNextString() {
475                 if (next == null)
476                         return null;
477                 return next.toString();
478         }
479
480         @GetPropertyValue(name="Previous",tabId="Debug",value="previous")
481         private String getPrevString() {
482                 if (previous == null)
483                         return null;
484                 return previous.toString();
485         }
486
487         @GetPropertyValue(name="Sub",tabId="Debug",value="sub")
488         private String getSubString() {
489                 if (children.size() == 0)
490                         return "";
491                 return Arrays.toString(children.toArray());
492         }
493
494         @GetPropertyValue(name="Type",tabId="Debug",value="type")
495         public String getTypeString() {
496                 return type.name();
497         }
498
499         public Quat4d getControlPointOrientationQuat(double angle) {
500
501                 if (turnAxis == null) {
502                         Vector3d dir = getPathLegDirection(Direction.NEXT);
503                         if (dir.lengthSquared() > MathTools.NEAR_ZERO)
504                                 dir.normalize();
505                         return getControlPointOrientationQuat(dir, angle);
506                 } else {
507                         Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
508                         dir.negate();
509                         if (dir.lengthSquared() > MathTools.NEAR_ZERO)
510                                 dir.normalize();
511                         return getControlPointOrientationQuat(dir, turnAxis, angle);
512                 }
513         }
514
515         public Quat4d getControlPointOrientationQuat(double angle, boolean reversed) {
516
517                 if (turnAxis == null) {
518                         Vector3d dir = getPathLegDirection(Direction.NEXT);
519                         if (dir.lengthSquared() > MathTools.NEAR_ZERO)
520                                 dir.normalize();
521                         Quat4d q =  getControlPointOrientationQuat(dir, angle);
522                         if (reversed) {
523                                 Quat4d q2 = new Quat4d();
524                                 q2.set(new AxisAngle4d(MathTools.Y_AXIS, Math.PI));
525                                 q.mulInverse(q2);
526                         }
527                         return q;
528                 } else {
529                         Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
530                         dir.negate();
531                         if (dir.lengthSquared() > MathTools.NEAR_ZERO)
532                                 dir.normalize();
533                         return getControlPointOrientationQuat(dir, turnAxis, angle);
534                 }
535         }
536
537
538
539         public static Quat4d getControlPointOrientationQuat(Vector3d dir, double angle) {
540                 if (dir.lengthSquared() < MathTools.NEAR_ZERO)
541                         return MathTools.getIdentityQuat();
542
543
544                 Vector3d up = new Vector3d(0.0, 1.0, 0.0);
545                 double a = up.angle(dir);
546                 if (a < 0.1 || (Math.PI - a) < 0.1) {
547                         up.set(1.0, 0.0, 0.0);
548                 }
549
550
551                 return getControlPointOrientationQuat(dir, up, angle);
552         }
553
554         public static Quat4d getControlPointOrientationQuat(Vector3d dir, Vector3d up,  double angle) {
555                 if (dir.lengthSquared() < MathTools.NEAR_ZERO)
556                         return MathTools.getIdentityQuat();
557
558                 final Vector3d front = new Vector3d(1.0,0.0,0.0);
559
560                 Quat4d q1 = new Quat4d();
561
562
563                 Vector3d right = new Vector3d();
564
565                 right.cross(dir, up);
566                 up.cross(right, dir);
567                 right.normalize();
568                 up.normalize();
569
570                 Matrix3d m = new Matrix3d();
571                 m.m00 = dir.x;
572                 m.m10 = dir.y;
573                 m.m20 = dir.z;
574                 m.m01 = up.x;
575                 m.m11 = up.y;
576                 m.m21 = up.z;
577                 m.m02 = right.x;
578                 m.m12 = right.y;
579                 m.m22 = right.z;
580
581                 //q1.set(m); MathTools contains more stable conversion
582                 MathTools.getQuat(m, q1);
583
584                 //                      if (DEBUG) System.out.println("PipingTools.getPipeComponentOrientationQuat() " + dir+ " " + up + " " + right);
585
586                 Quat4d q2 = new Quat4d();
587                 q2.set(new AxisAngle4d(front, angle));
588                 q1.mul(q2);
589                 return q1;
590         }
591
592         public void insert(PipeControlPoint previous, PipeControlPoint next) {
593                 // inserting an offsetpoint is error, 
594                 if (isDualSub())
595                         throw new RuntimeException("Dual sub points cannot be inserted.");
596                 // size change control point cannot be inserted this way, because it ends PipeRun
597                 if (isSizeChange())
598                         throw new RuntimeException("Size change points cannot be inserted.");
599                 PipeRun piperun = previous.getPipeRun();
600                 // and just to make sure that control point structure is not corrupted
601                 if (getPipeRun() != null) {
602                         if (piperun != getPipeRun() || piperun != next.getPipeRun())
603                                 throw new RuntimeException("All controls points must be located on the same pipe run");
604                 } else {
605                         piperun.addChild(this);
606                 }
607
608                 // insert new BranchControlPoint between straight's control points
609                 PipeControlPoint previousNext = previous.getNext();
610                 PipeControlPoint previousPrevious = previous.getPrevious();
611
612                 PipeControlPoint offsetCP = null;
613                 if (isOffset()) {
614                         offsetCP = getDualSub();
615                 }
616                 if (previousNext != null && previousNext == next) {
617                         if (previous.isDualInline()) {
618                                 throw new RuntimeException();
619                         }
620                         if (next.isDualSub()) {
621                                 throw new RuntimeException();
622                         }
623                         previous.setNext(this);
624                         this.setPrevious(previous);
625                         if (previous.isDualSub()) {
626                                 previous.getParentPoint().setNext(this);
627                         }
628                         this.setNext(next);
629
630                         if (offsetCP == null) {
631                                 next.setPrevious(this);
632                         } else {
633                                 next.setPrevious(offsetCP);
634                                 offsetCP.setNext(next);
635                                 offsetCP.setPrevious(previous);
636                         }
637
638                         if (next.isDualInline()) {
639                                 next.getDualSub().setPrevious(this);
640                         }
641                 } else if (previousPrevious != null && previousPrevious == next) {
642                         // control point were given in reverse order 
643                         if (next.isDualInline())
644                                 throw new RuntimeException();
645                         if (previous.isDualSub())
646                                 throw new RuntimeException();
647
648                         this.setNext(previous);
649                         if (offsetCP == null) {
650                                 previous.setNext(this);
651                         } else {
652                                 previous.setPrevious(offsetCP);
653                                 offsetCP.setNext(previous);
654                                 offsetCP.setPrevious(next);
655                         }
656                         if (previous.isDualInline()) {
657                                 previous.getDualSub().setPrevious(this);
658                         }
659                         this.setPrevious(next);
660                         next.setNext(this);
661                         if (next.isDualSub()) {
662                                 next.getParentPoint().setNext(this);
663                         }
664
665                 } else {
666                         throw new RuntimeException();
667                 }       
668
669                 PipingRules.validate(piperun);
670         }
671
672
673
674         public void insert(PipeControlPoint pcp, Direction direction) {
675                 if (isDualSub())
676                         throw new RuntimeException();
677                 if (direction == Direction.NEXT) {
678                         // if direction is next, user must have given OffsetPoint
679                         if (pcp.isDualInline())
680                                 throw new RuntimeException();
681                         // basic next/prev links
682                         pcp.setNext(this);
683                         this.setPrevious(pcp);
684                         // and last take care of sizechange / offset points
685                         if (pcp.isDualSub()) {
686                                 pcp.getParentPoint().setNext(this);
687                         }
688                         if (isDualInline()) {
689                             getDualSub().setPrevious(this);
690                         }
691                 } else {
692                         // if direction is previous, user must have given sizechange
693                         if (pcp.isDualSub())
694                                 throw new RuntimeException();
695                         // previous direction is more complicated, since if newCP is SizeChangeControlPoint,
696                         // we must link pcp to newCP's OffsetPoint
697                         PipeControlPoint nocp = null;
698                         if (isDualInline()) {
699                                 nocp = getDualSub();
700                                 nocp.setNext(pcp);
701                         }
702                         if (nocp == null) {
703                                 pcp.setPrevious(this);
704                         } else {
705                                 pcp.setPrevious(nocp);
706                         }
707                         this.setNext(pcp);
708                         if (pcp.isDualInline()) {
709                                 PipeControlPoint ocp = pcp.getDualSub();
710                                 if (nocp == null)
711                                         ocp.setPrevious(this);
712                                 else
713                                         ocp.setPrevious(nocp);
714                         }
715
716                 }
717                 PipingRules.validate(getPipeRun());
718         }
719
720         public Vector3d getDirectedControlPointDirection() {
721                 assert (isDirected());
722                 Vector3d dir = new Vector3d();
723                 MathTools.rotate(getWorldOrientation(), new Vector3d(1.0, 0.0, 0.0), dir);
724                 dir.normalize();
725                 return dir;
726         }
727         
728         public Vector3d getDirection(Direction direction) {
729         if (isDirected())
730             return getDirectedControlPointDirection();
731         if (isTurn() && asFixedAngle()) {
732             if (direction == Direction.NEXT) {
733                 if (previous != null) {
734                     PipeControlPoint pcp = this;
735                     Vector3d dir = new Vector3d();
736                     dir.sub(pcp.getWorldPosition(),previous.getWorldPosition());
737                     if (dir.lengthSquared() > MathTools.NEAR_ZERO)
738                         dir.normalize();
739                     else
740                         return null;
741                     Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
742                     AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
743                     Quat4d q2 = MathTools.getQuat(aa);
744                     Vector3d v = new Vector3d(1.,0.,0.);
745                     Vector3d offset = new Vector3d();
746                     MathTools.rotate(q2, v, offset);
747                     MathTools.rotate(q, offset, dir);
748                     return dir;
749                 }
750             } else {
751                 if (next != null) {
752                     PipeControlPoint pcp = this;
753                     Vector3d dir = new Vector3d();
754                     dir.sub(next.getWorldPosition(),pcp.getWorldPosition());
755                     if (dir.lengthSquared() > MathTools.NEAR_ZERO)
756                         dir.normalize();
757                     else
758                         return null;
759                     Quat4d q = getControlPointOrientationQuat(dir, pcp.getRotationAngle() != null ? pcp.getRotationAngle() : 0.0);
760                     AxisAngle4d aa = new AxisAngle4d(MathTools.Y_AXIS,pcp.getTurnAngle() == null ? 0.0 : pcp.getTurnAngle());
761                     Quat4d q2 = MathTools.getQuat(aa);
762                     Vector3d v = new Vector3d(1.,0.,0.);
763                     Vector3d offset = new Vector3d();
764                     MathTools.rotate(q2, v, offset);
765                     MathTools.rotate(q, offset, dir);
766                     return dir;
767                 }
768             }
769         }
770         return null;
771     }
772
773         public Vector3d getPathLegDirection(Direction direction) {
774                 if (direction == Direction.NEXT) {
775                         if (next != null) {
776                                 PipeControlPoint pcp = this;
777                                 if (pcp.isDualInline()) {
778                                         pcp = pcp.getDualSub();
779                                 }
780                                 Vector3d v = new Vector3d();
781                                 v.sub(next.getWorldPosition(),pcp.getWorldPosition());
782                                 return v;
783                         } else {
784                                 if (previous == null) {
785                                         if (!isDirected())
786                                                 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
787                                         return getDirectedControlPointDirection();
788
789                                 } else {
790                                         if (isVariableAngle() && !asFixedAngle())
791                                                 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
792                                         if (isInline()) {
793                                                 PipeControlPoint pcp = this;
794                                                 if (pcp.isDualSub()) {
795                                                         pcp = pcp.getParentPoint();
796                                                 }
797                                                 Vector3d v = new Vector3d();
798                                                 v.sub(pcp.getWorldPosition(),previous.getWorldPosition());
799                                                 return v;
800                                         } else if (isDirected()) {
801                                                 return getDirectedControlPointDirection();
802                                         } else if (isEnd()) {
803                                                 Vector3d v = new Vector3d();
804                                                 v.sub(getWorldPosition(),previous.getWorldPosition());
805                                                 return v;
806                                         } else if (isTurn() && asFixedAngle() && !_getReversed()) {
807                                                 return getDirection(Direction.NEXT);
808                                         }
809                                         throw new RuntimeException("Missing implementation " + this);
810                                 }
811                         }
812                 } else {
813                         if (previous != null) {
814                                 PipeControlPoint pcp = this;
815                                 if (isDualSub()) 
816                                         pcp = getParentPoint();
817                                 Vector3d v = new Vector3d();
818                                 v.sub(previous.getWorldPosition(),pcp.getWorldPosition());
819                                 return v;
820                         } else {
821                                 if (next == null)  {
822                                         if (!isDirected())
823                                                 throw new RuntimeException("Cannot calculate path leg direction for unconnected control point " + this);
824                                         Vector3d v = getDirectedControlPointDirection();
825                                         v.negate();
826                                         return v;
827                                 } else {
828                                         if (isVariableAngle() && !asFixedAngle())
829                                                 throw new RuntimeException("Cannot calculate path leg direction for unconnected variable angle control point " + this);
830                                         if (isInline()) {
831                                                 PipeControlPoint pcp = this;
832                                                 if (pcp.isDualInline()) {
833                                                         pcp = pcp.getDualSub();
834                                                 }
835                                                 Vector3d v = new Vector3d();
836                                                 v.sub(pcp.getWorldPosition(),next.getWorldPosition());
837                                                 return v;
838                                         } else if (isDirected()) {
839                                                 Vector3d v = getDirectedControlPointDirection();
840                                                 v.negate();
841                                                 return v;
842                                         } else if (isEnd()) {
843                                                 Vector3d v = new Vector3d();
844                                                 v.sub(getWorldPosition(),next.getWorldPosition());
845                                                 return v;
846                                         } else if (isTurn() && asFixedAngle() && _getReversed()) {
847                                                 return getDirection(Direction.PREVIOUS);
848                                         }
849                                         throw new RuntimeException("Missing implementation " + this);
850                                 }
851                         }
852                 }
853         }
854
855         public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2) {
856                 assert (isInline());
857
858                 PipeControlPoint sub = isAxial() ? this : getDualSub();
859                 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
860                 Vector3d dir = sub.getPathLegDirection(Direction.NEXT);
861                 
862                 dir.normalize();
863                 dir.scale(length * 0.5);
864                 p1.set(pos);
865                 p2.set(pos2);
866                 p1.sub(dir);
867                 p2.add(dir);
868         }
869
870         public void getControlPointEnds(Tuple3d p1, Tuple3d p2) {
871                 PipeControlPoint sub = isAxial() || isDirected() || isTurn() ? this : getChildPoints().get(0);
872                 Vector3d pos = getWorldPosition(), pos2 = sub == this ? pos : sub.getWorldPosition();
873                 
874                 Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
875                 dir1.normalize();
876                 Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
877                 dir2.normalize();
878                 if (isInline()) {
879                         dir1.scale(length * 0.5);
880                         dir2.scale(length * 0.5);
881                 } else {
882                         dir1.scale(length);
883                         dir2.scale(length);
884                 }
885                 p1.set(pos);
886                 p2.set(pos2);
887                 p1.add(dir1);
888                 p2.add(dir2);
889         }
890
891         public void getEndDirections(Tuple3d v1, Tuple3d v2) {
892                 PipeControlPoint sub = isAxial() ? this : getDualSub();
893                 
894                 Vector3d dir1 = getPathLegDirection(Direction.PREVIOUS);
895                 dir1.normalize();
896                 Vector3d dir2 = sub.getPathLegDirection(Direction.NEXT);
897                 dir2.normalize();
898                 v1.set(dir1);
899                 v2.set(dir2);
900         }
901
902         public void getInlineControlPointEnds(Tuple3d p1, Tuple3d p2, Vector3d dir) {
903                 assert (isInline());
904
905                 Vector3d pos = getWorldPosition();
906                 dir.set(getPathLegDirection(Direction.NEXT));
907                 dir.normalize();
908                 dir.scale(length * 0.5);
909                 p1.set(pos);
910                 p2.set(pos);
911                 p1.sub(dir);
912                 p2.add(dir);
913         }
914
915         public void getInlineControlPointEnds(Tuple3d center, Tuple3d p1, Tuple3d p2, Vector3d dir) {
916                 assert (isInline());
917
918                 Vector3d pos = getWorldPosition();
919                 center.set(pos);
920                 dir.set(getPathLegDirection(Direction.NEXT));
921                 dir.normalize();
922                 dir.scale(length * 0.5);
923                 p1.set(pos);
924                 p2.set(pos);
925                 p1.sub(dir);
926                 p2.add(dir);
927         }
928
929         public double getInlineLength() {
930                 if (type == PointType.TURN)
931                         return length;
932                 else if (type == PointType.INLINE)
933                         return length * 0.5;
934                 return 0;
935         }
936
937         public Vector3d getRealPosition(PositionType type) {
938                 Vector3d pos = getWorldPosition();
939                 switch (type) {
940                 case NEXT: {
941                         Vector3d dir = getPathLegDirection(Direction.NEXT);
942                         double length = getInlineLength();
943                         dir.normalize();
944                         dir.scale(length);
945                         pos.add(dir);
946                         break;
947                 }
948                 case PREVIOUS: {
949                         Vector3d dir = getPathLegDirection(Direction.PREVIOUS);
950                         double length = getInlineLength();
951                         dir.normalize();
952                         dir.scale(length);
953                         pos.add(dir);
954                         break;
955                 }
956                 case PORT:
957                         // IEntity portDir = pcp.getSingleRelatedObject(ProcessResource.plant3Dresource.HasDirection);
958                         // TODO : how we calculated needed space for a port; does it has an offset from control point's position or not?
959                         break;
960                 case SPLIT:
961                         // do nothing
962                         break;
963
964                 }
965                 return pos;
966         }
967
968         public void getInlineMovement(Tuple3d start, Tuple3d end) {
969                 // FIXME : check type of neighbor components and allow movement on top of variable length components,
970                 //         find proper range for movement (pcp's position is not)
971                 PipeControlPoint p = previous.getPrevious();
972                 PipeControlPoint n = next.getNext();
973                 start.set(p.getWorldPosition());
974                 end.set(n.getWorldPosition());
975         }
976
977         public PipeControlPoint findNextEnd() {
978                 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
979                 return findNextEnd( t);
980         }
981
982         public PipeControlPoint findPreviousEnd() {
983                 ArrayList<PipeControlPoint> t = new ArrayList<PipeControlPoint>();
984                 return findPreviousEnd(t);
985         }
986
987         public PipeControlPoint findNextEnd(List<PipeControlPoint> nextList) {
988                 while (true) {
989                         PipeControlPoint pcp = null;
990                         PipeControlPoint p = null;
991                         if (nextList.size() == 0)
992                                 p = this;
993
994                         else
995                                 p = nextList.get(nextList.size() - 1);
996
997                         pcp = p.getNext();
998                         if (pcp == null) {
999                                 pcp = p;
1000                                 if (nextList.size() > 0)
1001                                         nextList.remove(nextList.size() - 1);
1002                                 //              if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1003                                 return pcp;
1004                                 //break;
1005                         }
1006                         if (pcp.isPathLegEnd()) {
1007                                 //if (DEBUG) System.out.println(" " + pcp.getResource());
1008                                 return pcp;
1009                         } else {
1010                                 nextList.add(pcp);
1011                                 // if (DEBUG) System.out.print(" " + pcp.getResource());
1012                         }
1013                 }
1014         }
1015
1016         public PipeControlPoint findPreviousEnd(List<PipeControlPoint> prevList) {
1017                 while (true) {
1018                         PipeControlPoint pcp = null;
1019                         PipeControlPoint p = null;
1020                         if (prevList.size() == 0)
1021                                 p = this;
1022
1023                         else
1024                                 p = prevList.get(prevList.size() - 1);
1025
1026                         pcp = p.getPrevious();
1027                         if (pcp == null) {
1028                                 pcp = p;
1029                                 if (prevList.size() > 0)
1030                                         prevList.remove(prevList.size() - 1);
1031                                 //                              if (DEBUG) System.out.println(" " + pcp.getResource() + " not full");
1032                                 return pcp;
1033                         }
1034                         if (pcp.isPathLegEnd()) {
1035                                 //                              if (DEBUG)      System.out.println(" " + pcp.getResource());
1036                                 return pcp;
1037                         } else {
1038                                 prevList.add(pcp);
1039                                 //                              if (DEBUG)System.out.print(" " + pcp.getResource());
1040                         }
1041                 }
1042         }
1043         
1044         public void _remove() {
1045             _remove(true);
1046         }
1047         
1048         
1049         public PipeControlPoint getDualSub() {
1050             if (isDualInline())
1051                 return getChildPoints().get(0);
1052             else
1053                 throw new IllegalStateException("Current control point is not dual inline");
1054         }
1055         
1056
1057         public void _remove(boolean renconnect) {
1058                 if (component == null && next == null && previous == null)
1059                         return;
1060                 if (DEBUG) System.out.println(this + " Remove " + renconnect);
1061
1062                 if (getParentPoint() != null) {
1063                     getParentPoint()._remove(renconnect);
1064                     return;
1065                 }
1066                 PipeRun pipeRun = getPipeRun();
1067                 if (pipeRun == null)
1068                         return;
1069
1070                 PipeControlPoint additionalRemove = null;
1071                 if (!PipingRules.isEnabled()) {
1072                     component = null;
1073                         setPrevious(null);
1074                         setNext(null);
1075                 } else {
1076
1077                         PipeControlPoint currentPrev = previous;
1078                         PipeControlPoint currentNext = next;
1079                         if (currentNext == null && currentPrev == null) {
1080                                 removeComponent();
1081                                 pipeRun.remChild(this);
1082                                 checkRemove(pipeRun);
1083                                 return;
1084                         }
1085                         if (currentNext != null && currentPrev != null) {
1086                                 boolean link = renconnect;
1087                                 if (currentNext.isBranchEnd()) {
1088                                         link = false;
1089                                         currentNext.remove();
1090                                         currentNext = null;
1091                                         setNext(null);
1092                                 }
1093                                 if (currentPrev.isBranchEnd()) {
1094                                         link = false;
1095                                         currentPrev.remove();
1096                                         currentPrev = null;
1097                                         setPrevious(null);
1098                                 }
1099                                 if (link) {
1100                                     if (currentPrev.isDirected() && currentNext.isDirected())
1101                                         link = false;
1102                                     else if (this.isDualInline()) {
1103                                         link = false;
1104                                     } else if (this.isDualSub()) {
1105                                         throw new RuntimeException("_remove() is called for parent point, somehow got to child point. " + this);
1106                                     }
1107                                 }
1108                                 if (currentNext == null) {
1109                                         // Nothing to do
1110                                 } else if (currentNext.isDualInline()) {
1111                                         PipeControlPoint sccp = currentNext;
1112                                         PipeControlPoint ocp = currentNext.getDualSub();
1113                                         if (ocp == null) {
1114                                                 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1115                                         }
1116                                         if (link) {
1117                                                 sccp.setPrevious(currentPrev);
1118                                                 //ocp.setPrevious(currentPrev);
1119                                                 assert(ocp.getPrevious() == currentPrev);
1120                                         } else {
1121                                                 sccp.setPrevious(null);
1122                                                 //ocp.setPrevious(null);
1123                                                 assert(ocp.getPrevious() == null);
1124                                         }
1125                                         setNext(null);
1126                                 } else if (currentNext.isDualSub()) {
1127                                         throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1128                                 } else if (currentNext.previous == this) {
1129                                         if (link) {
1130                                                 currentNext.setPrevious(currentPrev);
1131                                         } else {
1132                                                 currentNext.setPrevious(null);
1133                                         }
1134                                         setNext(null);
1135                                 } else if (isDualInline()) {
1136                                     if (currentNext.previous != getDualSub()) {
1137                                         throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1138                                     }
1139                                     if (link) {
1140                         currentNext.setPrevious(currentPrev);
1141                     } else {
1142                         currentNext.setPrevious(null);
1143                     }
1144                     setNext(null);
1145                                 } else {
1146                                         throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1147                                 }
1148                                 if (currentPrev == null) {
1149                                         // Nothing to do
1150                                 } else if (currentPrev.isDualInline()) {
1151                                         throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1152                                 } else if (currentPrev.isDualSub()) {
1153                                         PipeControlPoint ocp = currentPrev;
1154                                         PipeControlPoint sccp = currentPrev.getParentPoint();
1155                                         if (sccp == null)
1156                                                 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1157                                         if (link) {
1158                                                 //ocp.setNext(currentNext);
1159                                                 sccp.setNext(currentNext);
1160                                                 assert(ocp.getNext() == currentNext);
1161                                         } else {
1162                                                 //ocp.setNext(null);
1163                                                 sccp.setNext(null);
1164                                                 assert(ocp.getNext() == null);
1165                                         }
1166                                         setPrevious(null);
1167                                 } else if (currentPrev.next == this) {
1168                                         if (link)
1169                                                 currentPrev.setNext(currentNext);
1170                                         else
1171                                                 currentPrev.setNext(null);
1172
1173                                         setPrevious(null);
1174                                 } else {
1175                                         throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged");
1176                                 }
1177                                 if (link) {
1178                                         if (currentNext.isVariableLength() && currentPrev.isVariableLength()) {
1179                                                 // we have to join them into single variable length component.
1180                                                 additionalRemove = currentPrev;
1181                                                 // combine lengths and set the location of remaining control point to the center.
1182                                                 Point3d ps = new Point3d();
1183                                                 Point3d pe = new Point3d();
1184                                                 Point3d ns = new Point3d();
1185                                                 Point3d ne = new Point3d();
1186                                                 currentPrev.getInlineControlPointEnds(ps, pe);
1187                                                 currentNext.getInlineControlPointEnds(ns, ne);
1188                                                 double l = currentPrev.getLength() + currentNext.getLength();
1189                                                 Vector3d cp = new Vector3d();
1190                                                 cp.add(ps, ne);
1191                                                 cp.scale(0.5);
1192                                                 currentNext.setLength(l);
1193                                                 currentNext.setWorldPosition(cp);
1194                                         }
1195                                 } else {
1196                                         // FIXME : pipe run must be split into two parts, since the control point structure is no more continuous. 
1197                                 }
1198                         } else if (currentNext != null) {
1199                                 if (currentNext.isDualInline()) {
1200                                         PipeControlPoint sccp = currentNext;
1201                                         PipeControlPoint ocp = getDualSub();
1202                                         if (ocp == null) {
1203                                                 throw new RuntimeException("Removing PipeControlPoint " + this+ " structure damaged, no offset control point");
1204                                         }
1205                                         sccp.setPrevious(null);
1206                                         assert(ocp.getPrevious() == null);
1207                                         //ocp.setPrevious(null);
1208                                 } else if (currentNext.isDualSub()) {
1209                                         throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, next control point is offset control point");
1210                                 } else if (currentNext.previous == this) {
1211                                     currentNext.setPrevious(null);
1212                                 }  else if (isDualInline()) {
1213                     if (currentNext.previous != getDualSub()) {
1214                         throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1215                     }
1216                     currentNext.setPrevious(null);
1217                                 } else {
1218                                         throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1219                                 }
1220                                 setNext(null);
1221                         } else {  //(previous != null)
1222                                 if(currentPrev.isDualInline()) {
1223                                         throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, previous control point is size change control point");
1224                                 } else if (currentPrev.isDualSub()) {
1225                                         PipeControlPoint ocp = currentPrev;
1226                                         PipeControlPoint sccp = currentPrev.getParentPoint();
1227                                         if (sccp == null) {
1228                                                 throw new RuntimeException("Removing PipeControlPoint " + this + " structure damaged, no size change control point");
1229                                         }
1230                                         sccp.setNext(null);
1231                                         assert(ocp.getNext() == null);
1232                                 } else if (currentPrev.next == this) {
1233                                     currentPrev.setNext(null);
1234                                 } else {
1235                                         throw new RuntimeException("Removing PipeControlPoint "+ this+ " structure damaged");
1236                                 }
1237                                 setPrevious(null);
1238                         }
1239                         if (children.size() > 0 ) {
1240                                 removeSubPoints();
1241                         } else if (parent!= null) {
1242                                 removeParentPoint();
1243                         }
1244
1245                 }
1246
1247                 removeComponent();
1248                 pipeRun.remChild(this);
1249                 checkRemove(pipeRun);
1250                 if (PipingRules.isEnabled() && pipeRun.getParent() != null && pipeRun.getControlPoints().size() > 0)
1251                         PipingRules.validate(pipeRun);
1252                 if (additionalRemove != null)
1253                         additionalRemove.remove();
1254         }
1255
1256         /**
1257          * Removes control point and attempts to reconnect next/prev
1258          * 
1259          * If this point is size change (PipeRuns are different on both sides), then reconnection cannot be made.
1260          */
1261         public void remove() {
1262                 PipeControlPoint currentPrev = previous;
1263                 PipeControlPoint currentNext = next;
1264                 _remove();
1265                 try {
1266                         if (currentNext != null)
1267                             if (!currentNext.checkRemove())
1268                                 PipingRules.requestUpdate(currentNext);
1269                         if (currentPrev != null)
1270                             if (!currentPrev.checkRemove())
1271                                 PipingRules.requestUpdate(currentPrev);
1272                 } catch (Exception e) {
1273                         e.printStackTrace();
1274                 }
1275         }
1276         
1277         
1278         /**
1279          * Removes control point without attempting to reconnect next/prev.
1280          * This usually leads to creation of another PipeRun for the control points after this point. 
1281          */
1282         public void removeAndSplit() {
1283         PipeControlPoint currentPrev = previous;
1284         PipeControlPoint currentNext = next;
1285         
1286         if (next != null && previous != null) {
1287             P3DRootNode root = (P3DRootNode)getPipelineComponent().getRootNode();
1288             PipeRun nextPipeRun = new PipeRun();
1289             nextPipeRun.setName(root.getUniqueName("PipeRun"));
1290             root.addChild(nextPipeRun);
1291             
1292             PipeRun previousRun = previous.getPipeRun();
1293             nextPipeRun.setPipeDiameter(previousRun.getPipeDiameter());
1294             nextPipeRun.setTurnRadiusArray(previousRun.getTurnRadiusArray());
1295             
1296             PipelineComponent n = next.getPipelineComponent();
1297             while (n != null) {
1298                 if (! (n instanceof Nozzle)) {
1299                     n.deattach();
1300                     nextPipeRun.addChild(n);
1301                 } else
1302                     n.setPipeRun(nextPipeRun);
1303                 n = n.getNext();
1304             }
1305         }
1306         _remove(false);
1307         try {
1308             if (currentNext != null)
1309                 if (!currentNext.checkRemove())
1310                     PipingRules.requestUpdate(currentNext);
1311             if (currentPrev != null)
1312                 if (!currentPrev.checkRemove())
1313                     PipingRules.requestUpdate(currentPrev);
1314         } catch (Exception e) {
1315             e.printStackTrace();
1316         }
1317     }
1318         
1319         /**
1320          * This is called when adjacent control point is removed.
1321          * 
1322          * This call should remove the give point, if the point cannot exist alone. 
1323          * At the moment there is one such case: branch.
1324          * 
1325          * @return
1326          */
1327         protected boolean checkRemove() {
1328             if (getParentPoint() != null) {
1329                 return getParentPoint().checkRemove();
1330             } else {
1331                 if (getPipelineComponent() == null)
1332                     return true; // already removed
1333             if (getPipelineComponent().getType().equals("Plant3D.URIs.Builtin_BranchSplitComponent")) {
1334                 if (getChildPoints().get(0).getNext() == null && getChildPoints().get(0).getPrevious() == null) {
1335                         remove();
1336                         return true;
1337                 }
1338             }
1339             return checkRemove(getPipeRun());
1340             }
1341     }
1342
1343         private boolean checkRemove(PipeRun pipeRun) {
1344                 Collection<PipeControlPoint> points = pipeRun.getControlPoints();
1345                 if (points.size() == 0) {
1346                         pipeRun.remove();
1347                         return true;
1348                 } else if (points.size() == 1) {
1349                         PipeControlPoint pcp = points.iterator().next();
1350                         if (pcp.isDeletable() && pcp.getNext() == null && pcp.getPrevious() == null) {
1351                                 pcp._remove(); // This call will recursively call also this method...
1352                                 return true;
1353                         }
1354                 } else if (points.size() == 2) {
1355                     
1356                 }
1357                 return false;
1358         }
1359
1360         private void removeSubPoints() {
1361                 for (PipeControlPoint p : children) {
1362                         p.parent = null;
1363                         p.component = null;
1364                         //p._remove();
1365                         PipeControlPoint currentNext = p.getNext();
1366                         PipeControlPoint currentPrev = p.getPrevious();
1367                         p._setNext(null);
1368                         p._setPrevious(null);
1369                         PipeRun run = p.getPipeRun();
1370                         if (run != null) {
1371                             run.remChild(p);
1372                             checkRemove(run);
1373                         }
1374                         if (currentNext != null)
1375                 if (!currentNext.checkRemove())
1376                     PipingRules.requestUpdate(currentNext);
1377             if (currentPrev != null)
1378                 if (!currentPrev.checkRemove())
1379                     PipingRules.requestUpdate(currentPrev);
1380             
1381                 }
1382                 children.clear();
1383         }
1384
1385         private void removeParentPoint() {
1386                 throw new RuntimeException("Child points cannot be removed directly");
1387         }
1388         
1389         public boolean isRemoved() {
1390             return component == null;
1391         }
1392
1393         private void removeComponent() {
1394                 if (component == null)
1395                         return;
1396                 PipelineComponent next = component.getNext();
1397                 PipelineComponent prev = component.getPrevious();
1398                 PipelineComponent br0 = component.getBranch0();
1399                 component.setNext(null);
1400                 component.setPrevious(null);
1401                 component.setBranch0(null);
1402                 if (next != null) {
1403                         if (next.getNext() == component)
1404                                 next.setNext(null);
1405                         else if (next.getPrevious() == component)
1406                                 next.setPrevious(null);
1407                         else if (next.getBranch0() == component)
1408                                 next.setBranch0(null);
1409                 }
1410                 if (prev != null) {
1411                         if (prev.getNext() == component)
1412                                 prev.setNext(null);
1413                         else if (prev.getPrevious() == component)
1414                                 prev.setPrevious(null);
1415                         else if (prev.getBranch0() == component)
1416                                 prev.setBranch0(null);
1417                 }
1418                 if (br0 != null) {
1419                         if (br0.getNext() == component)
1420                                 br0.setNext(null);
1421                         else if (br0.getPrevious() == component)
1422                                 br0.setPrevious(null);
1423                         else if (br0.getBranch0() == component)
1424                                 br0.setBranch0(null);
1425                 }
1426                 PipelineComponent comp = component;
1427                 component = null;
1428
1429                 comp.remove();
1430         }
1431
1432         @Override
1433         public void setOrientation(Quat4d orientation) {
1434                 if (MathTools.equals(orientation, getOrientation()))
1435                         return;
1436                 super.setOrientation(orientation);
1437                 if (getParentPoint() == null && component != null)
1438                         component._setWorldOrientation(getWorldOrientation());
1439                 updateSubPoint();
1440         }
1441
1442         @Override
1443         public void setPosition(Vector3d position) {
1444                 if (MathTools.equals(position, getPosition()))
1445                         return;
1446                 if (Double.isNaN(position.x) || Double.isNaN(position.y) || Double.isNaN(position.z))
1447                         throw new IllegalArgumentException("NaN is not supported");
1448                 super.setPosition(position);
1449                 if (getParentPoint() == null && component != null)
1450                         component._setWorldPosition(getWorldPosition());
1451                 updateSubPoint();
1452         }
1453
1454         private void updateSubPoint() {
1455                 if (isOffset()) {
1456                         if (next == null && previous == null) {
1457                                 for (PipeControlPoint sub : getChildPoints()) {
1458                                         sub.setWorldPosition(getWorldPosition());
1459                                         sub.setWorldOrientation(getWorldOrientation());
1460                                 }
1461                                 return;
1462                         }
1463                         for (PipeControlPoint sub : getChildPoints()) {
1464                                 Vector3d wp = getWorldPosition();
1465                                 wp.add(getSizeChangeOffsetVector());
1466                                 sub.setWorldPosition(wp);
1467                                 sub.setWorldOrientation(getWorldOrientation());
1468                         }
1469                 } else {
1470                         for (PipeControlPoint sub : getChildPoints()) {
1471                                 sub.setWorldPosition(getWorldPosition());
1472                                 sub.setWorldOrientation(getWorldOrientation());
1473                         }
1474                 }
1475         }
1476
1477
1478         public void _setWorldPosition(Vector3d position) {
1479                 Vector3d localPos = getLocalPosition(position);
1480                 super.setPosition(localPos);
1481                 updateSubPoint();
1482         }
1483
1484         public void _setWorldOrientation(Quat4d orientation) {
1485                 Quat4d localOr = getLocalOrientation(orientation);
1486                 super.setOrientation(localOr);
1487                 updateSubPoint();
1488         }
1489
1490         @Override
1491         public String toString() {
1492                 return getClass().getName() + "@" + Integer.toHexString(hashCode());
1493         }
1494
1495 }