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