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