]> gerrit.simantics Code Review - simantics/3d.git/blob - org.simantics.plant3d/src/org/simantics/plant3d/scenegraph/controlpoint/PipingRules.java
Handle variable length free end updates properly
[simantics/3d.git] / org.simantics.plant3d / src / org / simantics / plant3d / scenegraph / controlpoint / PipingRules.java
1 package org.simantics.plant3d.scenegraph.controlpoint;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8
9 import javax.vecmath.Point3d;
10 import javax.vecmath.Quat4d;
11 import javax.vecmath.Vector3d;
12
13 import org.simantics.g3d.math.MathTools;
14 import org.simantics.plant3d.scenegraph.InlineComponent;
15 import org.simantics.plant3d.scenegraph.Nozzle;
16 import org.simantics.plant3d.scenegraph.P3DRootNode;
17 import org.simantics.plant3d.scenegraph.PipeRun;
18 import org.simantics.plant3d.scenegraph.PipelineComponent;
19 import org.simantics.plant3d.scenegraph.TurnComponent;
20 import org.simantics.plant3d.scenegraph.controlpoint.PipeControlPoint.Direction;
21 import org.simantics.plant3d.utils.ComponentUtils;
22 import org.simantics.utils.ui.ErrorLogger;
23
24 public class PipingRules {
25         private static final boolean DEBUG = false;
26         private static final boolean DUMMY = false;
27
28         private static double MIN_TURN_ANGLE = 0.001; // Threshold for removing turn components.
29         private static double ALLOWED_OFFSET = 0.001; // Allowed offset for directed path legs
30
31         private static final int REMOVE_NONE = 0;
32         private static final int REMOVE_START = 1;
33         private static final int REMOVE_END = 2;
34         private static final int REMOVE_BOTH = 3;
35         
36
37         private enum PathLegUpdateType {
38                 NONE, PREV, NEXT, PREV_S, NEXT_S
39         };
40         
41         private static boolean enabled = true;
42         private static boolean updating = false;
43         private static boolean allowInsertRemove = true;
44         private static boolean triedIR = false;
45
46         
47         private static List<PipeControlPoint> requestUpdates = new ArrayList<PipeControlPoint>();
48         private static List<PipeControlPoint> currentUpdates = new ArrayList<PipeControlPoint>();
49         
50         private static Object updateMutex = new Object();
51         private static Object ruleMutex = new Object();
52         
53         public static void requestUpdate(PipeControlPoint pcp) {
54                 if (DEBUG) System.out.println("PipingRules request " + pcp);
55                 synchronized (updateMutex) {
56                     if (!requestUpdates.contains(pcp))
57                         requestUpdates.add(pcp);
58                 }
59         }
60         
61         public static boolean update() throws Exception {
62                 if (requestUpdates.size() == 0)
63                         return false;
64                 
65                 List<PipeControlPoint> temp = new ArrayList<PipeControlPoint>(requestUpdates.size());
66                 synchronized(updateMutex) {
67                     temp.addAll(requestUpdates);
68                 requestUpdates.clear();
69                 }
70                 synchronized (ruleMutex) {
71                     currentUpdates.clear();
72                     currentUpdates.addAll(temp);
73                     // TODO : we should remove already processed control points from currentUpdates after each _positionUpdate call.
74                     for (PipeControlPoint pcp : currentUpdates)
75                     _positionUpdate(pcp, true);
76                     currentUpdates.clear();
77         }
78                 synchronized(updateMutex) {
79                     requestUpdates.removeAll(temp);
80                 }
81                 
82                 return true;
83         }
84         
85         public static boolean positionUpdate(PipeControlPoint pcp) throws Exception {
86                 
87                 return positionUpdate(pcp, true);
88         }
89         
90         public static boolean positionUpdate(PipeControlPoint pcp, boolean allowIR) throws Exception {
91             synchronized (ruleMutex) {
92             currentUpdates.add(pcp);
93             boolean b = _positionUpdate(pcp, allowIR);
94             currentUpdates.clear();
95             return b;
96             }
97             
98         }
99         
100         private static boolean _positionUpdate(PipeControlPoint pcp, boolean allowIR) throws Exception {
101                 if (updating || !enabled)
102                         return true;
103                 if (pcp.getPipeRun() == null)
104                         return false;
105                 try {
106                         if (DEBUG) System.out.println("PipingRules " + pcp);
107                         updating = true;
108                         allowInsertRemove = allowIR;
109                         triedIR = false;
110                         validate(pcp.getPipeRun());
111                         if (pcp.isPathLegEnd()) {
112                                 updatePathLegEndControlPoint(pcp); // FIXME: Rules won't work properly, if they are not run twice.
113                                 updatePathLegEndControlPoint(pcp);
114                         } else {
115                                 updateInlineControlPoint(pcp);
116                                 updateInlineControlPoint(pcp);
117                         }
118                         validate(pcp.getPipeRun());
119                         if (!allowInsertRemove)
120                                 return !triedIR;
121                         return true;
122                 } finally {
123                         updating = false;
124 //                      System.out.println("PipingRules done " + pcp);
125                 }
126         }
127         
128         public static void setEnabled(boolean enabled) {
129                 PipingRules.enabled = enabled;
130                 if(!enabled)
131                         currentUpdates.clear();
132         }
133         
134         public static boolean isEnabled() {
135                 return enabled;
136         }
137         
138 //      private void commit() {
139 //              root.getNodeMap().commit();
140 //      }
141
142         public static class ExpandIterInfo {
143                 // these two are turn control points
144                 private PipeControlPoint start;
145                 private PipeControlPoint end;
146                 private int type;
147
148                 public ExpandIterInfo() {
149
150                 }
151
152                 public ExpandIterInfo(PipeControlPoint tcp, int type) {
153                         if (type == REMOVE_START)
154                                 start = tcp;
155                         else
156                                 end = tcp;
157                         this.type = type;
158                 }
159
160                 public ExpandIterInfo(PipeControlPoint start, PipeControlPoint end) {
161                         this.start = start;
162                         this.end = end;
163                         this.type = REMOVE_BOTH;
164                 }
165
166                 public PipeControlPoint getEnd() {
167                         return end;
168                 }
169
170                 public void setEnd(PipeControlPoint end) {
171                         this.end = end;
172                 }
173
174                 public PipeControlPoint getStart() {
175                         return start;
176                 }
177
178                 public void setStart(PipeControlPoint start) {
179                         this.start = start;
180                 }
181
182                 public int getType() {
183                         return type;
184                 }
185
186                 public void setType(int type) {
187                         this.type = type;
188                 }
189
190         }
191
192         private static void updatePathLegEndControlPoint(PipeControlPoint pcp) throws Exception {
193                 if (DEBUG)
194                         System.out.println("PipingRules.updatePathLegEndControlPoint() " + pcp);
195                 if (pcp.getNext() != null) {
196                         updatePathLegNext(pcp, pcp, PathLegUpdateType.NEXT_S);
197                 }
198                 if (pcp.getPrevious() != null) {
199                         updatePathLegPrev(pcp, pcp, PathLegUpdateType.PREV_S);
200                 }
201
202         }
203
204         private static void updateInlineControlPoint(PipeControlPoint pcp) throws Exception {
205                 if (DEBUG)
206                         System.out.println("PipingRules.updateInlineControlPoint() " + pcp);
207                 PipeControlPoint start = pcp.findPreviousEnd();
208                 updatePathLegNext(start, pcp, PathLegUpdateType.NONE);
209         }
210
211         private static PipeControlPoint insertElbow(PipeControlPoint pcp1, PipeControlPoint pcp2, Vector3d pos) throws Exception{
212                 if (DEBUG)
213                         System.out.println("PipingRules.insertElbow() " + pcp1 + " " + pcp2 + " " + pos);
214                 if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) {
215                         
216                 } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getSubPoint().get(0)) {
217                         pcp1 = pcp1.getSubPoint().get(0);       
218                 } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) {
219                         PipeControlPoint t = pcp1;
220                         pcp1 = pcp2;
221                         pcp2 = t;
222                 } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getSubPoint().get(0) && pcp2.getNext() == pcp1) {
223                         PipeControlPoint t = pcp1;
224                         pcp1 = pcp2.getSubPoint().get(0);
225                         pcp2 = t;
226                 } else {
227                         throw new RuntimeException();
228                 }
229                 TurnComponent elbow = ComponentUtils.createTurn((P3DRootNode)pcp1.getRootNode());
230                 PipeControlPoint pcp = elbow.getControlPoint();
231                 if (pcp1.isDualInline())
232                         pcp1 = pcp1.getSubPoint().get(0);
233                 String name = pcp1.getPipeRun().getUniqueName("Elbow");
234                 elbow.setName(name);
235                 pcp1.getPipeRun().addChild(elbow);
236
237                 pcp.insert(pcp1, pcp2);
238
239                 pcp.setWorldPosition(pos);
240                 validate(pcp.getPipeRun());
241                 return pcp;
242         }
243         
244         private static PipeControlPoint insertStraight(PipeControlPoint pcp1, PipeControlPoint pcp2, Vector3d pos, double length) throws Exception {
245                 if (DEBUG)
246                         System.out.println("PipingRules.insertStraight() " + pcp1 + " " + pcp2 + " " + pos);
247                 if (pcp1.getNext() == pcp2 && pcp2.getPrevious() == pcp1) {
248                         
249                 } else if (pcp1.getNext() == pcp2 && pcp1.isDualInline() && pcp2.getPrevious() == pcp1.getSubPoint().get(0)) {
250                         pcp1 = pcp1.getSubPoint().get(0);       
251                 } else if (pcp1.getPrevious() == pcp2 && pcp2.getNext() == pcp1) {
252                         PipeControlPoint t = pcp1;
253                         pcp1 = pcp2;
254                         pcp2 = t;
255                 } else if (pcp2.isDualInline() && pcp1.getPrevious() == pcp2.getSubPoint().get(0) && pcp2.getNext() == pcp1) {
256                         PipeControlPoint t = pcp1;
257                         pcp1 = pcp2.getSubPoint().get(0);
258                         pcp2 = t;
259                 } else {
260                         throw new RuntimeException();
261                 }
262                 InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp1.getRootNode());
263                 PipeControlPoint scp = component.getControlPoint();
264                 if (pcp1.isDualInline())
265                         pcp1 = pcp1.getSubPoint().get(0);
266                 String name = pcp1.getPipeRun().getUniqueName("Pipe");
267                 component.setName(name);
268                 pcp1.getPipeRun().addChild(component);
269                 
270                 scp.insert(pcp1, pcp2);
271
272                 scp.setWorldPosition(pos);
273                 scp.setLength(length);
274                 validate(scp.getPipeRun());
275                 return scp;
276         }
277         
278         private static PipeControlPoint insertStraight(PipeControlPoint pcp, Direction direction , Vector3d pos, double length) throws Exception {
279                 if (DEBUG)
280                         System.out.println("PipingRules.insertStraight() " + pcp + " " + direction + " " + pos);
281                 
282                 InlineComponent component = ComponentUtils.createStraight((P3DRootNode)pcp.getRootNode());
283                 PipeControlPoint scp = component.getControlPoint();
284                 if (pcp.isDualInline() && direction == Direction.NEXT)
285                         pcp = pcp.getSubPoint().get(0);
286                 String name = pcp.getPipeRun().getUniqueName("Pipe");
287                 component.setName(name);
288                 pcp.getPipeRun().addChild(component);
289                 
290                 scp.insert(pcp,direction);
291
292                 scp.setWorldPosition(pos);
293                 scp.setLength(length);
294                 validate(scp.getPipeRun());
295                 return scp;
296         }
297
298         private static void updatePathLegNext(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
299                 ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
300                 PipeControlPoint end = start.findNextEnd(list);
301                 // this is for inline cp that is also path leg end
302                 if (lengthChange == PathLegUpdateType.NONE) {
303                         if (start.equals(updated))
304                                 lengthChange = PathLegUpdateType.NEXT;
305                         else if (end.equals(updated))
306                                 lengthChange = PathLegUpdateType.PREV;
307                 }
308                 updatePathLegNext(start, list, end, updated, lengthChange);
309         }
310
311         private static void updatePathLegNext(PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
312                 updatePathLeg(start, list, end, false, 0, new ArrayList<ExpandIterInfo>(), updated, lengthChange);
313         }
314         
315         private static void updatePathLegPrev(PipeControlPoint start, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
316                 ArrayList<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
317                 PipeControlPoint end = start.findPreviousEnd(list);
318                 // TODO: this method is not symmetric with updatePathLegNext, which may alter lengthChange parameter?
319                 updatePathLegPrev(start, list, end, updated, lengthChange);
320         }
321
322         private static void updatePathLegPrev(PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
323                 // reverses the list
324                 ArrayList<PipeControlPoint> nextList = new ArrayList<PipeControlPoint>();
325                 for (PipeControlPoint icp : list) {
326                         if (icp.isDualSub()) {
327                                 nextList.add(0, icp.getParentPoint());
328                         } else {
329                                 nextList.add(0, icp);
330                         }
331                 }
332                 updatePathLeg(end, nextList, start, true, 0, new ArrayList<ExpandIterInfo>(), updated, lengthChange);
333
334         }
335
336         private static class UpdateStruct2 {
337                 public PipeControlPoint start;
338                 public Vector3d startPoint;
339                 public ArrayList<PipeControlPoint> list;
340                 public PipeControlPoint end;
341                 public Vector3d endPoint;
342                 public Vector3d dir;
343                 public Vector3d offset;
344                 public boolean hasOffsets;
345                 public int iter;
346                 public boolean reversed;
347                 public ArrayList<ExpandIterInfo> toRemove;
348                 public PipeControlPoint updated;
349
350                 public UpdateStruct2(PipeControlPoint start, Vector3d startPoint, ArrayList<PipeControlPoint> list, PipeControlPoint end, Vector3d endPoint, Vector3d dir, Vector3d offset, boolean hasOffsets, int iter, boolean reversed, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated) {
351                         if (start == null || end == null)
352                                 throw new NullPointerException();
353                         this.start = start;
354                         this.startPoint = startPoint;
355                         this.list = list;
356                         this.end = end;
357                         this.endPoint = endPoint;
358                         this.dir = dir;
359                         this.offset = offset;
360                         this.hasOffsets = hasOffsets;
361                         this.iter = iter;
362                         this.reversed = reversed;
363                         this.toRemove = toRemove;
364                         this.updated = updated;
365                         
366                         if (!MathTools.isValid(startPoint) || 
367                                 !MathTools.isValid(endPoint) || 
368                                 !MathTools.isValid(dir)) {
369                                 throw new RuntimeException();
370                         }
371                 }
372
373                 public String toString() {
374                         return start + " " + end+ " " + dir + " " + hasOffsets + " " + offset + " " + iter + " " + toRemove.size();
375                 }
376
377         }
378
379         private static boolean calculateOffset(Vector3d startPoint, Vector3d endPoint, ArrayList<PipeControlPoint> list, Vector3d dir, Vector3d offset) {
380                 boolean hasOffsets = false;
381                 List<PipeControlPoint> offsets = new ArrayList<PipeControlPoint>(list.size());
382                 for (PipeControlPoint icp : list) {
383                         if (icp.isOffset()) {
384                                 offsets.add(icp);
385                         } else if (icp.isDualSub())
386                                 ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
387                 }
388                 if (offsets.size() == 0) {
389                         dir.set(endPoint);
390                         dir.sub(startPoint);
391                         double l = dir.lengthSquared(); 
392                         if (l > MathTools.NEAR_ZERO)
393                                 dir.scale(1.0/Math.sqrt(l));
394                         offset.set(0.0, 0.0, 0.0);
395                         return false;
396                 } else {
397                         Vector3d sp = new Vector3d(startPoint);
398                         Point3d ep = new Point3d(endPoint);
399                         dir.set(ep);
400                         dir.sub(sp);
401                         double l = dir.lengthSquared(); 
402                         if (l > MathTools.NEAR_ZERO)
403                                 dir.scale(1.0/Math.sqrt(l));
404                         int iter = 100;
405                         while (iter >= 0) {
406                                 iter--;
407                                 offset.set(0.0, 0.0, 0.0);
408                                 
409                                 for (PipeControlPoint icp : offsets) {
410                                         Vector3d v = icp.getSizeChangeOffsetVector(dir);
411                                         offset.add(v);
412                                 }
413                                 Point3d nep = new Point3d(endPoint);
414                                 nep.sub(offset);
415                                 if (nep.distance(ep) < 0.0000000001) {
416                                         break;
417                                 } 
418                                 ep = nep;
419                                 dir.set(ep);
420                                 dir.sub(sp);
421                                 l = dir.lengthSquared(); 
422                                 if (l > MathTools.NEAR_ZERO)
423                                         dir.scale(1.0/Math.sqrt(l));
424                         }
425                         hasOffsets = true;
426                 }
427                 
428 //              for (PipeControlPoint icp : list) {
429 //                      if (icp.isOffset()) {
430 //                              icp.setOffset(((InlineComponent)icp.getPipelineComponent()).getOffset());
431 //                              hasOffsets = true;
432 //                              Vector3d v = icp.getSizeChangeOffsetVector(dir);
433 //                              offset.add(v);
434 //                      } else if (icp.isDualSub())
435 //                              ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
436 //              }
437                 if (DEBUG && hasOffsets)
438                         System.out.println("calcOffset s:"+ startPoint + " e:" + endPoint + " d:" + dir + " o:"+offset) ;
439                 return hasOffsets;
440         }
441
442         /**
443          * @param start
444          *            starting point of the pipe run
445          * @param list
446          *            list of inline control points in the pipe run
447          * @param end
448          *            ending point of the pipe run
449          * @param reversed
450          *            boolean flag indicating wether start or end control point was
451          *            modified (if true then end point was modified)
452          * @throws TransactionException
453          */
454         private static void updatePathLeg(PipeControlPoint start, ArrayList<PipeControlPoint> list, PipeControlPoint end, boolean reversed, int iter, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated, PathLegUpdateType lengthChange) throws Exception {
455                 if (start == end)
456                         return;
457                 // FIXME: direction is calculated wrong way!
458                 boolean hasOffsets = false;
459                 Vector3d offset = new Vector3d();
460                 Vector3d startPoint = start.getWorldPosition();
461                 Vector3d endPoint = end.getWorldPosition();
462                 Vector3d dir = new Vector3d();
463                 hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset);
464                 updatePathLeg(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, offset, hasOffsets, iter, reversed, toRemove, updated), lengthChange);
465
466         }
467         
468         private static boolean asDirected(PipeControlPoint pcp, Direction direction) {
469             if (pcp.isDirected())
470                 return true;
471             if (pcp.isTurn() && pcp.isFixed()) {
472                if (!pcp._getReversed())
473                    return direction == Direction.NEXT;
474                else
475                    return direction == Direction.PREVIOUS;
476             }
477             return false;
478         }
479         
480         private static Vector3d direction(PipeControlPoint pcp, Direction direction) {
481             return pcp.getDirection(direction);
482         }
483  
484         private static void updatePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
485                 int directed = 0;
486                 if (asDirected(u.start, Direction.NEXT))
487                         directed++;
488                 if (asDirected(u.end, Direction.PREVIOUS))
489                         directed++;
490                 switch (directed) {
491                 case 0:
492                         updateFreePathLeg(u, lengthChange);
493                         break;
494                 case 1:
495                         updateDirectedPathLeg(u, lengthChange);
496                         break;
497                 case 2:
498                         updateDualDirectedPathLeg(u, lengthChange);
499                         break;
500                 }
501
502         }
503
504         private static void updateFreePathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
505                 if (DEBUG)
506                         System.out.println("PipingRules.updateFreePipeRun " + u + " " + lengthChange);
507                 checkExpandPathLeg(u, lengthChange);
508                 if (u.start.isInline() || u.end.isInline() || u.start.isFixed() || u.end.isFixed())
509                         processPathLeg(u, true, false);
510         }
511
512         private static void updateInlineControlPoints(UpdateStruct2 u, boolean checkSizes) throws Exception{
513                 if (DEBUG)
514                         System.out.println("PipingRules.updateInlineControlPoints() " + u);
515
516                 Vector3d start = new Vector3d(u.startPoint);
517                 Vector3d end = new Vector3d(u.endPoint);
518                 
519                 if (checkSizes) {
520                         // create offsets for leg ends.
521                         MathTools.mad(start, u.dir, u.start.getInlineLength());
522                         MathTools.mad(end, u.dir, -u.end.getInlineLength());
523                 }
524                 
525                 boolean recalcline = false;
526                 if (!u.hasOffsets) {
527                         
528                         
529                         for (PipeControlPoint icp : u.list) {
530                                 updateInlineControlPoint(icp, start, end, u.dir);
531                                 
532                                 if (icp.isOffset()) {
533                                         // TODO : offset vector is already calculated and should be cached
534                                         Vector3d off = icp.getSizeChangeOffsetVector(u.dir);
535                                         updateOffsetPoint(icp, off);
536                                 }
537                         }
538                         if (!checkSizes)
539                                 return;                 
540
541                         ArrayList<PipeControlPoint> pathLegPoints = new ArrayList<PipeControlPoint>();
542                         pathLegPoints.add(u.start);
543                         for (PipeControlPoint icp : u.list) {
544                                 // updateInlineControlPoint(icp, u.startPoint,
545                                 // u.endPoint,u.dir);
546                                 updateBranchControlPointBranches(icp);
547                                 pathLegPoints.add(icp);
548                         }
549                         pathLegPoints.add(u.end);
550
551                         // TODO : values can be cached in the loop
552                         for (int i = 0; i < pathLegPoints.size(); i++) {
553                                 PipeControlPoint icp = pathLegPoints.get(i);
554
555                                 PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null;
556                                 PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null;
557
558                                 if (icp.isVariableLength()) {
559                                         if (prev != null && next != null) {
560                                                 
561                                                 recalcline = recalcline | updateVariableLength(icp,  prev, next);
562
563                                         } else {
564                                                 // this is variable length component at the end of the
565                                                 // piperun.
566                                                 // the problem is that we want to keep unconnected end
567                                                 // of the component in the same
568                                                 // place, but center of the component must be moved.
569                                                 updateVariableLengthEnd(icp, prev != null ? prev : next);
570                                         }
571
572
573                                 } else if (prev != null && !prev.isVariableLength()) {
574                                         // If this and previous control point are not variable
575                                         // length pcps, we'll have to check if there is no empty
576                                         // space between them.
577                                         // I there is, we'll have to create new variable length
578                                         // component between them.
579                                         recalcline = recalcline | possibleVaribleLengthInsert(icp, prev);
580                                 }
581                         }
582                 } else { // with offset
583                         Vector3d sp = new Vector3d(start);
584                         Vector3d ep = new Vector3d(end);
585                         ep.sub(u.offset);
586                         
587                         ArrayList<PipeControlPoint> pathLegPoints = new ArrayList<PipeControlPoint>();
588                         pathLegPoints.add(u.start);
589
590                         for (PipeControlPoint icp : u.list) {
591                                 updateInlineControlPoint(icp, sp, ep, u.dir);
592                                 updateBranchControlPointBranches(icp);
593                                 pathLegPoints.add(icp);
594                                 if (icp.isOffset()) {
595                                         // TODO : offset vector is already calculated and should be
596                                         // cached
597                                         Vector3d  offset = icp.getSizeChangeOffsetVector(u.dir);
598                                         updateOffsetPoint(icp, offset);
599                                         sp.add(offset);
600                                         ep.add(offset);
601                                 }
602                         }
603                         pathLegPoints.add(u.end);
604                         
605                         if (!checkSizes)
606                                 return; 
607                         
608                         sp = new Vector3d(u.startPoint);
609                         ep = new Vector3d(u.endPoint);
610                         ep.sub(u.offset);
611                         
612                         for (int i = 0; i < pathLegPoints.size(); i++) {
613                                 PipeControlPoint icp = pathLegPoints.get(i);
614
615                                 PipeControlPoint prev = i > 0 ? pathLegPoints.get(i - 1) : null;
616                 PipeControlPoint next = i < pathLegPoints.size() - 1 ? pathLegPoints.get(i + 1) : null;
617                 
618                                 if (prev != null && prev.isDualInline())
619                                         prev = prev.getSubPoint().get(0);
620                                 
621
622                                 if (icp.isVariableLength()) {
623                                         if (prev != null && next != null) {
624                                                 recalcline = recalcline | updateVariableLength(icp,  prev, next);
625
626                                         } else {
627                                                 // this is variable length component at the end of the
628                                                 // piperun.
629                                                 // the problem is that we want to keep unconnected end
630                                                 // of the component in the same
631                                                 // place, but center of the component must be moved.
632                                                 updateVariableLengthEnd(icp, prev != null ? prev : next);
633                                         }
634                                 } else if (prev != null && !prev.isVariableLength()) {
635                                         // If this and previous control point are not variable
636                                         // length pcps, we'll have to check if there is no empty
637                                         // space between them.
638                                         // I there is, we'll have to create new variable length
639                                         // component between them.
640                                         recalcline = recalcline | possibleVaribleLengthInsert(icp, prev);
641                                 }
642                                 if (icp.isOffset()) {
643                                         // TODO : offset vector is already calculated and should be
644                                         // cached
645                                         Vector3d  offset = icp.getSizeChangeOffsetVector(u.dir);
646                                         sp.add(offset);
647                                         ep.add(offset);
648                                 }
649                         }
650                 }
651                 if (recalcline) {
652                         u.list.clear();
653                         u.start.findNextEnd(u.list);
654                 }
655         }
656         
657         private static boolean updateVariableLength(PipeControlPoint icp, PipeControlPoint prev,  PipeControlPoint next) {
658                 Vector3d prevPos = prev.getWorldPosition();
659                 Vector3d nextPos = next.getWorldPosition();
660                 
661                 Vector3d dir = new Vector3d(nextPos);
662                 dir.sub(prevPos);
663                 double l = dir.lengthSquared(); // distance between
664                                                                                 // control points
665                                                                                 // (square)
666                 double l2prev = prev.getInlineLength(); // distance
667                                                                                                                                         // taken
668                                                                                                                                         // by
669                                                                                                                                         // components
670                 double l2next = next.getInlineLength();
671                 double l2 = l2prev + l2next;
672                 double l2s = MathTools.square(l2);
673                 if (l2s < l) { // check if there is enough space for
674                                                 // variable length component.
675                         // components fit
676                         dir.normalize();
677                         double length = Math.sqrt(l) - l2; // true length of
678                                                                                                 // the variable
679                                                                                                 // length
680                                                                                                 // component
681                         dir.scale(length * 0.5 + l2prev); // calculate
682                                                                                                 // center
683                                                                                                 // position of
684                                                                                                 // the component
685                         dir.add(prevPos);
686                         icp.setWorldPosition(dir);
687                         icp.setLength(length);
688                         return false;
689                 } else {
690                         // components leave no space to the component and it
691                         // must be removed
692                         
693                         if (icp.isDeletable()) {
694                                 if (DEBUG)
695                                         System.out.println("PipingRules.updateVariableLength removing " + icp);
696                                 icp._remove();
697                                 return true;
698                         }
699                         return false;
700                 }
701         }
702         
703         private static boolean possibleVaribleLengthInsert(PipeControlPoint icp, PipeControlPoint prev) throws Exception{
704                 Vector3d currentPos = icp.getWorldPosition();
705                 Vector3d prevPos = prev.getWorldPosition();
706                 Vector3d dir = new Vector3d(currentPos);
707                 dir.sub(prevPos);
708                 double l = dir.lengthSquared();
709                 double l2prev = prev.getInlineLength();
710                 double l2next = icp.getInlineLength();
711                 double l2 = l2prev + l2next;
712                 double l2s = l2 * l2;
713                 if (l > l2s) {
714                         if (allowInsertRemove) {
715                                 dir.normalize();
716                                 double length = Math.sqrt(l) - l2; // true length of the
717                                                                                                         // variable length
718                                                                                                         // component
719                                 dir.scale(length * 0.5 + l2prev); // calculate center
720                                                                                                         // position of the
721                                                                                                         // component
722                                 dir.add(prevPos);
723                                 PipeControlPoint scp = insertStraight(prev, icp, dir, length);
724                                 return true;
725                         } else {
726                                 triedIR = true;
727                         }
728                 }
729                 return false;
730         }
731         
732         private static void updateVariableLengthEnd(PipeControlPoint icp,  PipeControlPoint prev) {
733             Vector3d currentPos = icp.getWorldPosition();
734         Vector3d prevPos = prev.getWorldPosition();
735         
736         Vector3d dir = new Vector3d();
737         dir.sub(currentPos, prevPos);
738         
739             boolean simple = currentUpdates.contains(icp);
740             if (simple) {
741                 // Update based on position -> adjust length
742                 double currentLength = (dir.length() - prev.getInlineLength()) * 2.0;
743                 icp.setLength(currentLength);
744             } else {
745                 // Update based on neighbour movement -> adjust length and position, so that free end stays in place.
746                 double currentLength = icp.getLength();
747                 if (currentLength < MathTools.NEAR_ZERO) {
748                         currentLength = (dir.length() - prev.getInlineLength()) * 2.0;
749                 }
750                 
751                 if (dir.lengthSquared() > MathTools.NEAR_ZERO)
752                         dir.normalize();
753                 Point3d endPos = new Point3d(dir);
754                 endPos.scale(currentLength * 0.5);
755                 endPos.add(currentPos); // this is the free end of the component
756     
757                 double offset = prev.getInlineLength();
758                 Point3d beginPos = new Point3d(dir);
759                 beginPos.scale(offset);
760                 beginPos.add(prevPos); // this is the connected end of the component
761     
762                 double l = beginPos.distance(endPos);
763                 
764                 if (Double.isNaN(l))
765                         System.out.println("Length for " + icp + " is NaN");
766     
767                 dir.scale(l * 0.5);
768                 beginPos.add(dir); // center position
769     
770                 if (DEBUG)
771                         System.out.println("PipingRules.updateInlineControlPoints() setting variable length to " + l);
772                 icp.setLength(l);
773     
774                 icp.setWorldPosition(new Vector3d(beginPos));
775             }
776         }
777
778         private static void ppNoOffset(UpdateStruct2 u) throws Exception {
779                 if (DEBUG)
780                         System.out.println("PipingRules.ppNoOffset() " + u);
781                 Vector3d offset = new Vector3d();
782                 if (u.hasOffsets) {
783                         u.dir.normalize();
784                         for (PipeControlPoint icp : u.list) {
785                                 if (icp.isOffset()) {
786                                         offset.add(icp.getSizeChangeOffsetVector(u.dir));
787                                 } else if (icp.isDualSub())
788                                         ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
789                         }
790                 }
791                 u.offset = offset;
792                 checkExpandPathLeg(u, PathLegUpdateType.NONE);
793         }
794
795         private static void ppNoDir(PipeControlPoint start, Vector3d startPoint, ArrayList<PipeControlPoint> list, PipeControlPoint end, Vector3d endPoint, boolean hasOffsets, int iter, boolean reversed, ArrayList<ExpandIterInfo> toRemove, PipeControlPoint updated) throws Exception {
796                 if (DEBUG)
797                         System.out.println("PipingRules.ppNoDir() " + start + " " + end + " " + iter + " " + toRemove.size());
798                 // FIXME : extra loop (dir should be calculated here)
799                 Vector3d dir = new Vector3d();
800                 Vector3d offset = new Vector3d();
801                 hasOffsets = calculateOffset(startPoint, endPoint, list, dir, offset);
802                 ppNoOffset(new UpdateStruct2(start, startPoint, list, end, endPoint, dir, null, hasOffsets, iter, reversed, toRemove, updated));
803         }
804
805         private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
806                 checkExpandPathLeg(u, lengthChange, u.updated.isInline() && u.updated.isOffset());
807         }
808         
809         private static void checkExpandPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange, boolean updateEnds) throws Exception {
810                 if (DEBUG)
811                         System.out.println("PipingRules.checkExpandPathLeg() " + u + " " + lengthChange);
812                 if (lengthChange != PathLegUpdateType.NONE) {
813                         // FIXME : turns cannot be checked before inline cps are updated,
814                         // since their position affects calculation of turns
815                         processPathLeg(u, updateEnds, false);
816                         int type = checkTurns(u, lengthChange);
817                         if (type == REMOVE_NONE) {
818                                 processPathLeg(u, updateEnds, true);
819                         } else {
820                                 expandPathLeg(u, type);
821                         }
822                 } else {
823                         processPathLeg(u, updateEnds, true);
824                 }
825         }
826
827         private static void updateDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
828                 if (DEBUG)
829                         System.out.println("PipingRules.updateDirectedPipeRun() " + u + " " + lengthChange);
830                 PipeControlPoint dcp;
831                 PipeControlPoint other;
832                 boolean canMoveOther = false;
833                 boolean dcpStart = false;
834                 boolean inlineEnd = false;
835                 Vector3d position;
836                 if (asDirected(u.start, Direction.NEXT)) {
837                         dcp = u.start;
838                         other = u.end;
839                         position = u.startPoint;
840                         dcpStart = true;
841                         if (!u.reversed)
842                                 canMoveOther = true;
843                         inlineEnd = u.end.isInline();
844                                 
845                 } else {
846                         dcp = u.end;
847                         other = u.start;
848                         position = u.endPoint;
849                         if (u.reversed)
850                                 canMoveOther = true;
851                         inlineEnd = u.start.isInline();
852                 }
853
854                 Vector3d directedDirection = direction(dcp, dcpStart ? Direction.NEXT : Direction.PREVIOUS);
855                 if (directedDirection == null) {
856                     updateTurnControlPointTurn(dcp, dcp.getPrevious(), dcp.getNext());
857                     directedDirection = direction(dcp, dcpStart ? Direction.NEXT : Direction.PREVIOUS);
858                     if (directedDirection == null) {
859                         return;
860                     }
861                 }
862                 Point3d directedEndPoint = new Point3d(u.endPoint);
863                 if (u.hasOffsets)
864                         directedEndPoint.add(u.offset);
865
866                 double mu[] = new double[2];
867
868                 Vector3d closest;
869                 Vector3d t = new Vector3d();
870
871                 if (dcpStart) {
872                         closest = MathTools.closestPointOnStraight(directedEndPoint, u.startPoint, directedDirection, mu);
873                         t.sub(closest, directedEndPoint);
874                 } else {
875                         closest = MathTools.closestPointOnStraight(u.startPoint, directedEndPoint, directedDirection, mu);
876                         t.sub(closest, u.startPoint);
877                 }
878
879                 double distance = t.length();
880                 boolean aligned = (distance < ALLOWED_OFFSET);
881                 if (aligned) {
882                     if (u.start.isInline() || u.end.isInline() || u.start.isFixed() || u.end.isFixed())
883                     processPathLeg(u, true, false);
884                         checkExpandPathLeg(u, lengthChange, inlineEnd);
885                         
886                 } else {
887                         if (u.iter > 0) {
888                                 backIter(u);
889                         } else {
890                                 PipeControlPoint nextToMoved;
891
892                                 if (u.list.size() > 0)
893                                         if (dcpStart)
894                                                 nextToMoved = u.list.get(0);
895                                         else
896                                                 nextToMoved = u.list.get(u.list.size() - 1);
897                                 else if (dcpStart)
898                                         nextToMoved = u.end;
899                                 else
900                                         nextToMoved = u.start;
901                                 if (other.isVariableAngle()) {
902
903                                         // TODO calculate needed space from next run end.
904                                         if (mu[0] < 1.0) {
905                                                 if (dcpStart) {
906                                                         closest.set(u.startPoint);
907                                                 } else {
908                                                         closest.set(u.endPoint);
909                                                 }
910                                                 Vector3d v = new Vector3d(directedDirection);
911                                                 v.scale(spaceForTurn(other));
912                                                 closest.add(v);
913                                         }
914
915                                         if (canMoveOther) {
916                                                 if (DEBUG)
917                                                         System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + closest);
918                                                 other.setWorldPosition(closest);
919                                                 if (dcpStart) {
920                                                         ppNoOffset(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(closest), directedDirection, null, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated));
921                                                         if (u.end.getNext() != null)
922                                                                 updatePathLegNext(u.end, u.updated, PathLegUpdateType.NEXT);
923                                                 } else {
924                                                         ppNoOffset(new UpdateStruct2(u.start, new Vector3d(closest), u.list, u.end, u.endPoint, directedDirection, null, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated));
925                                                         if (u.start.getPrevious() != null)
926                                                                 updatePathLegPrev(u.start, u.updated, PathLegUpdateType.PREV);
927                                                 }
928                                         } else {
929                                                 // TODO : calculate needed space from next run end.
930                                                 if (allowInsertRemove)
931                                                         insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection);
932                                                         
933                                                 else
934                                                         triedIR = true;
935                                         }
936                                 } else if (other.isNonDirected() && other.getParentPoint() != null) {
937                                         // FIXME : this code was for updating branches
938                                         Vector3d bintersect = new Vector3d();
939                                         PipeControlPoint bcp = other.getParentPoint();
940                                         if (bcp != null && canMoveOther) {
941                                                 Point3d bstart = new Point3d();
942                                                 Point3d bend = new Point3d();
943                                                 Vector3d bdir = new Vector3d();
944                                                 bcp.getInlineControlPointEnds(bstart, bend, bdir);
945                                                 Vector3d nintersect = new Vector3d();
946
947                                                 MathTools.intersectStraightStraight(position, directedDirection, bend, bdir, nintersect, bintersect, mu);
948                                                 Vector3d dist = new Vector3d(nintersect);
949                                                 dist.sub(bintersect);
950                                                 canMoveOther = mu[1] > 0.0 && mu[1] < 1.0 && dist.lengthSquared() < 0.01;
951                                         } else {
952                                                 // TODO : endControlPoints are undirected: calculcate
953                                                 // correct position for it
954                                                 throw new UnsupportedOperationException("not implemented");
955                                         }
956                                         if (canMoveOther) {
957                                                 if (DEBUG)
958                                                         System.out.println("PipingRules.updateDirectedPipeRun() moved end " + other + " to " + bintersect);
959                                                 // is required branch position is in possible range
960                                                 bcp.setWorldPosition(bintersect);
961                                                 if (dcpStart) {
962                                                         checkExpandPathLeg(new UpdateStruct2(u.start, u.startPoint, u.list, u.end, new Vector3d(bintersect), directedDirection, u.offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), lengthChange);
963                                                 } else {
964                                                         checkExpandPathLeg(new UpdateStruct2(u.start, new Vector3d(bintersect), u.list, u.end, u.endPoint, directedDirection, u.offset, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated), lengthChange);
965                                                 }
966                                         } else {
967                                                 // branch cannot be moved into right position, new turn
968                                                 // / elbow must be inserted
969                                                 if (allowInsertRemove)
970                                                         insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection);
971                                                 else
972                                                         triedIR = true;
973                                         }
974
975                                 } else { // assume that control point cannot be moved, but can
976                                                         // be rotated
977                                         if (allowInsertRemove)
978                                                 insertElbowUpdate(u, dcp, nextToMoved, dcpStart, position, directedDirection);
979                                         else
980                                                 triedIR = true;
981                                 }
982                         }
983                 }
984                 
985                 
986         }
987
988         private static void updateDualDirectedPathLeg(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
989                 if (DEBUG)
990                         System.out.println("PipingRules.updateDualDirectedPipeRun() " + u + " " + lengthChange);
991                 
992                 PipeControlPoint dcp1 = u.start;
993                 PipeControlPoint dcp2 = u.end;
994                 Point3d position1 = new Point3d(u.startPoint);
995                 Point3d position2 = new Point3d(u.endPoint);
996                 Point3d position1offset = new Point3d(position1);
997                 position1offset.sub(u.offset);
998                 Point3d position2offset = new Point3d(position2);
999                 position2offset.add(u.offset);
1000                 Vector3d dir1 = direction(dcp1, Direction.NEXT);
1001                 Vector3d dir2 = direction(dcp2, Direction.PREVIOUS);
1002                 Vector3d p1 = MathTools.closestPointOnStraight(position1offset, position2, dir2);
1003                 Vector3d p2 = MathTools.closestPointOnStraight(position2offset, position1, dir1);
1004                 double d1 = position1.distance(new Point3d(p1));
1005                 double d2 = position2.distance(new Point3d(p2));
1006
1007                 boolean aligned = (d1 < ALLOWED_OFFSET && d2 < ALLOWED_OFFSET);
1008                 if (aligned) {
1009                         processPathLeg(u);
1010                 } else {
1011                         if (u.iter > 0) {
1012                                 backIter(u);
1013                         } else if (allowInsertRemove){
1014                                 PipeControlPoint dcp;
1015                                 PipeControlPoint next;
1016                                 if (!u.reversed) {
1017                                         dcp = dcp1;
1018                                         if (u.list.size() > 0)
1019                                                 next = u.list.get(0);
1020                                         else
1021                                                 next = dcp2;
1022                                 } else {
1023                                         dcp = dcp2;
1024                                         if (u.list.size() > 0)
1025                                                 next = u.list.get(u.list.size() - 1);
1026                                         else
1027                                                 next = dcp1;
1028                                 }
1029                                 
1030                                 p1 = dcp.getWorldPosition();
1031                                 Vector3d v = new Vector3d();
1032                                 if (!u.reversed)
1033                                     v.set(dir1);
1034                                 else
1035                                         v.set(dir2);
1036                                 
1037                                 // Reserve space for 90 deg elbow
1038                                 double off = dcp1.getPipeRun().getTurnRadius();
1039                                 v.scale(off);
1040                                 p1.add(v);
1041
1042                                 if (!u.reversed)
1043                                         p2 = MathTools.closestPointOnStraight(new Point3d(p1), position2, dir2);
1044                                 else
1045                                         p2 = MathTools.closestPointOnStraight(new Point3d(p1), position1, dir1);
1046
1047                                 // By default, the elbows are placed next to each other, by using 90 deg angles.
1048                                 // If the distance between elbows is not enough, we must move the other elbow (and create more shallow angle elbows)
1049                                 if (MathTools.distance(p1, p2) < off*2.05) {
1050                                     p2.add(v);
1051                                 }
1052                                 
1053                                 PipeControlPoint tcp1 = insertElbow(dcp, next, p1);
1054                                 PipeControlPoint tcp2 = insertElbow(tcp1, next, p2);
1055
1056                                 if (DEBUG)
1057                                         System.out.println("PipingRules.updateDualDirectedPipeRun() created two turns " + tcp1 + " " + tcp2);
1058
1059                                 if (!u.reversed) {
1060                                         Vector3d dd = new Vector3d(p2);
1061                                         dd.sub(p1);
1062                                         dir2.negate();
1063                                         updatePathLegNext(u.start, u.updated, PathLegUpdateType.NONE);
1064                                         updatePathLegNext(tcp1, u.updated, PathLegUpdateType.NONE);
1065                                         if (!u.reversed)
1066                                                 updatePathLegNext(tcp2, u.updated, PathLegUpdateType.NONE);
1067                                         else
1068                                                 updatePathLegPrev(tcp2, u.updated, PathLegUpdateType.NONE);
1069                                 } else {
1070                                         Vector3d dd = new Vector3d(p1);
1071                                         dd.sub(p2);
1072                                         dir2.negate();
1073                                         updatePathLegNext(tcp1, u.updated, PathLegUpdateType.NONE);
1074                                         updatePathLegNext(tcp2, u.updated, PathLegUpdateType.NONE);
1075                                         if (!u.reversed)
1076                                                 updatePathLegNext(u.start, u.updated, PathLegUpdateType.NONE);
1077                                         else
1078                                                 updatePathLegPrev(u.start, u.updated, PathLegUpdateType.NONE);
1079                                 }
1080                         } else {
1081                                 triedIR = true;
1082                         }
1083                 }
1084                 
1085         }
1086         
1087         private static double spaceForTurn(PipeControlPoint tcp) {
1088                 // TODO : this returns now space for 90 deg turn.
1089                 // The challenge: position of tcp affects the turn angle, which then affects the required space. Perhaps we need to iterate...
1090                 // Additionally, if the path legs contain offset, using just positions of opposite path leg ends is not enough,    
1091                 return tcp.getPipeRun().getTurnRadius();
1092         }
1093
1094         private static void insertElbowUpdate(UpdateStruct2 u, PipeControlPoint dcp, PipeControlPoint next, boolean dcpStart, Vector3d position, Vector3d directedDirection) throws Exception{
1095
1096                 
1097 //              Vector3d closest = new Vector3d(position);
1098 //              closest.add(directedDirection);
1099                 
1100                 PipeControlPoint tcp = null;
1101                 Vector3d closest;
1102                 if (dcpStart) {
1103                         closest = MathTools.closestPointOnStraight(next.getWorldPosition(), position, directedDirection);
1104                         tcp = insertElbow(dcp, next, closest);
1105                 } else {
1106                         closest = MathTools.closestPointOnStraight(dcp.getWorldPosition(), position, directedDirection);
1107                         tcp = insertElbow(next, dcp, closest);
1108                 }
1109                 // TODO properly calculate required distance between start and inserted elbow.
1110                 double d = MathTools.distance(position, closest);
1111                 double s = spaceForTurn(tcp);
1112                 if (d < s)  {
1113                         d = s - d;
1114                         Vector3d p = new Vector3d(directedDirection);
1115                         p.scale(d);
1116                         p.add(closest);
1117                         tcp.setPosition(p);
1118                         closest = p;
1119                 }
1120                 
1121                 
1122
1123                 if (DEBUG)
1124                         System.out.println("PipingRules.updateDirectedPipeRun() inserted " + tcp);
1125
1126                 if (dcpStart) {
1127                         // update pipe run from new turn to other end
1128                         ppNoDir(tcp, new Vector3d(closest), u.list, u.end, u.endPoint, u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated);
1129                         // update pipe run from directed to new turn
1130                         processPathLeg(new UpdateStruct2(u.start, u.startPoint, new ArrayList<PipeControlPoint>(), tcp, new Vector3d(closest), directedDirection, new Vector3d(), false, 0, false, new ArrayList<ExpandIterInfo>(), u.updated));
1131                 } else {
1132                         // update pipe run from other end to new turn
1133                         ppNoDir(u.start, u.startPoint, u.list, tcp, new Vector3d(closest), u.hasOffsets, u.iter, u.reversed, u.toRemove, u.updated);
1134                         // update pipe run from new turn to directed
1135                         processPathLeg(new UpdateStruct2(tcp, new Vector3d(closest), new ArrayList<PipeControlPoint>(), u.end, u.endPoint, directedDirection, new Vector3d(), false, 0, false, new ArrayList<ExpandIterInfo>(), u.updated));
1136                 }
1137         }
1138
1139         /**
1140          * Checks if turns can be removed (turn angle near zero)
1141          */
1142         private static int checkTurns(UpdateStruct2 u, PathLegUpdateType lengthChange) throws Exception {
1143                 if (DEBUG)
1144                         System.out.println("PipingRules.checkTurns() " + u.start + " " + u.end);
1145                 boolean startRemoved = false;
1146                 boolean endRemoved = false;
1147                 if (u.start.isVariableAngle()) {
1148                         // this won't work properly if inline control points are not updated
1149                         PipeControlPoint startPrev = u.start.getPrevious();
1150                         if (startPrev != null) {
1151                                 double a;
1152                                 if (!u.hasOffsets) {
1153                                         a = updateTurnControlPointTurn(u.start, startPrev, u.end);
1154                                 } else {
1155                                         Vector3d ep = new Vector3d(u.endPoint);
1156                                         ep.sub(u.offset);
1157                                         a = updateTurnControlPointTurn(u.start, u.startPoint, startPrev.getPosition(), ep);
1158
1159                                 }
1160                                 if (a < MIN_TURN_ANGLE && u.start.isDeletable())
1161                                         startRemoved = true;
1162                                 else if (lengthChange == PathLegUpdateType.PREV || lengthChange == PathLegUpdateType.PREV_S) {
1163                                         PathLegUpdateType type;
1164                                         if (lengthChange == PathLegUpdateType.PREV_S)
1165                                                 type = PathLegUpdateType.PREV;
1166                                         else
1167                                                 type = PathLegUpdateType.NONE;
1168                                         updatePathLegPrev(u.start, u.start, type);
1169                                 }
1170                         }
1171                 }
1172                 if (u.end.isVariableAngle()) {
1173
1174                         PipeControlPoint endNext = u.end.getNext();
1175                         if (endNext != null) {
1176                                 double a;
1177                                 if (!u.hasOffsets) {
1178                                         a = updateTurnControlPointTurn(u.end, u.start, endNext);
1179                                 } else {
1180                                         Vector3d sp = new Vector3d(u.startPoint);
1181                                         sp.add(u.offset);
1182                                         a = updateTurnControlPointTurn(u.end, u.endPoint, sp, endNext.getPosition());
1183                                 }
1184                                 if (a < MIN_TURN_ANGLE && u.end.isDeletable())
1185                                         endRemoved = true;
1186                                 else if (lengthChange == PathLegUpdateType.NEXT || lengthChange == PathLegUpdateType.NEXT_S) {
1187                                         PathLegUpdateType type;
1188                                         if (lengthChange == PathLegUpdateType.NEXT_S)
1189                                                 type = PathLegUpdateType.NEXT;
1190                                         else
1191                                                 type = PathLegUpdateType.NONE;
1192                                         updatePathLegNext(u.end, u.end, type);
1193                                 }
1194                         }
1195                 }
1196                 if (DEBUG)
1197                         System.out.println("PipingRules.checkTurns() res " + startRemoved + " " + endRemoved);
1198                 if (!startRemoved && !endRemoved)
1199                         return REMOVE_NONE;
1200                 if (startRemoved && endRemoved)
1201                         return REMOVE_BOTH;
1202                 if (startRemoved)
1203                         return REMOVE_START;
1204                 return REMOVE_END;
1205         }
1206
1207         /**
1208          * Expands piperun search over turns that are going to be removed
1209          * 
1210          */
1211         private static void expandPathLeg(UpdateStruct2 u, int type) throws Exception {
1212                 if (DEBUG)
1213                         System.out.println("PipingRules.expandPipeline " + u.start + " " + u.end);
1214                 ArrayList<PipeControlPoint> newList = new ArrayList<PipeControlPoint>();
1215                 switch (type) {
1216                 case REMOVE_NONE:
1217                         throw new RuntimeException("Error in piping rules");
1218                 case REMOVE_START:
1219                         u.toRemove.add(new ExpandIterInfo(u.start, REMOVE_START));
1220                         u.start = u.start.findPreviousEnd();
1221                         u.startPoint = u.start.getPosition();
1222                         u.start.findNextEnd(newList);
1223                         newList.addAll(u.list);
1224                         u.list = newList;
1225                         break;
1226                 case REMOVE_END:
1227                         u.toRemove.add(new ExpandIterInfo(u.end, REMOVE_END));
1228                         u.end = u.end.findNextEnd(newList);
1229                         u.endPoint = u.end.getPosition();
1230                         u.list.addAll(newList);
1231                         break;
1232                 case REMOVE_BOTH:
1233                         u.toRemove.add(new ExpandIterInfo(u.start, u.end));
1234                         u.start = u.start.findPreviousEnd();
1235                         u.startPoint = u.start.getPosition();
1236                         u.start.findNextEnd(newList);
1237                         newList.addAll(u.list);
1238                         u.list = newList;
1239                         newList = new ArrayList<PipeControlPoint>();
1240                         u.end = u.end.findNextEnd(newList);
1241                         u.endPoint = u.end.getPosition();
1242                         u.list.addAll(newList);
1243                         break;
1244                 default:
1245                         throw new RuntimeException("Error in piping rules");
1246
1247                 }
1248                 u.offset = new Vector3d();
1249                 if (u.hasOffsets) {
1250                         u.dir.normalize();
1251                         for (PipeControlPoint icp : u.list) {
1252                                 if (icp.isOffset()) {
1253                                         u.offset.add(icp.getSizeChangeOffsetVector(u.dir));
1254                                 } else if (icp.isDualSub())
1255                                         ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
1256                         }
1257                 }
1258                 if (DEBUG)
1259                         System.out.println("PipingRules.expandPipeline expanded " + u.start + " " + u.end);
1260                 u.iter++;
1261                 updatePathLeg(u, PathLegUpdateType.NONE);
1262         }
1263
1264         /**
1265          * reverts one iteration of turn removing back)
1266          */
1267         private static void backIter(UpdateStruct2 u) throws Exception {
1268
1269                 if (DEBUG)
1270                         System.out.println("PipingRules.backIter" + u.start + " " + u.end);
1271                 if (u.iter == 0)
1272                         throw new RuntimeException("Error in piping rules");
1273                 ExpandIterInfo info = u.toRemove.get(u.toRemove.size() - 1);
1274                 u.toRemove.remove(u.toRemove.size() - 1);
1275                 if (info.getType() == REMOVE_START || info.getType() == REMOVE_BOTH) {
1276                         while (u.list.size() > 0) {
1277                                 PipeControlPoint icp = u.list.get(0);
1278                                 if (icp.getPrevious().equals(info.getStart()))
1279                                         break;
1280                                 u.list.remove(icp);
1281                         }
1282                         u.start = info.getStart();
1283                 }
1284                 if (info.getType() == REMOVE_END || info.getType() == REMOVE_BOTH) {
1285                         while (u.list.size() > 0) {
1286                                 PipeControlPoint icp = u.list.get(u.list.size() - 1);
1287                                 if (icp.getNext().equals(info.getEnd()))
1288                                         break;
1289                                 u.list.remove(icp);
1290                         }
1291                         u.end = info.getEnd();
1292                 }
1293                 u.offset = new Vector3d();
1294                 if (u.hasOffsets) {
1295                         u.dir.normalize();
1296                         for (PipeControlPoint icp : u.list) {
1297                                 if (icp.isOffset()) {
1298                                         u.offset.add(icp.getSizeChangeOffsetVector(u.dir));
1299                                 } else if (icp.isDualSub())
1300                                         ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
1301                         }
1302                 }
1303                 processPathLeg(u);
1304
1305         }
1306
1307         /**
1308          * Processes pipe run (removes necessary turns and updates run ends)
1309          */
1310         // private static void processPathLeg(PipeControlPoint start, Point3d
1311         // startPoint,ArrayList<InlineControlPoint> list, PipeControlPoint
1312         // end,Point3d endPoint, Vector3d dir,Vector3d offset, boolean
1313         // hasOffsets,int iter, boolean reversed, ArrayList<ExpandIterInfo>
1314         // toRemove) throws TransactionException {
1315
1316         private static void processPathLeg(UpdateStruct2 u) throws Exception {
1317                 if (DEBUG)
1318                         System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end);
1319                 processPathLeg(u, true, true);
1320         }
1321
1322         private static void processPathLeg(UpdateStruct2 u, boolean updateEnds, boolean updateInline) throws Exception {
1323                 if (DEBUG)
1324                         System.out.println("PipingRules.processPathLeg " + (updateEnds ? "ends " : "") + (updateInline ? "inline " : "") + u.start + " " + u.end);
1325
1326                 if (u.toRemove.size() > 0) {
1327                         for (ExpandIterInfo info : u.toRemove) {
1328                                 if (info.getStart() != null) {
1329                                         if (DEBUG)
1330                                                 System.out.println("PipingRules.processPathLeg removing start " + info.getStart());
1331                                         info.getStart()._remove();
1332                                 }
1333                                 if (info.getEnd() != null) {
1334                                         if (DEBUG)
1335                                                 System.out.println("PipingRules.processPathLeg removing end " + info.getEnd());
1336                                         info.getEnd()._remove();
1337                                 }
1338                         }
1339                         // ControlPointTools.removeControlPoint may remove more than one CP;
1340                         // we must populate inline CP list again.
1341                         u.list.clear();
1342                         u.start.findNextEnd( u.list);
1343                 }
1344                 // FIXME : inline CPs are update twice because their positions must be
1345                 // updated before and after ends.
1346                 updateInlineControlPoints(u, false);
1347                 
1348                 if (updateEnds) {
1349                         if (u.start.isTurn()) {
1350                                 updateTurnControlPointTurn(u.start, u.start.getPrevious(), u.start.getNext());
1351 //                              updatePathLegPrev(u.start, u.start, PathLegUpdateType.NONE);
1352                         } else if (u.start.isEnd()) {
1353                                 updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint);
1354                         } else if (u.start.isInline()) {
1355                                 updateControlPointOrientation(u.start);
1356                         }
1357                         if (u.end.isTurn()) {
1358                                 updateTurnControlPointTurn(u.end, u.end.getPrevious(), u.end.getNext());
1359 //                              updatePathLegNext(u.end, u.end, PathLegUpdateType.NONE);
1360                         } else if (u.end.isEnd()) {
1361                                 updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint);
1362                         } else if (u.end.isInline()) {
1363                                 updateControlPointOrientation(u.end);
1364                         }
1365
1366                 } else {
1367                         if (u.start.isEnd()) {
1368                                 updateEndComponentControlPoint(u.start, u.startPoint, u.endPoint);
1369                         }
1370                         if (u.end.isEnd()) {
1371                                 updateEndComponentControlPoint(u.end, u.startPoint, u.endPoint);
1372                         }
1373                 }
1374                 if (updateInline)
1375                         updateInlineControlPoints(u, true);
1376
1377         }
1378
1379         /**
1380          * Processes pipe run and recalculates offset
1381          */
1382         // private static void processPathLeg(PipeControlPoint start, Point3d
1383         // startPoint,ArrayList<InlineControlPoint> list, PipeControlPoint
1384         // end,Point3d endPoint, Vector3d dir, boolean hasOffsets,int iter, boolean
1385         // reversed, ArrayList<ExpandIterInfo> toRemove) throws TransactionException
1386         // {
1387         private static void processPathLegNoOffset(UpdateStruct2 u) throws Exception {
1388                 if (DEBUG)
1389                         System.out.println("PipingRules.processPathLeg " + u.start + " " + u.end);
1390                 Vector3d offset = new Vector3d();
1391                 if (u.hasOffsets) {
1392                         u.dir.normalize();
1393                         for (PipeControlPoint icp : u.list) {
1394                                 if (icp.isOffset()) {
1395                                         offset.add(icp.getSizeChangeOffsetVector(u.dir));
1396                                 } else if (icp.isDualSub()) {
1397                                         ErrorLogger.defaultLogError("Updating pipe run, found offset controlpoint " + icp, new Exception("ASSERT!"));
1398                                 }
1399                         }
1400                 }
1401                 processPathLeg(u);
1402         }
1403
1404         private static void updateOffsetPoint(PipeControlPoint sccp, Vector3d offset) {
1405                 Vector3d world = sccp.getWorldPosition();
1406                 world.add(offset);
1407                 PipeControlPoint ocp = sccp.getSubPoint().iterator().next();
1408                 ocp.setWorldPosition(world);
1409         }
1410
1411         /**
1412          * Updates InlineControlPoints position when straight pipe's end(s) have
1413          * been changed)
1414          * 
1415          * @param pipeline
1416          * @param icp
1417          * @param nextPoint
1418          * @param prevPoint
1419          */
1420         private static void updateInlineControlPoint(PipeControlPoint icp, Vector3d prev, Vector3d next,  Vector3d dir) {
1421                 if (DEBUG)
1422                         System.out.println("PipingRules.updateInlineControlPoint() " + icp);
1423
1424                 Vector3d inlinePoint = icp.getWorldPosition();
1425                 Vector3d prevPoint = new Vector3d(prev);
1426                 Vector3d nextPoint = new Vector3d(next);
1427                 if (!icp.isVariableLength()) {
1428                         // Reserve space for fixed length components. 
1429                         MathTools.mad(prevPoint, dir, icp.getInlineLength());
1430                         MathTools.mad(nextPoint, dir, -icp.getInlineLength());
1431                         if (MathTools.distance(prevPoint, nextPoint) < ALLOWED_OFFSET) {
1432                                 prevPoint = prev;
1433                                 nextPoint = next;
1434                         }
1435                 }
1436                 boolean canCalc = MathTools.distance(prevPoint, nextPoint) > ALLOWED_OFFSET;
1437                 if (DEBUG)
1438                         System.out.print("InlineControlPoint update " + icp + " " + inlinePoint + " " + prevPoint + " " + nextPoint);
1439                 Vector3d newInlinePoint = null;
1440                 if (canCalc) {
1441                         boolean branchUpdate = false;
1442                         PipeControlPoint becp = null;
1443                         for (PipeControlPoint pcp : icp.getSubPoint())
1444                                 if (pcp.isNonDirected()) {
1445                                         branchUpdate = true;
1446                                         becp = pcp;
1447                                         break;
1448                                 }
1449         
1450                         if (DUMMY || !branchUpdate) {
1451                                 newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), prevPoint, nextPoint);
1452                                 
1453                         } else {
1454         
1455                                 // FIXME : can only handle one branch
1456                                 PipeControlPoint p = null;
1457                                 if (becp.getNext() != null) {
1458                                         p = becp.findNextEnd();
1459                                 } else if (becp.getPrevious() != null) {
1460                                         p = becp.findPreviousEnd();
1461                                 }
1462                                 if (p == null) {
1463                                         newInlinePoint = MathTools.closestPointOnEdge(new Vector3d(inlinePoint), prevPoint, nextPoint);
1464                                 } else if (canCalc){
1465                                         Vector3d branchLegEnd = p.getWorldPosition();
1466                                         Vector3d dir2 = new Vector3d(inlinePoint);
1467                                         dir2.sub(branchLegEnd);
1468                                         Vector3d dir1 = new Vector3d(nextPoint);
1469                                         dir1.sub(prevPoint);
1470                                         newInlinePoint = new Vector3d();
1471                                         double mu[] = new double[2];
1472                                         MathTools.intersectStraightStraight(new Vector3d(prevPoint), dir1, new Vector3d(branchLegEnd), dir2, newInlinePoint, new Vector3d(), mu);
1473                                         if (DEBUG)
1474                                                 System.out.println(mu[0]);
1475                                         // FIXME : reserve space
1476                                         if (mu[0] < 0.0) {
1477                                                 newInlinePoint = new Vector3d(prevPoint);
1478                                         } else if (mu[0] > 1.0) {
1479                                                 newInlinePoint = new Vector3d(nextPoint);
1480                                         }
1481                                 }
1482                         }
1483                 } else {
1484                         // prevPoint == nextPoint
1485                         newInlinePoint = new Vector3d(prevPoint);
1486                 }
1487                 if (DEBUG)
1488                         System.out.println(" " + newInlinePoint);
1489
1490                 icp.setWorldPosition(newInlinePoint);
1491                 updateControlPointOrientation(icp);
1492         }
1493
1494         /**
1495          * Updates InlineControlPoints position when straight pipe's end(s) have
1496          * been changed)
1497          * 
1498          * @param pipeline
1499          * @param icp
1500          * @param nextPoint
1501          * @param prevPoint
1502          */
1503         private static void updateEndComponentControlPoint(PipeControlPoint ecp, Vector3d start, Vector3d end) throws Exception {
1504                 if (DEBUG)
1505                         System.out.println("PipingRules.updateEndComponentControlPoint() " + ecp);
1506                 // PipeControlPoint next = ecp.getNext();
1507                 // PipeControlPoint prev = ecp.getPrevious();
1508                 // if (next != null) {
1509                 // end = G3DTools.getPoint(next.getLocalPosition());
1510                 // start = G3DTools.getPoint(ecp.getLocalPosition());
1511                 // } else if (prev != null) {
1512                 // end = G3DTools.getPoint(ecp.getLocalPosition());
1513                 // start = G3DTools.getPoint(prev.getLocalPosition());
1514                 // } else {
1515                 // // TODO : warning?
1516                 // return;
1517                 // }
1518                 // Vector3d dir = new Vector3d (end);
1519                 // dir.sub(start);
1520                 // dir.normalize();
1521                 // G3DTools.setTuple(ecp.getDirection(), dir);
1522                 if (!ecp.isFixed())
1523                         updateControlPointOrientation(ecp);
1524
1525                 for (PipeControlPoint pcp : ecp.getSubPoint()) {
1526                         // TODO update position
1527                         updatePathLegEndControlPoint(pcp);
1528                 }
1529         }
1530
1531         private static void updateControlPointOrientation(PipeControlPoint pcp) {
1532                 // FIXME : hack to bypass variable length components orientation
1533 //              if (pcp.getAtMostOneRelatedObject(ProcessResource.g3dResource.HasWorldOrientation) == null)
1534 //                      return;
1535 //              if (pcp.rotationAngle == null)
1536 //                      return;
1537                 Double angleO = pcp.getRotationAngle();
1538                 double angle = 0.0;
1539                 if (angleO != null)
1540                         angle = angleO;
1541                 Boolean reversedO = pcp.getReversed();
1542                 boolean reversed = false;
1543                 if (reversedO != null)
1544                         reversed = reversedO;
1545                 Quat4d q = pcp.getControlPointOrientationQuat(angle, reversed);
1546                 pcp.setWorldOrientation(q);
1547         }
1548
1549         /**
1550          * Updates all branches when branch's position has been changed
1551          * 
1552          * @param bcp
1553          */
1554         private static void updateBranchControlPointBranches(PipeControlPoint bcp) throws Exception {
1555                 if (DEBUG)
1556                         System.out.println("PipingRules.updateBranchControlPointBranches() " + bcp);
1557                 if (bcp.isDualInline())
1558                         return;
1559                 Collection<PipeControlPoint> branches = bcp.getSubPoint();
1560                 if (branches.size() == 0) {
1561                         if (DEBUG)
1562                                 System.out.println("No Branches found");
1563                         return;
1564                 }
1565                 
1566                 for (PipeControlPoint pcp : branches) {
1567                         updatePathLegEndControlPoint(pcp);
1568                 }
1569         }
1570
1571         /**
1572          * Recalculates turn control point's internal data (turn angle and offset)
1573          * 
1574          * @param tcp
1575          * @param prev
1576          * @param next
1577          */
1578         private static double updateTurnControlPointTurn(PipeControlPoint tcp, PipeControlPoint prev, PipeControlPoint next) {
1579                 if (DEBUG)
1580                         System.out.println("PipingTools.updateTurnControlPointTurn()" + tcp);
1581                 
1582                 if (!tcp.isFixed()) {
1583                     if (next == null || prev == null)
1584                     return tcp.getTurnAngle();
1585                 Vector3d middlePoint = tcp.getWorldPosition();
1586                 Vector3d nextPoint = next.getWorldPosition();
1587                 Vector3d prevPoint = prev.getWorldPosition();
1588                 return updateTurnControlPointTurn(tcp, middlePoint, prevPoint, nextPoint);
1589                 } else {
1590                     // Verify that fixed turn is properly defined.
1591                     // TODO : when reversed flag is changed, rotation angle should be recalculated, otherwise the orientation will change.
1592                     // TODO : should reverse flag toggle to be done already when we are removing defining side?
1593                     if (prev != null && next != null) {
1594                         // Nothing to do
1595                     } else if (prev == null) {
1596                         if (!tcp._getReversed())
1597                             tcp.setReversed(true);
1598                     } else if (next == null) {
1599                         if (tcp._getReversed())
1600                             tcp.setReversed(false);
1601                     }
1602                     
1603                     Vector3d dir;
1604                     if (!tcp._getReversed()) {
1605                     if (prev == null)
1606                     return Math.PI; // FIXME : argh
1607                     
1608                     Vector3d middlePoint = tcp.getWorldPosition();
1609                     Vector3d prevPoint = prev.getWorldPosition();
1610                     dir = new Vector3d();
1611                     dir.sub(middlePoint, prevPoint);        
1612                    
1613                     } else {
1614                         if (next == null)
1615                     return Math.PI; // FIXME : argh
1616                 
1617                 Vector3d middlePoint = tcp.getWorldPosition();
1618                 Vector3d nextPoint = next.getWorldPosition();
1619                 dir = new Vector3d();
1620                 dir.sub(middlePoint, nextPoint);
1621                     }
1622                     dir.normalize();
1623             
1624             Quat4d q = PipeControlPoint.getControlPointOrientationQuat(dir, tcp.getRotationAngle() != null ? tcp.getRotationAngle() : 0.0);
1625             Vector3d v = new Vector3d();
1626             MathTools.rotate(q, MathTools.Y_AXIS,v);
1627             tcp.setTurnAxis(v);
1628             tcp.setWorldOrientation(q);
1629             return tcp.getTurnAngle();
1630                 }
1631         }
1632
1633         /**
1634          * Recalculates turn control point's internal data (turn angle and offset)
1635          * 
1636          * @param tcp
1637          * @param middlePoint
1638          * @param nextPoint
1639          * @param prevPoint
1640          */
1641         private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d middlePoint, Vector3d prevPoint, Vector3d nextPoint) {
1642
1643                 Vector3d dir1 = new Vector3d(middlePoint);
1644                 dir1.sub(prevPoint);
1645                 Vector3d dir2 = new Vector3d(nextPoint);
1646                 dir2.sub(middlePoint);
1647                 if (DEBUG)
1648                         System.out.println("PipingTools.updateTurnControlPointTurn " + tcp + " " + prevPoint + " " + middlePoint + " " + nextPoint);
1649                 return updateTurnControlPointTurn(tcp, dir1, dir2);
1650         }
1651
1652         private static double updateTurnControlPointTurn(PipeControlPoint tcp, Vector3d dir1, Vector3d dir2) {
1653                 double turnAngle = dir1.angle(dir2);
1654
1655                 double angle = Math.PI - turnAngle;
1656
1657                 Vector3d turnAxis = new Vector3d();
1658                 turnAxis.cross(dir1, dir2);
1659                 if (turnAxis.lengthSquared() > MathTools.NEAR_ZERO) {
1660                         double elbowRadius = tcp.getPipelineComponent().getPipeRun().getTurnRadius();
1661                         double R = elbowRadius / Math.tan(angle * 0.5);
1662                         
1663                         turnAxis.normalize();
1664                         tcp.setTurnAngle(turnAngle);
1665                         tcp.setLength(R);// setComponentOffsetValue(R);
1666                         tcp.setTurnAxis(turnAxis);
1667 //                      tcp.setPosition(tcp.getPosition());
1668                 } else {
1669                         turnAngle = 0.0;
1670                         tcp.setTurnAngle(0.0);
1671                         tcp.setLength(0.0);
1672                         tcp.setTurnAxis(new Vector3d(MathTools.Y_AXIS));
1673                 }
1674                 updateControlPointOrientation(tcp);
1675                 if (DEBUG)
1676                         System.out.println("PipingTools.updateTurnControlPointTurn " + dir1 + " " + dir2 + " " + turnAngle + " " + turnAxis);
1677                 return turnAngle;
1678         }
1679         
1680         public static List<PipeControlPoint> getControlPoints(PipeRun pipeRun) {
1681                 List<PipeControlPoint> list = new ArrayList<PipeControlPoint>();
1682                 if (pipeRun.getControlPoints().size() == 0)
1683                         return list;
1684                 PipeControlPoint pcp = pipeRun.getControlPoints().iterator().next();
1685                 while (pcp.getPrevious() != null) {
1686                         PipeControlPoint prev = pcp.getPrevious();
1687                         if (prev.getPipeRun() != pipeRun)
1688                                 break;
1689                         pcp = prev;
1690                 }
1691                 if (pcp.isDualSub()) {
1692                         pcp = pcp.getParentPoint();
1693                 }
1694                 list.add(pcp);
1695                 while (pcp.getNext() != null) {
1696                         pcp = pcp.getNext();
1697                         if (pcp.getPipeRun() != pipeRun)
1698                                 break;
1699                         list.add(pcp);
1700                 }
1701                 return list;
1702         }
1703         
1704         public static void reverse(PipeRun pipeRun) {
1705                 List<PipeControlPoint> list = getControlPoints(pipeRun);
1706                 if (list.size() <= 1)
1707                         return; // nothing to do.
1708                 
1709                 for (int i = 0 ; i < list.size(); i++) {
1710                         boolean first = i == 0;
1711                         boolean last = i == list.size() - 1;
1712                         PipeControlPoint current = list.get(i);
1713                         PipeControlPoint currentSub = null;
1714                         if (current.isDualInline())
1715                                 currentSub = current.getSubPoint().get(0);
1716                         if (first) {
1717                                 PipeControlPoint next = list.get(i+1);
1718                                 if (next.isDualInline())
1719                                         next = next.getSubPoint().get(0);
1720                                 current.setNext(null);
1721                                 current.setPrevious(next);
1722                                 if (currentSub != null) {
1723                                         currentSub.setNext(null);
1724                                         currentSub.setPrevious(next);           
1725                                 }
1726                         } else if (last) {
1727                                 PipeControlPoint prev = list.get(i-1);
1728                                 
1729                                 current.setPrevious(null);
1730                                 current.setNext(prev);
1731                                 
1732                                 if (currentSub != null) {
1733                                         currentSub.setPrevious(null);
1734                                         currentSub.setNext(prev);               
1735                                 }
1736                         } else {
1737                                 PipeControlPoint prev = list.get(i-1);
1738                                 PipeControlPoint next = list.get(i+1);
1739                                 if (next.isDualInline())
1740                                         next = next.getSubPoint().get(0);
1741                                 
1742                                 
1743                                 current.setPrevious(next);
1744                                 current.setNext(prev);
1745                                 
1746                                 if (currentSub != null) {
1747                                         currentSub.setPrevious(next);
1748                                         currentSub.setNext(prev);               
1749                                 }
1750                                 
1751                         }
1752                         if (current.isTurn() && current.isFixed()) {
1753                             current.setReversed(!current._getReversed());
1754                         }
1755                         if (current.isInline() && current.isReverse()) {
1756                             current.setReversed(!current._getReversed());
1757                         }   
1758                 }
1759         }
1760         
1761         public static void merge(PipeRun run1, PipeRun r2) {
1762                 Map<PipeControlPoint, Vector3d> positions = new HashMap<PipeControlPoint, Vector3d>();
1763                 Map<PipeControlPoint, Quat4d> orientations = new HashMap<PipeControlPoint, Quat4d>();
1764                 for (PipeControlPoint pcp : r2.getControlPoints()) {
1765                         positions.put(pcp, pcp.getWorldPosition());
1766                         orientations.put(pcp, pcp.getWorldOrientation());
1767                 }
1768                 for (PipeControlPoint pcp : r2.getControlPoints()) {
1769                         r2.deattachChild(pcp);
1770                         run1.addChild(pcp);
1771                         PipelineComponent component = pcp.getPipelineComponent();
1772                         if (component != null) {
1773                                 if (!(component instanceof Nozzle)) {
1774                                         component.deattach();
1775                                         run1.addChild(component);
1776                                 } else {
1777                                         Nozzle n = (Nozzle)component;
1778                                         n.setPipeRun(run1);
1779                                 }
1780                         }
1781                 }
1782                 r2.remove();
1783                 
1784         }
1785         
1786         public static void validate(PipeRun pipeRun) {
1787                 if (pipeRun == null)
1788                         return;
1789                 Collection<PipeControlPoint> pcps = pipeRun.getControlPoints();
1790                 int count = 0;
1791                 //System.out.println("Validate " + pipeRun.getName());
1792                 for (PipeControlPoint pcp : pcps) {
1793                         if (pcp.getParentPoint() == null || pcp.getParentPoint().getPipeRun() != pipeRun)
1794                                 count++;
1795                 }
1796                 List<PipeControlPoint> runPcps = getControlPoints(pipeRun);
1797                 if (runPcps.size() != count) {
1798                         System.out.println("Run " + pipeRun.getName() + " contains unconnected control points");
1799                 }
1800                 for (PipeControlPoint pcp : pcps) {
1801                         if (!pcp.isDirected() && pcp.getNext() == null && pcp.getPrevious() == null)
1802                                 System.out.println("Orphan undirected " + pcp);
1803                 }
1804                 for (PipeControlPoint pcp : pcps) {
1805                         if (pcp.getParentPoint() == null) {
1806                                 PipeControlPoint sub = null;
1807                                 if (pcp.isDualInline())
1808                                         sub = pcp.getSubPoint().get(0);
1809                                 PipeControlPoint next = pcp.getNext();
1810                                 PipeControlPoint prev = pcp.getPrevious();
1811                                 if (next != null) {
1812                                         if (!(next.getPrevious() == pcp || next.getPrevious() == sub)) {
1813                                                 System.out.println("Inconsistency between " + pcp + " -> " +next );
1814                                         }
1815                                 }
1816                                 if (prev != null) {
1817                                         PipeControlPoint prevParent = null;
1818                                         if (prev.isDualSub()) {
1819                                                 prevParent = prev.getParentPoint();
1820                                         } else if (prev.isDualInline()) {
1821                                                 System.out.println("Inconsistency between " + pcp + " <-- " +prev );
1822                                         }
1823                                         if (!(prev.getNext() == pcp && (prevParent == null || prevParent.getNext() == pcp))) {
1824                                                 System.out.println("Inconsistency between " + pcp + " <-- " +prev );
1825                                         }
1826                                 }
1827                         }
1828                 }
1829         }
1830         
1831         public static void splitVariableLengthComponent(PipelineComponent newComponent, InlineComponent splittingComponent, boolean assignPos) throws Exception{
1832                 assert(!splittingComponent.getControlPoint().isFixed());
1833                 assert(!(newComponent instanceof  InlineComponent && !newComponent.getControlPoint().isFixed()));
1834                 PipeControlPoint newCP = newComponent.getControlPoint();
1835                 PipeControlPoint splittingCP = splittingComponent.getControlPoint();
1836                 PipeControlPoint nextCP = splittingCP.getNext();
1837                 PipeControlPoint prevCP = splittingCP.getPrevious();
1838                 
1839                 /* there are many different cases to insert new component when
1840                    it splits existing VariableLengthinlineComponent.
1841             
1842                1. VariableLengthComponet is connected from both sides:
1843                   - insert new component between VariableLength component and component connected to it
1844                   - insert new VariableLengthComponent between inserted component and component selected in previous step
1845                 
1846                    2. VariableLengthComponent is connected from one side
1847                      - Use previous case or:
1848                      - Insert new component to empty end
1849                      - Insert new VariableLength component to inserted components empty end
1850                      
1851                    3. VariableLength is not connected to any component.
1852                      - Should not be possible, at least in current implementation.
1853                      - Could be done using second case
1854
1855                 */
1856                 
1857                 if (nextCP == null && prevCP == null) {
1858                         // this should not be possible
1859                         throw new RuntimeException("VariableLengthComponent " + splittingComponent + " is not connected to anything.");
1860                 }
1861                 double reservedLength = splittingComponent.getControlPoint().getLength();
1862                 double newLength = newComponent.getControlPoint().getLength();
1863                 
1864                 
1865                 Point3d next = new Point3d();
1866                 Point3d prev = new Point3d();
1867                 splittingCP.getInlineControlPointEnds(prev, next);
1868                 
1869                 Vector3d newPos = null;
1870                 if (assignPos) {
1871                         newPos = new Vector3d(prev);
1872                         Vector3d dir = new Vector3d(next);
1873                         dir.sub(prev);
1874                         dir.scale(0.5);
1875                         newPos.add(dir);
1876                         newComponent.setWorldPosition(newPos);
1877                 } else {
1878                         newPos = newComponent.getWorldPosition();
1879                 }
1880                 
1881                 
1882
1883                 Vector3d dir = new Vector3d(next);
1884                 dir.sub(prev);
1885                 dir.normalize();
1886                 dir.scale(newLength * 0.5);
1887                 Point3d vn = new Point3d(newPos);
1888                 Point3d vp = new Point3d(newPos);
1889                 vn.add(dir);
1890                 vp.sub(dir);
1891                 double ln = vn.distance(next);
1892                 double lp = vp.distance(prev);
1893                 vp.interpolate(prev, 0.5);
1894                 vn.interpolate(next, 0.5);
1895                 
1896                 
1897                 PipeControlPoint newVariableLengthCP = null;//insertStraight(pcp1, pcp2, pos, length);
1898                 if (nextCP == null) {
1899                         newCP.insert(splittingCP, Direction.NEXT);
1900                         newVariableLengthCP = insertStraight(newCP, Direction.NEXT, new Vector3d(vn), ln);
1901                         splittingCP.setWorldPosition(new Vector3d(vp));
1902 //                      ControlPointTools.setWorldPosition(splittingCP, vp);
1903 //                      splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp);
1904                 } else if (prevCP == null) {
1905                         newCP.insert(splittingCP, Direction.PREVIOUS);
1906                         newVariableLengthCP = insertStraight(newCP, Direction.PREVIOUS, new Vector3d(vp), lp);
1907                         splittingCP.setWorldPosition(new Vector3d(vn));
1908 //                      splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, ln);
1909                 } else {
1910                         newCP.insert(splittingCP, nextCP);
1911                         newVariableLengthCP = insertStraight(newCP, nextCP, new Vector3d(vn), ln);
1912                         splittingCP.setWorldPosition(new Vector3d(vp));
1913 //                      splittingCP.setRelatedScalarDouble(ProcessResource.plant3Dresource.HasLength, lp);
1914                 }
1915                 positionUpdate(newCP);
1916
1917         }
1918         
1919         public static void addSizeChange(boolean reversed, PipeRun pipeRun, PipeRun other, InlineComponent reducer, PipeControlPoint previous, PipeControlPoint next) {
1920                 PipeControlPoint pcp = reducer.getControlPoint();
1921                 PipeControlPoint ocp = pcp.getSubPoint().get(0);
1922                 if (!reversed) {
1923                         String name = pipeRun.getUniqueName("Reducer");
1924                         reducer.setName(name);
1925                         pipeRun.addChild(reducer);
1926                         other.addChild(ocp);
1927                         reducer.setAlternativePipeRun(other);
1928                         
1929                         previous.setNext(pcp);
1930                         pcp.setPrevious(previous);
1931                         ocp.setPrevious(previous);
1932                         if (next != null) {
1933                                 pcp.setNext(next);
1934                                 ocp.setNext(next);
1935                                 next.setPrevious(ocp);
1936                         }
1937                 } else {
1938                         String name = other.getUniqueName("Reducer");
1939                         reducer.setName(name);
1940                         other.addChild(reducer);
1941                         pipeRun.addChild(ocp);
1942                         reducer.setAlternativePipeRun(pipeRun);
1943                         
1944                         if (next != null) {
1945                                 next.setNext(pcp);
1946                                 pcp.setPrevious(next);
1947                                 ocp.setPrevious(next);
1948                         }
1949                         pcp.setNext(previous);
1950                         ocp.setNext(previous);
1951                         previous.setPrevious(ocp);
1952                 }
1953         }
1954 }