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