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