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