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