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