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