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