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