3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005, 2006 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.display
;
25 import ij
.measure
.Calibration
;
26 import ij
.measure
.ResultsTable
;
28 import ini
.trakem2
.Project
;
29 import ini
.trakem2
.utils
.IJError
;
30 import ini
.trakem2
.utils
.ProjectToolbar
;
31 import ini
.trakem2
.utils
.Utils
;
32 import ini
.trakem2
.utils
.M
;
33 import ini
.trakem2
.utils
.Search
;
34 import ini
.trakem2
.utils
.Vector3
;
35 import ini
.trakem2
.persistence
.DBObject
;
36 import ini
.trakem2
.vector
.VectorString3D
;
38 import java
.awt
.Color
;
39 import java
.awt
.Rectangle
;
40 import java
.awt
.Polygon
;
41 import java
.awt
.event
.MouseEvent
;
42 import java
.awt
.event
.KeyEvent
;
43 import java
.util
.ArrayList
;
44 import java
.util
.HashSet
;
46 import java
.util
.HashMap
;
47 import java
.util
.Iterator
;
48 import java
.util
.List
;
49 import java
.awt
.Graphics
;
50 import java
.awt
.Graphics2D
;
51 import java
.awt
.Composite
;
52 import java
.awt
.AlphaComposite
;
53 import java
.awt
.geom
.AffineTransform
;
54 import java
.awt
.geom
.Point2D
;
55 import java
.awt
.geom
.Area
;
56 import java
.awt
.Shape
;
58 import javax
.vecmath
.Point3f
;
61 public class Pipe
extends ZDisplayable
implements Line3D
{
63 /**The number of points.*/
64 protected int n_points
;
65 /**The array of clicked points.*/
66 protected double[][] p
;
67 /**The array of left control points, one for each clicked point.*/
68 protected double[][] p_l
;
69 /**The array of right control points, one for each clicked point.*/
70 protected double[][] p_r
;
71 /**The array of interpolated points generated from p, p_l and p_r.*/
72 protected double[][] p_i
= new double[2][0];
73 /**The array of Layers over which the points of this pipe live */
74 protected long[] p_layer
;
75 /**The width of each point. */
76 protected double[] p_width
;
77 /**The interpolated width for each interpolated point. */
78 protected double[] p_width_i
= new double[0];
80 /** Every new Pipe will have, for its first point, the last user-adjusted radius value. */
81 static private double last_radius
= -1;
83 public Pipe(Project project
, String title
, double x
, double y
) {
84 super(project
, title
, x
, y
);
87 p_l
= new double[2][5];
88 p_r
= new double[2][5];
89 p_layer
= new long[5]; // the ids of the layers in which each point lays
90 p_width
= new double[5];
94 /** Construct an unloaded Pipe from the database. Points will be loaded later, when needed. */
95 public Pipe(Project project
, long id
, String title
, double width
, double height
, float alpha
, boolean visible
, Color color
, boolean locked
, AffineTransform at
) {
96 super(project
, id
, title
, locked
, at
, width
, height
);
97 this.visible
= visible
;
99 this.visible
= visible
;
101 this.n_points
= -1; //used as a flag to signal "I have points, but unloaded"
104 /** Construct a Pipe from an XML entry. */
105 public Pipe(Project project
, long id
, HashMap ht
, HashMap ht_links
) {
106 super(project
, id
, ht
, ht_links
);
107 // parse specific data
108 for (Iterator it
= ht
.entrySet().iterator(); it
.hasNext(); ) {
109 Map
.Entry entry
= (Map
.Entry
)it
.next();
110 String key
= (String
)entry
.getKey();
111 String data
= (String
)entry
.getValue();
112 if (key
.equals("d")) {
114 // parse the SVG points data
115 ArrayList al_p
= new ArrayList();
116 ArrayList al_p_r
= new ArrayList();
117 ArrayList al_p_l
= new ArrayList();// needs shifting, inserting one point at the beginning if not closed.
118 // sequence is: M p[0],p[1] C p_r[0],p_r[1] p_l[0],p_l[1] and repeat without the M, and finishes with the last p[0],p[1]. If closed, appended at the end is p_r[0],p_r[1] p_l[0],p_l[1]
120 int i_start
= data
.indexOf('M');
121 int i_end
= data
.indexOf('C');
122 String point
= data
.substring(i_start
+1, i_end
).trim();
127 i_end
= data
.indexOf('C', i_end
+1);
129 i_end
= data
.length() -1;
132 String txt
= data
.substring(i_start
+1, i_end
).trim();
133 // eliminate double spaces
134 while (-1 != txt
.indexOf(" ")) {
135 txt
= txt
.replaceAll(" ", " ");
137 // reduce ", " and " ," to ","
138 txt
= txt
.replaceAll(" ,", ",");
139 txt
= txt
.replaceAll(", ", ",");
141 String
[] points
= txt
.split(" ");
142 if (3 == points
.length
) {
143 al_p_r
.add(points
[0]);
144 al_p_l
.add(points
[1]);
148 Utils
.log("Pipe constructor from XML: error at parsing points.");
150 // example: C 34.5,45.6 45.7,23.0 34.8, 78.0 C ..
152 // fix missing control points
153 al_p_l
.add(0, al_p
.get(0));
154 al_p_r
.add(al_p
.get(al_p
.size() -1));
156 if (!(al_p
.size() == al_p_l
.size() && al_p_l
.size() == al_p_r
.size())) {
157 Utils
.log2("Pipe XML parsing: Disagreement in the number of points:\n\tp.length=" + al_p
.size() + "\n\tp_l.length=" + al_p_l
.size() + "\n\tp_r.length=" + al_p_r
.size());
159 // Now parse the points
160 this.n_points
= al_p
.size();
161 this.p
= new double[2][n_points
];
162 this.p_l
= new double[2][n_points
];
163 this.p_r
= new double[2][n_points
];
164 for (int i
=0; i
<n_points
; i
++) {
165 String
[] sp
= ((String
)al_p
.get(i
)).split(",");
166 p
[0][i
] = Double
.parseDouble(sp
[0]);
167 p
[1][i
] = Double
.parseDouble(sp
[1]);
168 sp
= ((String
)al_p_l
.get(i
)).split(",");
169 p_l
[0][i
] = Double
.parseDouble(sp
[0]);
170 p_l
[1][i
] = Double
.parseDouble(sp
[1]);
171 sp
= ((String
)al_p_r
.get(i
)).split(",");
172 p_r
[0][i
] = Double
.parseDouble(sp
[0]);
173 p_r
[1][i
] = Double
.parseDouble(sp
[1]);
175 } else if (key
.equals("layer_ids")) {
176 // parse comma-separated list of layer ids. Creates empty Layer instances with the proper id, that will be replaced later.
177 String
[] layer_ids
= data
.replaceAll(" ", "").trim().split(",");
178 this.p_layer
= new long[layer_ids
.length
];
179 for (int i
=0; i
<layer_ids
.length
; i
++) {
180 this.p_layer
[i
] = Long
.parseLong(layer_ids
[i
]);
182 } else if (key
.equals("p_width")) {
183 String
[] widths
= data
.replaceAll(" ", "").trim().split(",");
184 this.p_width
= new double[widths
.length
];
185 for (int i
=0; i
<widths
.length
; i
++) {
186 this.p_width
[i
] = Double
.parseDouble(widths
[i
]);
191 this.p_i
= new double[2][0]; // empty
192 this.p_width_i
= new double[0];
193 generateInterpolatedPoints(0.05);
194 if (null == this.p
) {
196 this.p
= new double[2][0];
197 this.p_l
= new double[2][0];
198 this.p_r
= new double[2][0];
199 this.p_width
= new double[0];
200 this.p_layer
= new long[0];
203 if (!(n_points
== p
[0].length
&& p
[0].length
== p_width
.length
&& p_width
.length
== p_layer
.length
)) {
204 Utils
.log2("Pipe at parsing XML: inconsistent number of points for id=" + id
);
205 Utils
.log2("\tn_points: " + n_points
+ " p.length: " + p
[0].length
+ " p_width.length: " + p_width
.length
+ " p_layer.length: " + p_layer
.length
);
209 /**Increase the size of the arrays by 5.*/
210 synchronized private void enlargeArrays() {
212 int length
= p
[0].length
;
214 double[][] p_copy
= new double[2][length
+ 5];
215 double[][] p_l_copy
= new double[2][length
+ 5];
216 double[][] p_r_copy
= new double[2][length
+ 5];
217 long[] p_layer_copy
= new long[length
+ 5];
218 double[] p_width_copy
= new double[length
+ 5];
220 System
.arraycopy(p
[0], 0, p_copy
[0], 0, length
);
221 System
.arraycopy(p
[1], 0, p_copy
[1], 0, length
);
222 System
.arraycopy(p_l
[0], 0, p_l_copy
[0], 0, length
);
223 System
.arraycopy(p_l
[1], 0, p_l_copy
[1], 0, length
);
224 System
.arraycopy(p_r
[0], 0, p_r_copy
[0], 0, length
);
225 System
.arraycopy(p_r
[1], 0, p_r_copy
[1], 0, length
);
226 System
.arraycopy(p_layer
, 0, p_layer_copy
, 0, length
);
227 System
.arraycopy(p_width
, 0, p_width_copy
, 0, length
);
232 this.p_layer
= p_layer_copy
;
233 this.p_width
= p_width_copy
;
236 /**Find a point in an array, with a precision dependent on the magnification. Only points in the current layer are found, the rest are ignored. Returns -1 if none found. */
237 synchronized protected int findPoint(double[][] a
, int x_p
, int y_p
, double magnification
) {
239 double d
= (10.0D
/ magnification
);
241 double min_dist
= Double
.MAX_VALUE
;
242 long i_layer
= Display
.getFrontLayer(this.project
).getId();
243 for (int i
=0; i
<n_points
; i
++) {
244 double dist
= Math
.abs(x_p
- a
[0][i
]) + Math
.abs(y_p
- a
[1][i
]);
245 if (i_layer
== p_layer
[i
] && dist
<= d
&& dist
<= min_dist
) {
253 /**Remove a point from the bezier backbone and its two associated control points.*/
254 synchronized protected void removePoint(int index
) {
255 // check preconditions:
258 } else if (n_points
- 1 == index
) {
262 //one point out (but not the last)
265 // shift all points after 'index' one position to the left:
266 for (int i
=index
; i
<n_points
; i
++) {
267 p
[0][i
] = p
[0][i
+1]; //the +1 doesn't fail ever because the n_points has been adjusted above, but the arrays are still the same size. The case of deleting the last point is taken care above.
269 p_l
[0][i
] = p_l
[0][i
+1];
270 p_l
[1][i
] = p_l
[1][i
+1];
271 p_r
[0][i
] = p_r
[0][i
+1];
272 p_r
[1][i
] = p_r
[1][i
+1];
273 p_layer
[i
] = p_layer
[i
+1];
274 p_width
[i
] = p_width
[i
+1];
279 updateInDatabase("points");
281 /**Calculate distance from one point to another.*/
282 static public double distance(final double x1
, final double y1
,
283 final double x2
, final double y2
) {
284 return (Math
.sqrt((x2
-x1
)*(x2
-x1
) + (y2
-y1
)*(y2
-y1
)));
287 /**Move backbone point by the given deltas.*/
288 protected void dragPoint(int index
, int dx
, int dy
) {
297 /**Set the control points to the same value as the backbone point which they control.*/
298 protected void resetControlPoints(int index
) {
299 p_l
[0][index
] = p
[0][index
];
300 p_l
[1][index
] = p
[1][index
];
301 p_r
[0][index
] = p
[0][index
];
302 p_r
[1][index
] = p
[1][index
];
304 /**Drag a control point and adjust the other, dependent one, in a symmetric way or not.*/
305 protected void dragControlPoint(int index
, int x_d
, int y_d
, double[][] p_dragged
, double[][] p_adjusted
, boolean symmetric
) {
306 //measure hypothenusa: from p to p control
309 //make both points be dragged in parallel, the same distance
310 hypothenusa
= distance(p
[0][index
], p
[1][index
], p_dragged
[0][index
], p_dragged
[1][index
]);
312 //make each point be dragged with its own distance
313 hypothenusa
= distance(p
[0][index
], p
[1][index
], p_adjusted
[0][index
], p_adjusted
[1][index
]);
315 //measure angle: use the point being dragged
316 double angle
= Math
.atan2(p_dragged
[0][index
] - p
[0][index
], p_dragged
[1][index
] - p
[1][index
]) + Math
.PI
;
318 p_dragged
[0][index
] = x_d
;
319 p_dragged
[1][index
] = y_d
;
320 p_adjusted
[0][index
] = p
[0][index
] + hypothenusa
* Math
.sin(angle
); // it's sin and not cos because of stupid Math.atan2 way of delivering angles
321 p_adjusted
[1][index
] = p
[1][index
] + hypothenusa
* Math
.cos(angle
);
324 static private double getFirstWidth() {
325 if (null == Display
.getFront()) return 1;
326 if (-1 != last_radius
) return last_radius
;
327 return 10 / Display
.getFront().getCanvas().getMagnification(); // 10 pixels in the screen
330 /**Add a point either at the end or between two existing points, with accuracy depending on magnification. The width of the new point is that of the closest point after which it is inserted.*/
331 synchronized protected int addPoint(int x_p
, int y_p
, double magnification
, double bezier_finess
, long layer_id
) {
332 if (-1 == n_points
) setupForDisplay(); //reload
333 //lookup closest interpolated point and then get the closest clicked point to it
334 int index
= findClosestPoint(x_p
, y_p
, magnification
, bezier_finess
);
336 if (p
[0].length
== n_points
) {
340 if (0 == n_points
|| 1 == n_points
|| index
+ 1 == n_points
) {
342 p
[0][n_points
] = p_l
[0][n_points
] = p_r
[0][n_points
] = x_p
;
343 p
[1][n_points
] = p_l
[1][n_points
] = p_r
[1][n_points
] = y_p
;
344 p_layer
[n_points
] = layer_id
;
345 p_width
[n_points
] = (0 == n_points ? Pipe
.getFirstWidth() : p_width
[n_points
-1]); // either 1.0 or the same as the last point
347 } else if (-1 == index
) {
348 // decide whether to append at the end or prepend at the beginning
349 // compute distance in the 3D space to the first and last points
350 final Calibration cal
= layer_set
.getCalibration();
351 final double lz
= layer_set
.getLayer(layer_id
).getZ();
352 final double p0z
=layer_set
.getLayer(p_layer
[0]).getZ();
353 final double pNz
=layer_set
.getLayer(p_layer
[n_points
-1]).getZ();
354 double sqdist0
= (p
[0][0] - x_p
) * (p
[0][0] - x_p
) * cal
.pixelWidth
* cal
.pixelWidth
355 + (p
[1][0] - y_p
) * (p
[1][0] - y_p
) * cal
.pixelHeight
* cal
.pixelHeight
356 + (lz
- p0z
) * (lz
- p0z
) * cal
.pixelWidth
* cal
.pixelWidth
;
357 double sqdistN
= (p
[0][n_points
-1] - x_p
) * (p
[0][n_points
-1] - x_p
) * cal
.pixelWidth
* cal
.pixelWidth
358 + (p
[1][n_points
-1] - y_p
) * (p
[1][n_points
-1] - y_p
) * cal
.pixelHeight
* cal
.pixelHeight
359 + (lz
- pNz
) * (lz
- pNz
) * cal
.pixelWidth
* cal
.pixelWidth
;
360 if (sqdistN
< sqdist0
) {
362 p
[0][n_points
] = p_l
[0][n_points
] = p_r
[0][n_points
] = x_p
;
363 p
[1][n_points
] = p_l
[1][n_points
] = p_r
[1][n_points
] = y_p
;
364 p_layer
[n_points
] = layer_id
;
365 p_width
[n_points
] = p_width
[n_points
-1];
368 // prepend at the beginning
369 for (int i
=n_points
-1; i
>-1; i
--) {
372 p_l
[0][i
+1] = p_l
[0][i
];
373 p_l
[1][i
+1] = p_l
[1][i
];
374 p_r
[0][i
+1] = p_r
[0][i
];
375 p_r
[1][i
+1] = p_r
[1][i
];
376 p_width
[i
+1] = p_width
[i
];
377 p_layer
[i
+1] = p_layer
[i
];
379 p
[0][0] = p_l
[0][0] = p_r
[0][0] = x_p
;
380 p
[1][0] = p_l
[1][0] = p_r
[1][0] = y_p
;
381 p_width
[0] = p_width
[1];
382 p_layer
[0] = layer_id
;
385 //debug: I miss gdb/ddd !
386 //Utils.log("p_width.length = " + p_width.length + " || n_points = " + n_points + " || p[0].length = " + p[0].length);
390 Utils.log(" p length = " + p[0].length
391 + "\np_l length = " + p_l[0].length
392 + "\np_r length = " + p_r[0].length
393 + "\np_layer len= " + p_layer.length);
395 index
++; //so it is added after the closest point;
396 // 1 - copy second half of array
397 int sh_length
= n_points
-index
;
398 double[][] p_copy
= new double[2][sh_length
];
399 double[][] p_l_copy
= new double[2][sh_length
];
400 double[][] p_r_copy
= new double[2][sh_length
];
401 long[] p_layer_copy
= new long[sh_length
];
402 double[] p_width_copy
= new double[sh_length
];
403 System
.arraycopy(p
[0], index
, p_copy
[0], 0, sh_length
);
404 System
.arraycopy(p
[1], index
, p_copy
[1], 0, sh_length
);
405 System
.arraycopy(p_l
[0], index
, p_l_copy
[0], 0, sh_length
);
406 System
.arraycopy(p_l
[1], index
, p_l_copy
[1], 0, sh_length
);
407 System
.arraycopy(p_r
[0], index
, p_r_copy
[0], 0, sh_length
);
408 System
.arraycopy(p_r
[1], index
, p_r_copy
[1], 0, sh_length
);
409 //Utils.log2("index=" + index + " sh_length=" + sh_length + " p_layer.length=" + p_layer.length + " p_layer_copy.length=" + p_layer_copy.length + " p_copy[0].length=" + p_copy[0].length);
410 System
.arraycopy(p_layer
, index
, p_layer_copy
, 0, sh_length
);
411 System
.arraycopy(p_width
, index
, p_width_copy
, 0, sh_length
);
412 // 2 - insert value into 'p' (the two control arrays get the same value)
413 p
[0][index
] = p_l
[0][index
] = p_r
[0][index
] = x_p
;
414 p
[1][index
] = p_l
[1][index
] = p_r
[1][index
] = y_p
;
415 p_layer
[index
] = layer_id
;
416 p_width
[index
] = p_width
[index
-1]; // -1 because the index has been increased by 1 above
417 // 3 - copy second half into the array
418 System
.arraycopy(p_copy
[0], 0, p
[0], index
+1, sh_length
);
419 System
.arraycopy(p_copy
[1], 0, p
[1], index
+1, sh_length
);
420 System
.arraycopy(p_l_copy
[0], 0, p_l
[0], index
+1, sh_length
);
421 System
.arraycopy(p_l_copy
[1], 0, p_l
[1], index
+1, sh_length
);
422 System
.arraycopy(p_r_copy
[0], 0, p_r
[0], index
+1, sh_length
);
423 System
.arraycopy(p_r_copy
[1], 0, p_r
[1], index
+1, sh_length
);
424 System
.arraycopy(p_layer_copy
, 0, p_layer
, index
+1, sh_length
);
425 System
.arraycopy(p_width_copy
, 0, p_width
, index
+1, sh_length
);
432 /**Find the closest point to an interpolated point with precision depending upon magnification.*/
433 synchronized protected int findClosestPoint(int x_p
, int y_p
, double magnification
, double bezier_finess
) {
435 double distance_sq
= Double
.MAX_VALUE
;
436 double distance_sq_i
;
437 double max
= 12.0D
/ magnification
;
438 max
= max
* max
; //squaring it
439 for (int i
=0; i
<p_i
[0].length
; i
++) {
440 //see which point is closer (there's no need to calculate the distance by multiplying squares and so on).
441 distance_sq_i
= (p_i
[0][i
] - x_p
)*(p_i
[0][i
] - x_p
) + (p_i
[1][i
] - y_p
)*(p_i
[1][i
] - y_p
);
442 if (distance_sq_i
< max
&& distance_sq_i
< distance_sq
) {
444 distance_sq
= distance_sq_i
;
448 int index_found
= (int)Math
.round((double)index
* bezier_finess
);
449 // correct to be always the one after: count the number of points before reaching the next point
450 if (index
< (index_found
/ bezier_finess
)) {
454 // allow only when the closest index is visible in the current layer
455 // handled at mousePressed now//if (Display.getFrontLayer(this.project).getId() != p_layer[index]) return -1;
459 synchronized protected void generateInterpolatedPoints(double bezier_finess
) {
466 // case there's only one point
468 p_i
= new double[2][1];
471 p_width_i
= new double[1];
472 p_width_i
[0] = p_width
[0];
475 // case there's more: interpolate!
476 p_i
= new double[2][(int)(n
* (1.0D
/bezier_finess
))];
477 p_width_i
= new double[p_i
[0].length
];
478 double t
, f0
, f1
, f2
, f3
;
480 for (int i
=0; i
<n
-1; i
++) {
481 for (t
=0.0D
; t
<1.0D
; t
+= bezier_finess
) {
482 f0
= (1-t
)*(1-t
)*(1-t
);
483 f1
= 3*t
*(1-t
)*(1-t
);
486 p_i
[0][next
] = f0
*p
[0][i
] + f1
*p_r
[0][i
] + f2
*p_l
[0][i
+1] + f3
*p
[0][i
+1];
487 p_i
[1][next
] = f0
*p
[1][i
] + f1
*p_r
[1][i
] + f2
*p_l
[1][i
+1] + f3
*p
[1][i
+1];
488 p_width_i
[next
] = p_width
[i
]*(1-t
) + p_width
[i
+1]*t
;
490 //enlarge if needed (when bezier_finess is not 0.05, it's difficult to predict because of int loss of precision.
491 if (p_i
[0].length
== next
) {
492 double[][] p_i_copy
= new double[2][p_i
[0].length
+ 5];
493 double[] p_width_i_copy
= new double[p_width_i
.length
+ 5];
494 System
.arraycopy(p_i
[0], 0, p_i_copy
[0], 0, p_i
[0].length
);
495 System
.arraycopy(p_i
[1], 0, p_i_copy
[1], 0, p_i
[1].length
);
496 System
.arraycopy(p_width_i
, 0, p_width_i_copy
, 0, p_width_i
.length
);
498 p_width_i
= p_width_i_copy
;
502 // add the last point
503 if (p_i
[0].length
== next
) {
504 double[][] p_i_copy
= new double[2][p_i
[0].length
+ 1];
505 double[] p_width_i_copy
= new double[p_width_i
.length
+ 1];
506 System
.arraycopy(p_i
[0], 0, p_i_copy
[0], 0, p_i
[0].length
);
507 System
.arraycopy(p_i
[1], 0, p_i_copy
[1], 0, p_i
[1].length
);
508 System
.arraycopy(p_width_i
, 0, p_width_i_copy
, 0, p_width_i
.length
);
510 p_width_i
= p_width_i_copy
;
512 p_i
[0][next
] = p
[0][n_points
-1];
513 p_i
[1][next
] = p
[1][n_points
-1];
514 p_width_i
[next
] = p_width
[n_points
-1];
517 if (p_i
[0].length
!= next
) { // 'next' works as a length here
519 double[][] p_i_copy
= new double[2][next
];
520 double[] p_width_i_copy
= new double[next
];
521 System
.arraycopy(p_i
[0], 0, p_i_copy
[0], 0, next
);
522 System
.arraycopy(p_i
[1], 0, p_i_copy
[1], 0, next
);
523 System
.arraycopy(p_width_i
, 0, p_width_i_copy
, 0, next
);
525 p_width_i
= p_width_i_copy
;
529 // synchronizing to protect n_points ... need to wrap it in a lock
530 public void paint(final Graphics2D g
, final double magnification
, final boolean active
, final int channels
, final Layer active_layer
) {
531 if (0 == n_points
) return;
532 if (-1 == n_points
) {
533 // load points from the database
536 //arrange transparency
537 Composite original_composite
= null;
539 original_composite
= g
.getComposite();
540 g
.setComposite(AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, alpha
));
543 // local pointers, since they may be transformed
544 int n_points
= this.n_points
;
545 double[][] p
= this.p
;
546 double[][] p_r
= this.p_r
;
547 double[][] p_l
= this.p_l
;
548 double[][] p_i
= this.p_i
;
549 double[] p_width
= this.p_width
;
550 double[] p_width_i
= this.p_width_i
;
551 if (!this.at
.isIdentity()) {
552 final Object
[] ob
= getTransformedData();
553 p
= (double[][])ob
[0];
554 n_points
= p
[0].length
;
555 p_l
= (double[][])ob
[1];
556 p_r
= (double[][])ob
[2];
557 p_i
= (double[][])ob
[3];
558 p_width
= (double[])ob
[4];
559 p_width_i
= (double[])ob
[5];
562 final boolean no_color_cues
= "true".equals(project
.getProperty("no_color_cues"));
564 final long layer_id
= active_layer
.getId();
568 final int oval_radius
= (int)Math
.ceil(4 / magnification
);
569 final int oval_corr
= (int)Math
.ceil(3 / magnification
);
570 for (int j
=0; j
<n_points
; j
++) { //TODO there is room for optimization, operations are being done twice or 3 times; BUT is the creation of new variables as costly as the calculations? I have no idea.
571 if (layer_id
!= p_layer
[j
]) continue;
572 //draw big ovals at backbone points
573 DisplayCanvas
.drawHandle(g
, (int)p
[0][j
], (int)p
[1][j
], magnification
);
575 //fill small ovals at control points
576 g
.fillOval((int)p_l
[0][j
] -oval_corr
, (int)p_l
[1][j
] -oval_corr
, oval_radius
, oval_radius
);
577 g
.fillOval((int)p_r
[0][j
] -oval_corr
, (int)p_r
[1][j
] -oval_corr
, oval_radius
, oval_radius
);
578 //draw lines between backbone and control points
579 g
.drawLine((int)p
[0][j
], (int)p
[1][j
], (int)p_l
[0][j
], (int)p_l
[1][j
]);
580 g
.drawLine((int)p
[0][j
], (int)p
[1][j
], (int)p_r
[0][j
], (int)p_r
[1][j
]);
583 // paint the tube in 2D:
584 if (n_points
> 1 && p_i
[0].length
> 1) { // need the second check for repaints that happen before generating the interpolated points.
586 double a0
= Math
.toRadians(0);
587 double a90
= Math
.toRadians(90);
588 double a180
= Math
.toRadians(180);
589 double a270
= Math
.toRadians(270);
590 final int n
= p_i
[0].length
;
591 final double[] r_side_x
= new double[n
];
592 final double[] r_side_y
= new double[n
];
593 final double[] l_side_x
= new double[n
];
594 final double[] l_side_y
= new double[n
];
597 for (int i
=0; i
<n
-1; i
++) {
598 angle
= Math
.atan2(p_i
[0][i
+1] - p_i
[0][i
], p_i
[1][i
+1] - p_i
[1][i
]);
600 r_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
+a90
) * p_width_i
[i
]; //sin and cos are inverted, but works better like this. WHY ??
601 r_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
+a90
) * p_width_i
[i
];
602 l_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
-a90
) * p_width_i
[i
];
603 l_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
-a90
) * p_width_i
[i
];
606 angle
= Math
.atan2(p_i
[0][m
] - p_i
[0][m
-1], p_i
[1][m
] - p_i
[1][m
-1]);
608 r_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
+a90
) * p_width_i
[m
];
609 r_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
+a90
) * p_width_i
[m
];
610 l_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
-a90
) * p_width_i
[m
];
611 l_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
-a90
) * p_width_i
[m
];
613 final double z_current
= active_layer
.getZ();
616 // paint a tiny bit where it should!
617 g
.setColor(this.color
);
620 for (int j
=0; j
<n_points
; j
++) { // at least looping through 2 points, as guaranteed by the preconditions checking
622 if (layer_id
== p_layer
[j
]) {
625 // else if crossed the current layer, paint segment as well
626 if (0 == j
) continue;
627 double z1
= layer_set
.getLayer(p_layer
[j
-1]).getZ();
628 double z2
= layer_set
.getLayer(p_layer
[j
]).getZ();
629 if ( (z1
< z_current
&& z_current
< z2
)
630 || (z2
< z_current
&& z_current
< z1
) ) {
631 // paint normally, in this pipe's color
637 double z
= layer_set
.getLayer(p_layer
[j
]).getZ();
638 if (z
< z_current
) g
.setColor(Color
.red
);
639 else if (z
== z_current
) g
.setColor(this.color
);
640 else g
.setColor(Color
.blue
);
645 if (0 != j
) fi
= (j
* 20) - 10; // 10 is half a segment
646 if (n_points
-1 != j
) la
+= 10; // same //= j * 20 + 9;
647 if (la
>= r_side_x
.length
) la
= r_side_x
.length
-2; // quick fix. -2 so that the k+1 below can work
648 if (fi
> la
) fi
= la
;
652 for (int k
=fi
; k
<=la
; k
++) {
653 g
.drawLine((int)r_side_x
[k
], (int)r_side_y
[k
], (int)r_side_x
[k
+1], (int)r_side_y
[k
+1]);
654 g
.drawLine((int)l_side_x
[k
], (int)l_side_y
[k
], (int)l_side_x
[k
+1], (int)l_side_y
[k
+1]);
657 } catch (Exception ee
) {
658 Utils
.log2("Pipe paint failed with: fi=" + fi
+ " la=" + la
+ " n_points=" + n_points
+ " r_side_x.length=" + r_side_x
.length
);
659 // WARNING still something is wrong with the synchronization over the arrays ... despite this error being patched with the line above:
660 // if (la >= r_side_x.length) r_side_x.length-2; // quick fix
662 // ADDING the synchronized keyword to many methods involving n_points did not fix it; neither to crop arrays and get the new n_points from the getTransformedData() returned p array.
667 //Transparency: fix alpha composite back to original.
668 if (null != original_composite
) {
669 g
.setComposite(original_composite
);
673 public void keyPressed(KeyEvent ke
) {
674 //Utils.log2("Pipe.keyPressed not implemented.");
677 /**Helper vars for mouse events. It's safe to have them static since only one Pipe will be edited at a time.*/
678 static private int index
, index_l
, index_r
;
679 static private boolean is_new_point
= false;
681 public void mousePressed(MouseEvent me
, int x_p
, int y_p
, double mag
) {
682 // transform the x_p, y_p to the local coordinates
683 if (!this.at
.isIdentity()) {
684 final Point2D
.Double po
= inverseTransformPoint(x_p
, y_p
);
689 final int tool
= ProjectToolbar
.getToolId();
691 if (ProjectToolbar
.PEN
== tool
) {
693 if (Utils
.isControlDown(me
) && me
.isShiftDown()) {
694 index
= Displayable
.findNearestPoint(p
, n_points
, x_p
, y_p
);
696 index
= findPoint(p
, x_p
, y_p
, mag
);
700 if (Utils
.isControlDown(me
) && me
.isShiftDown() && p_layer
[index
] == Display
.getFrontLayer(this.project
).getId()) {
703 index
= index_r
= index_l
= -1;
707 // Make the radius for newly added point that of the last added
708 last_radius
= p_width
[index
];
710 if (me
.isAltDown()) {
711 resetControlPoints(index
);
716 // find if click is on a left control point
717 index_l
= findPoint(p_l
, x_p
, y_p
, mag
);
719 // if not, then try on the set of right control points
721 index_r
= findPoint(p_r
, x_p
, y_p
, mag
);
724 long layer_id
= Display
.getFrontLayer(this.project
).getId();
726 if (-1 != index
&& layer_id
!= p_layer
[index
]) index
= -1; // disable!
727 else if (-1 != index_l
&& layer_id
!= p_layer
[index_l
]) index_l
= -1;
728 else if (-1 != index_r
&& layer_id
!= p_layer
[index_r
]) index_r
= -1;
729 //if no conditions are met, attempt to add point
730 else if (-1 == index
&& -1 == index_l
&& -1 == index_r
&& !me
.isShiftDown() && !me
.isAltDown()) {
731 //add a new point and assign its index to the left control point, so it can be dragged right away. This is copying the way Karbon does for drawing Bezier curves, which is the contrary to Adobe's way, but which I find more useful.
732 index_l
= addPoint(x_p
, y_p
, mag
, 0.05, layer_id
);
734 if (0 == index_l
) { //1 == n_points)
735 //for the very first point, drag the right control point, not the left.
739 generateInterpolatedPoints(0.05);
747 public void mouseDragged(MouseEvent me
, int x_p
, int y_p
, int x_d
, int y_d
, int x_d_old
, int y_d_old
) {
748 // transform to the local coordinates
749 if (!this.at
.isIdentity()) {
750 final Point2D
.Double p
= inverseTransformPoint(x_p
, y_p
);
753 final Point2D
.Double pd
= inverseTransformPoint(x_d
, y_d
);
756 final Point2D
.Double pdo
= inverseTransformPoint(x_d_old
, y_d_old
);
757 x_d_old
= (int)pdo
.x
;
758 y_d_old
= (int)pdo
.y
;
761 final int tool
= ProjectToolbar
.getToolId();
763 if (ProjectToolbar
.PEN
== tool
) {
764 //if a point in the backbone is found, then:
766 if (!me
.isAltDown() && !me
.isShiftDown()) {
767 dragPoint(index
, x_d
- x_d_old
, y_d
- y_d_old
);
768 } else if (me
.isShiftDown()) {
770 p_width
[index
] = Math
.sqrt((x_d
- p
[0][index
])*(x_d
- p
[0][index
]) + (y_d
- p
[1][index
])*(y_d
- p
[1][index
]));
771 last_radius
= p_width
[index
];
772 Utils
.showStatus("radius: " + p_width
[index
], false);
773 } else { //TODO in linux the alt+click is stolen by the KDE window manager but then the middle-click works as if it was the alt+click. Weird!
774 //drag both control points symmetrically
775 dragControlPoint(index
, x_d
, y_d
, p_l
, p_r
, true);
777 generateInterpolatedPoints(0.05);
782 //if a control point is found, then drag it, adjusting the other control point non-symmetrically
784 dragControlPoint(index_r
, x_d
, y_d
, p_r
, p_l
, is_new_point
); //((index_r != n_points-1)?false:true)); //last point gets its 2 control points dragged symetrically
785 generateInterpolatedPoints(0.05);
790 dragControlPoint(index_l
, x_d
, y_d
, p_l
, p_r
, is_new_point
); //((index_l != n_points-1)?false:true)); //last point gets its 2 control points dragged symetrically
791 generateInterpolatedPoints(0.05);
798 public void mouseReleased(MouseEvent me
, int x_p
, int y_p
, int x_d
, int y_d
, int x_r
, int y_r
) {
800 final int tool
= ProjectToolbar
.getToolId();
802 if (ProjectToolbar
.PEN
== tool
) {
803 //generate interpolated points
804 generateInterpolatedPoints(0.05);
805 repaint(); //needed at least for the removePoint
808 //update points in database if there was any change
809 if (-1 != index
|| -1 != index_r
|| -1 != index_l
) {
810 //updateInDatabase("position");
812 // update all points, since the index may have changed
813 updateInDatabase("points");
814 } else if (-1 != index
&& index
!= n_points
) { //second condition happens when the last point has been removed
815 updateInDatabase(getUpdatePointForSQL(index
));
816 } else if (-1 != index_r
) {
817 updateInDatabase(getUpdateRightControlPointForSQL(index_r
));
818 } else if (-1 != index_l
) {
819 updateInDatabase(getUpdateLeftControlPointForSQL(index_l
));
820 } else if (index
!= n_points
) { // don't do it when the last point is removed
822 updateInDatabase("points");
824 updateInDatabase("dimensions");
825 } else if (x_r
!= x_p
|| y_r
!= y_p
) {
826 updateInDatabase("dimensions");
829 //Display.repaint(layer, this, 5); // the entire Displayable object
833 is_new_point
= false;
834 index
= index_r
= index_l
= -1;
837 synchronized protected void calculateBoundingBox(final boolean adjust_position
) {
838 double min_x
= Double
.MAX_VALUE
;
839 double min_y
= Double
.MAX_VALUE
;
843 this.width
= this.height
= 0;
844 layer_set
.updateBucket(this);
847 // get perimeter of the tube, without the transform
848 final Polygon pol
= Pipe
.getRawPerimeter(p_i
, p_width_i
);
849 if (null != pol
&& 0 != pol
.npoints
) {
851 for (int i
=0; i
<pol
.npoints
; i
++) {
852 if (pol
.xpoints
[i
] < min_x
) min_x
= pol
.xpoints
[i
];
853 if (pol
.ypoints
[i
] < min_y
) min_y
= pol
.ypoints
[i
];
854 if (pol
.xpoints
[i
] > max_x
) max_x
= pol
.xpoints
[i
];
855 if (pol
.ypoints
[i
] > max_y
) max_y
= pol
.ypoints
[i
];
858 // check the control points
859 for (int i
=0; i
<n_points
; i
++) {
860 if (p_l
[0][i
] < min_x
) min_x
= p_l
[0][i
];
861 if (p_r
[0][i
] < min_x
) min_x
= p_r
[0][i
];
862 if (p_l
[1][i
] < min_y
) min_y
= p_l
[1][i
];
863 if (p_r
[1][i
] < min_y
) min_y
= p_r
[1][i
];
864 if (p_l
[0][i
] > max_x
) max_x
= p_l
[0][i
];
865 if (p_r
[0][i
] > max_x
) max_x
= p_r
[0][i
];
866 if (p_l
[1][i
] > max_y
) max_y
= p_l
[1][i
];
867 if (p_r
[1][i
] > max_y
) max_y
= p_r
[1][i
];
870 this.width
= max_x
- min_x
;
871 this.height
= max_y
- min_y
;
873 if (adjust_position
) {
874 // now readjust points to make min_x,min_y be the x,y
875 for (int i
=0; i
<n_points
; i
++) {
876 p
[0][i
] -= min_x
; p
[1][i
] -= min_y
;
877 p_l
[0][i
] -= min_x
; p_l
[1][i
] -= min_y
;
878 p_r
[0][i
] -= min_x
; p_r
[1][i
] -= min_y
;
880 for (int i
=0; i
<p_i
[0].length
; i
++) {
881 p_i
[0][i
] -= min_x
; p_i
[1][i
] -= min_y
;
883 this.at
.translate(min_x
, min_y
); // not using super.translate(...) because a preConcatenation is not needed; here we deal with the data.
884 updateInDatabase("transform");
886 updateInDatabase("dimensions");
888 layer_set
.updateBucket(this);
891 /**Release all memory resources taken by this object.*/
892 synchronized public void destroy() {
903 /**Release memory resources used by this object: namely the arrays of points, which can be reloaded with a call to setupForDisplay()*/
904 synchronized public void flush() {
911 n_points
= -1; // flag that points exist but are not loaded
914 public void repaint() {
918 /**Repaints in the given ImageCanvas only the area corresponding to the bounding box of this Pipe. */
919 public void repaint(boolean repaint_navigator
) {
920 //TODO: this could be further optimized to repaint the bounding box of the last modified segments, i.e. the previous and next set of interpolated points of any given backbone point. This would be trivial if each segment of the Bezier curve was an object.
921 Rectangle box
= getBoundingBox(null);
922 calculateBoundingBox(true);
923 box
.add(getBoundingBox(null));
924 Display
.repaint(layer_set
, this, box
, 5, repaint_navigator
);
927 /**Make this object ready to be painted.*/
928 synchronized private void setupForDisplay() {
930 if (null == p
|| null == p_l
|| null == p_r
) {
931 ArrayList al
= project
.getLoader().fetchPipePoints(id
);
932 n_points
= al
.size();
933 p
= new double[2][n_points
];
934 p_l
= new double[2][n_points
];
935 p_r
= new double[2][n_points
];
936 p_layer
= new long[n_points
];
937 p_width
= new double[n_points
];
938 Iterator it
= al
.iterator();
940 while (it
.hasNext()) {
941 Object
[] ob
= (Object
[])it
.next();
942 p
[0][i
] = ((Double
)ob
[0]).doubleValue();
943 p
[1][i
] = ((Double
)ob
[1]).doubleValue();
944 p_r
[0][i
] = ((Double
)ob
[2]).doubleValue();
945 p_r
[1][i
] = ((Double
)ob
[3]).doubleValue();
946 p_l
[0][i
] = ((Double
)ob
[4]).doubleValue();
947 p_l
[1][i
] = ((Double
)ob
[5]).doubleValue();
948 p_width
[i
] = ((Double
)ob
[6]).doubleValue();
949 p_layer
[i
] = ((Long
)ob
[7]).longValue();
952 // recreate interpolated points
953 generateInterpolatedPoints(0.05); //TODO adjust this or make it read the value from the Project perhaps.
957 static private final Polygon
getRawPerimeter(final double[][] p_i
, final double[] p_width_i
) {
958 final int n
= p_i
[0].length
; // the number of interpolated points
959 if (n
< 2) return null;
961 final double a0
= Math
.toRadians(0);
962 final double a90
= Math
.toRadians(90);
963 final double a180
= Math
.toRadians(180);
964 final double a270
= Math
.toRadians(270);
965 double[] r_side_x
= new double[n
];
966 double[] r_side_y
= new double[n
];
967 double[] l_side_x
= new double[n
];
968 double[] l_side_y
= new double[n
];
971 for (int i
=0; i
<n
-1; i
++) {
972 angle
= Math
.atan2(p_i
[0][i
+1] - p_i
[0][i
], p_i
[1][i
+1] - p_i
[1][i
]);
974 r_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
+a90
) * p_width_i
[i
]; //sin and cos are inverted, but works better like this. WHY ??
975 r_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
+a90
) * p_width_i
[i
];
976 l_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
-a90
) * p_width_i
[i
];
977 l_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
-a90
) * p_width_i
[i
];
981 angle
= Math
.atan2(p_i
[0][m
] - p_i
[0][m
-1], p_i
[1][m
] - p_i
[1][m
-1]);
983 r_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
+a90
) * p_width_i
[m
];
984 r_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
+a90
) * p_width_i
[m
];
985 l_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
-a90
) * p_width_i
[m
];
986 l_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
-a90
) * p_width_i
[m
];
988 int[] pol_x
= new int[n
* 2];
989 int[] pol_y
= new int[n
* 2];
990 for (int j
=0; j
<n
; j
++) {
991 pol_x
[j
] = (int)r_side_x
[j
];
992 pol_y
[j
] = (int)r_side_y
[j
];
993 pol_x
[n
+ j
] = (int)l_side_x
[m
-j
];
994 pol_y
[n
+ j
] = (int)l_side_y
[m
-j
];
996 return new Polygon(pol_x
, pol_y
, pol_x
.length
);
999 /** The exact perimeter of this pipe, in integer precision. */
1000 synchronized public Polygon
getPerimeter() {
1001 if (null == p_i
|| p_i
[0].length
< 2) return new Polygon(); // meaning: if there aren't any interpolated points
1003 // local pointers, since they may be transformed
1004 int n_points
= this.n_points
;
1005 //double[][] p = this.p;
1006 //double[][] p_r = this.p_r;
1007 //double[][] p_l = this.p_l;
1008 double[][] p_i
= this.p_i
;
1009 //double[] p_width = this.p_width;
1010 double[] p_width_i
= this.p_width_i
;
1011 if (!this.at
.isIdentity()) {
1012 final Object
[] ob
= getTransformedData();
1013 //p = (double[][])ob[0];
1014 n_points
= p
[0].length
;
1015 //p_l = (double[][])ob[1];
1016 //p_r = (double[][])ob[2];
1017 p_i
= (double[][])ob
[3];
1018 //p_width = (double[])ob[4];
1019 p_width_i
= (double[])ob
[5];
1021 return Pipe
.getRawPerimeter(p_i
, p_width_i
);
1024 /** Writes the data of this object as a Pipe object in the .shapes file represented by the 'data' StringBuffer. */
1025 synchronized public void toShapesFile(StringBuffer data
, String group
, String color
, double z_scale
) {
1026 if (-1 == n_points
) setupForDisplay(); // reload
1027 final char l
= '\n';
1028 // local pointers, since they may be transformed
1029 int n_points
= this.n_points
;
1030 double[][] p
= this.p
;
1031 double[][] p_r
= this.p_r
;
1032 double[][] p_l
= this.p_l
;
1033 double[] p_width
= this.p_width
;
1034 if (!this.at
.isIdentity()) {
1035 final Object
[] ob
= getTransformedData();
1036 p
= (double[][])ob
[0];
1037 n_points
= p
[0].length
;
1038 p_l
= (double[][])ob
[1];
1039 p_r
= (double[][])ob
[2];
1040 p_width
= (double[])ob
[4];
1042 data
.append("type=pipe").append(l
)
1043 .append("name=").append(project
.getMeaningfulTitle(this)).append(l
)
1044 .append("group=").append(group
).append(l
)
1045 .append("color=").append(color
).append(l
)
1046 .append("supergroup=").append("null").append(l
)
1047 .append("supercolor=").append("null").append(l
)
1049 for (int i
=0; i
<n_points
; i
++) {
1050 data
.append("p x=").append(p
[0][i
]).append(l
)
1051 .append("p y=").append(p
[1][i
]).append(l
)
1052 .append("p_r x=").append(p_r
[0][i
]).append(l
)
1053 .append("p_r y=").append(p_r
[1][i
]).append(l
)
1054 .append("p_l x=").append(p_l
[0][i
]).append(l
)
1055 .append("p_l y=").append(p_l
[1][i
]).append(l
)
1056 .append("z=").append(layer_set
.getLayer(p_layer
[i
]).getZ() * z_scale
).append(l
)
1057 .append("width=").append(p_width
[i
]).append(l
)
1062 /** Return the list of query statements needed to insert all the points in the database. */
1063 synchronized public String
[] getPointsForSQL() {
1064 String
[] sql
= new String
[n_points
];
1065 for (int i
=0; i
<n_points
; i
++) {
1066 StringBuffer sb
= new StringBuffer("INSERT INTO ab_pipe_points (pipe_id, index, x, y, x_r, y_r, x_l, y_l, width, layer_id) VALUES (");
1067 sb
.append(this.id
).append(",")
1068 .append(i
).append(",")
1069 .append(p
[0][i
]).append(",")
1070 .append(p
[1][i
]).append(",")
1071 .append(p_r
[0][i
]).append(",")
1072 .append(p_r
[1][i
]).append(",")
1073 .append(p_l
[0][i
]).append(",")
1074 .append(p_l
[1][i
]).append(",")
1075 .append(p_width
[i
]).append(",")
1079 sql
[i
] = sb
.toString();
1084 synchronized public String
getUpdatePointForSQL(int index
) {
1085 if (index
< 0 || index
> n_points
-1) return null;
1087 StringBuffer sb
= new StringBuffer("UPDATE ab_pipe_points SET ");
1088 sb
.append("x=").append(p
[0][index
])
1089 .append(", y=").append(p
[1][index
])
1090 .append(", x_r=").append(p_r
[0][index
])
1091 .append(", y_r=").append(p_r
[1][index
])
1092 .append(", x_l=").append(p_l
[0][index
])
1093 .append(", y_l=").append(p_l
[1][index
])
1094 .append(", width=").append(p_width
[index
])
1095 .append(", layer_id=").append(p_layer
[index
])
1096 .append(" WHERE pipe_id=").append(this.id
)
1097 .append(" AND index=").append(index
)
1099 return sb
.toString();
1102 String
getUpdateLeftControlPointForSQL(int index
) {
1103 if (index
< 0 || index
> n_points
-1) return null;
1105 StringBuffer sb
= new StringBuffer("UPDATE ab_pipe_points SET ");
1106 sb
.append("x_l=").append(p_l
[0][index
])
1107 .append(", y_l=").append(p_l
[1][index
])
1108 .append(" WHERE pipe_id=").append(this.id
)
1109 .append(" AND index=").append(index
)
1111 return sb
.toString();
1114 String
getUpdateRightControlPointForSQL(int index
) {
1115 if (index
< 0 || index
> n_points
-1) return null;
1117 StringBuffer sb
= new StringBuffer("UPDATE ab_pipe_points SET ");
1118 sb
.append("x_r=").append(p_r
[0][index
])
1119 .append(", y_r=").append(p_r
[1][index
])
1120 .append(" WHERE pipe_id=").append(this.id
)
1121 .append(" AND index=").append(index
)
1123 return sb
.toString();
1126 public boolean isDeletable() {
1127 return 0 == n_points
;
1130 /** The number of clicked, backbone points in this pipe. */
1131 public int length() {
1132 if (-1 == n_points
) setupForDisplay();
1136 /** Test whether the Pipe contains the given point at the given layer. What it does: generates subpolygons that are present in the given layer, and tests whether the point is contained in any of them. */
1137 public boolean contains(final Layer layer
, int x
, int y
) {
1138 if (-1 == n_points
) setupForDisplay(); // reload points
1139 if (0 == n_points
) return false;
1141 final Point2D
.Double po
= inverseTransformPoint(x
, y
);
1144 if (1 == n_points
) {
1145 if (Math
.abs(p
[0][0] - x
) < 3 && Math
.abs(p
[1][0] - y
) < 3) return true; // error in clicked precision of 3 pixels
1148 final boolean no_color_cues
= "true".equals(project
.getProperty("no_color_cues"));
1151 double a0
= Math
.toRadians(0);
1152 double a90
= Math
.toRadians(90);
1153 double a180
= Math
.toRadians(180);
1154 double a270
= Math
.toRadians(270);
1155 int n
= p_i
[0].length
; // the number of interpolated points
1156 final double[] r_side_x
= new double[n
];
1157 final double[] r_side_y
= new double[n
];
1158 final double[] l_side_x
= new double[n
];
1159 final double[] l_side_y
= new double[n
];
1162 for (int i
=0; i
<n
-1; i
++) {
1163 angle
= Math
.atan2(p_i
[0][i
+1] - p_i
[0][i
], p_i
[1][i
+1] - p_i
[1][i
]);
1165 // side points, displaced by this.x, this.y
1166 r_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
+a90
) * p_width_i
[i
]; //sin and cos are inverted, but works better like this. WHY ??
1167 r_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
+a90
) * p_width_i
[i
];
1168 l_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
-a90
) * p_width_i
[i
];
1169 l_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
-a90
) * p_width_i
[i
];
1173 angle
= Math
.atan2(p_i
[0][m
] - p_i
[0][m
-1], p_i
[1][m
] - p_i
[1][m
-1]);
1175 // side points, displaced by this.x, this.y
1176 r_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
+a90
) * p_width_i
[m
];
1177 r_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
+a90
) * p_width_i
[m
];
1178 l_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
-a90
) * p_width_i
[m
];
1179 l_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
-a90
) * p_width_i
[m
];
1181 final long layer_id
= layer
.getId();
1182 final double z_current
= layer
.getZ();
1184 int first
= 0; // the first backbone point in the subpolygon present in the layer
1185 int last
= 0; // the last backbone point in the subpolygon present in the layer
1187 boolean add_pol
= false;
1189 for (int j
=0; j
<n_points
; j
++) { // at least looping through 2 points, as guaranteed by the preconditions checking
1190 if (!no_color_cues
&& layer_id
!= p_layer
[j
]) {
1191 first
= j
+ 1; // perhaps next, not this j
1195 // if j is last point, or the next point won't be in the same layer:
1196 if (j
== n_points
-1 || layer_id
!= p_layer
[j
+1]) {
1200 if (no_color_cues
) {
1201 // else if crossed the current layer, check segment as well
1202 if (0 == j
) continue;
1204 final double z1
= layer_set
.getLayer(p_layer
[j
-1]).getZ();
1205 final double z2
= layer_set
.getLayer(p_layer
[j
]).getZ();
1207 // 20 points is the length of interpolated points between any two backbone bezier 'j' points.
1208 // 10 points is half that.
1210 // Painting with cues paints the points on the layer padded 10 points to before and after, and when no points are in the layer, then just some padded area in between.
1212 // When approaching from below or from above, or when leaving torwards below or towards above, the segment to check is only half the length, as painted.
1214 if (z1
== z_current
&& z_current
== z2
) {
1216 fi
= ((j
-1) * 20) - 10;
1217 la
= ( j
* 20) + 10;
1218 } else if ( (z1
< z_current
&& z_current
== z2
)
1219 || (z1
> z_current
&& z_current
== z2
) ) {
1221 fi
= ((j
-1) * 20) + 10;
1222 la
= ( j
* 20) + 10;
1223 } else if ( (z1
== z_current
&& z_current
< z2
)
1224 || (z1
== z_current
&& z_current
> z2
) ) {
1226 fi
= ((j
-1) * 20) - 10;
1227 la
= ( j
* 20) - 10;
1228 } else if ( (z1
< z_current
&& z_current
< z2
)
1229 || (z1
> z_current
&& z_current
> z2
) ) {
1231 // crossing by without a point: short polygons
1232 fi
= ((j
-1) * 20) + 10;
1233 la
= ( j
* 20) - 10;
1239 if (0 == j
-1) fi
= 0;
1240 if (n_points
-1 == j
) la
= n_points
* 20;
1243 // compute sub polygon
1244 if (!no_color_cues
) {
1247 if (0 != first
) fi
= (first
* 20) - 10; // 10 is half a segment
1248 if (n_points
-1 != last
) la
+= 10; // same //= last * 20 + 9;
1252 if (la
>= r_side_x
.length
) la
= r_side_x
.length
-1; // quick fix
1253 final int length
= la
- fi
+ 1; // +1 because fi and la are indices
1255 final int [] pol_x
= new int[length
* 2];
1256 final int [] pol_y
= new int[length
* 2];
1257 for (int k
=0, g
=fi
; g
<=la
; g
++, k
++) {
1258 pol_x
[k
] = (int)r_side_x
[g
];
1259 pol_y
[k
] = (int)r_side_y
[g
];
1260 pol_x
[length
+ k
] = (int)l_side_x
[la
- k
];
1261 pol_y
[length
+ k
] = (int)l_side_y
[la
- k
];
1263 final Polygon pol
= new Polygon(pol_x
, pol_y
, pol_x
.length
);
1264 if (pol
.contains(x
, y
)) {
1265 //Utils.log2("first, last : " + first + ", " + last);
1277 /** Get the perimeter of all parts that show in the given layer (as defined by its Z). Returns null if none found. */
1278 private Polygon
[] getSubPerimeters(final Layer layer
) {
1279 if (n_points
<= 1) return null;
1281 // local pointers, since they may be transformed
1282 int n_points
= this.n_points
;
1283 double[][] p
= this.p
;
1284 double[][] p_r
= this.p_r
;
1285 double[][] p_l
= this.p_l
;
1286 double[][] p_i
= this.p_i
;
1287 double[] p_width
= this.p_width
;
1288 double[] p_width_i
= this.p_width_i
;
1289 if (!this.at
.isIdentity()) {
1290 final Object
[] ob
= getTransformedData();
1291 p
= (double[][])ob
[0];
1292 n_points
= p
[0].length
;
1293 p_l
= (double[][])ob
[1];
1294 p_r
= (double[][])ob
[2];
1295 p_i
= (double[][])ob
[3];
1296 p_width
= (double[])ob
[4];
1297 p_width_i
= (double[])ob
[5];
1301 double a0
= Math
.toRadians(0);
1302 double a90
= Math
.toRadians(90);
1303 double a180
= Math
.toRadians(180);
1304 double a270
= Math
.toRadians(270);
1305 int n
= p_i
[0].length
; // the number of interpolated points
1306 double[] r_side_x
= new double[n
];
1307 double[] r_side_y
= new double[n
];
1308 double[] l_side_x
= new double[n
];
1309 double[] l_side_y
= new double[n
];
1312 for (int i
=0; i
<n
-1; i
++) {
1313 angle
= Math
.atan2(p_i
[0][i
+1] - p_i
[0][i
], p_i
[1][i
+1] - p_i
[1][i
]);
1316 r_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
+a90
) * p_width_i
[i
]; //sin and cos are inverted, but works better like this. WHY ??
1317 r_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
+a90
) * p_width_i
[i
];
1318 l_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
-a90
) * p_width_i
[i
];
1319 l_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
-a90
) * p_width_i
[i
];
1323 angle
= Math
.atan2(p_i
[0][m
] - p_i
[0][m
-1], p_i
[1][m
] - p_i
[1][m
-1]);
1325 // side points, displaced by this.x, this.y
1326 r_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
+a90
) * p_width_i
[m
];
1327 r_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
+a90
) * p_width_i
[m
];
1328 l_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
-a90
) * p_width_i
[m
];
1329 l_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
-a90
) * p_width_i
[m
];
1331 final long layer_id
= layer
.getId();
1332 //double z_given = layer.getZ();
1334 int first
= 0; // the first backbone point in the subpolygon present in the layer
1335 int last
= 0; // the last backbone point in the subpolygon present in the layer
1337 boolean add_pol
= false;
1338 final ArrayList al
= new ArrayList();
1340 for (int j
=0; j
<n_points
; j
++) {
1341 if (layer_id
!= p_layer
[j
]) {
1342 first
= j
+ 1; // perhaps next, not this j
1346 // if j is last point, or the next point won't be in the same layer:
1347 if (j
== n_points
-1 || layer_id
!= p_layer
[j
+1]) {
1351 // compute sub polygon
1353 int la
= last
* 20 -1;
1354 if (0 != first
) fi
= (first
* 20) - 10; // 10 is half a segment
1355 if (n_points
-1 != last
) la
+= 10; // same //= last * 20 + 9;
1356 int length
= la
- fi
+ 1; // +1 because fi and la are indexes
1358 int [] pol_x
= new int[length
* 2];
1359 int [] pol_y
= new int[length
* 2];
1360 for (int k
=0, g
=fi
; g
<=la
; g
++, k
++) {
1361 pol_x
[k
] = (int)r_side_x
[g
];
1362 pol_y
[k
] = (int)r_side_y
[g
];
1363 pol_x
[length
+ k
] = (int)l_side_x
[la
- k
];
1364 pol_y
[length
+ k
] = (int)l_side_y
[la
- k
];
1366 al
.add(new Polygon(pol_x
, pol_y
, pol_x
.length
));
1372 if (al
.isEmpty()) return null;
1374 final Polygon
[] pols
= new Polygon
[al
.size()];
1380 // scan the Display and link Patch objects that lay under this Pipe's bounding box:
1381 public void linkPatches() { // TODO needs to check all layers!!
1382 // SHOULD check on every layer where there is a subperimeter. This method below will work only while the Display is not switching layer before deselecting this pipe (which calls linkPatches)
1384 // find the patches that don't lay under other profiles of this profile's linking group, and make sure they are unlinked. This will unlink any Patch objects under this Pipe:
1385 unlinkAll(Patch
.class);
1387 HashSet hs
= new HashSet();
1388 for (int l
=0; l
<n_points
; l
++) {
1389 // avoid repeating the ones that have been done
1390 Long lo
= new Long(p_layer
[l
]); // in blankets ...
1391 if (hs
.contains(lo
)) continue;
1394 Layer layer
= layer_set
.getLayer(p_layer
[l
]);
1396 // this bounding box as in the current layer
1397 final Polygon
[] perimeters
= getSubPerimeters(layer
);
1398 if (null == perimeters
) continue;
1400 // catch all displayables of the current Layer
1401 final ArrayList al
= layer
.getDisplayables(Patch
.class);
1403 // for each Patch, check if it underlays this profile's bounding box
1404 final Rectangle box
= new Rectangle();
1405 for (Iterator itd
= al
.iterator(); itd
.hasNext(); ) {
1406 final Displayable displ
= (Displayable
)itd
.next();
1407 // stupid java, Polygon cannot test for intersection with another Polygon !! //if (perimeter.intersects(displ.getPerimeter())) // TODO do it yourself: check if a Displayable intersects another Displayable
1408 for (int i
=0; i
<perimeters
.length
; i
++) {
1409 if (perimeters
[i
].intersects(displ
.getBoundingBox(box
))) {
1412 break; // no need to check more perimeters
1419 /** Returns the layer of lowest Z coordinate where this ZDisplayable has a point in, or the creation layer if no points yet. */
1420 public Layer
getFirstLayer() {
1421 if (0 == n_points
) return this.layer
;
1422 if (-1 == n_points
) setupForDisplay(); //reload
1423 Layer la
= this.layer
;
1424 double z
= Double
.MAX_VALUE
;
1425 for (int i
=0; i
<n_points
; i
++) {
1426 Layer layer
= layer_set
.getLayer(p_layer
[i
]);
1427 if (layer
.getZ() < z
) la
= layer
;
1432 synchronized public void exportSVG(StringBuffer data
, double z_scale
, String indent
) {
1433 String in
= indent
+ "\t";
1434 if (-1 == n_points
) setupForDisplay(); // reload
1435 if (0 == n_points
) return;
1436 String
[] RGB
= Utils
.getHexRGBColor(color
);
1437 final double[] a
= new double[6];
1439 data
.append(indent
).append("<path\n")
1440 .append(in
).append("type=\"pipe\"\n")
1441 .append(in
).append("id=\"").append(id
).append("\"\n")
1442 .append(in
).append("transform=\"matrix(").append(a
[0]).append(',')
1443 .append(a
[1]).append(',')
1444 .append(a
[2]).append(',')
1445 .append(a
[3]).append(',')
1446 .append(a
[4]).append(',')
1447 .append(a
[5]).append(")\"\n")
1448 .append(in
).append("style=\"fill:none;stroke-opacity:").append(alpha
).append(";stroke:#").append(RGB
[0]).append(RGB
[1]).append(RGB
[2]).append(";stroke-width:1.0px;stroke-opacity:1.0\"\n")
1449 .append(in
).append("d=\"M")
1451 for (int i
=0; i
<n_points
-1; i
++) {
1452 data
.append(" ").append(p
[0][i
]).append(",").append(p
[1][i
])
1453 .append(" C ").append(p_r
[0][i
]).append(",").append(p_r
[1][i
])
1454 .append(" ").append(p_l
[0][i
+1]).append(",").append(p_l
[1][i
+1])
1457 data
.append(" ").append(p
[0][n_points
-1]).append(',').append(p
[1][n_points
-1]);
1459 .append(in
).append("z=\"")
1461 for (int i
=0; i
<n_points
; i
++) {
1462 data
.append(layer_set
.getLayer(p_layer
[i
]).getZ() * z_scale
).append(",");
1464 data
.append(in
).append("\"\n");
1465 data
.append(in
).append("p_width=\"");
1466 for (int i
=0; i
<n_points
; i
++) {
1467 data
.append(p_width
[i
]).append(",");
1470 .append(in
).append("links=\"");
1471 if (null != hs_linked
&& 0 != hs_linked
.size()) {
1473 int len
= hs_linked
.size();
1474 for (Iterator it
= hs_linked
.iterator(); it
.hasNext(); ) {
1475 Object ob
= it
.next();
1476 data
.append(((DBObject
)ob
).getId());
1477 if (ii
!= len
-1) data
.append(",");
1481 data
.append(indent
).append("\"\n/>\n");
1484 /** Returns a [p_i[0].length][4] array, with x,y,z,radius on the second part. Not translated to x,y but local!*/
1485 public double[][] getBackbone() {
1486 if (-1 == n_points
) setupForDisplay(); // reload
1487 double[][] b
= new double[p_i
[0].length
][4];
1488 int ni
= 20; // 1/0.05;
1490 for (int j
=0; j
<n_points
-1; j
++) {
1491 double z1
= layer_set
.getLayer(p_layer
[j
]).getZ();
1492 double z2
= layer_set
.getLayer(p_layer
[j
+1]).getZ();
1493 double depth
= z2
- z1
;
1494 double radius1
= p_width
[j
];
1495 double radius2
= p_width
[j
+1];
1496 double dif
= radius2
- radius1
;
1497 for (int i
=start
, k
=0; i
<start
+ ni
; i
++, k
++) {
1498 b
[i
][0] = p_i
[0][i
];
1499 b
[i
][1] = p_i
[1][i
];
1500 b
[i
][2] = z1
+ (k
* depth
) / (double)ni
;
1501 b
[i
][3] = radius1
+ (k
* dif
) / (double)ni
;
1506 start
= p_i
[0].length
-1; // recycling start
1507 b
[start
][0] = p
[0][n_points
-1];
1508 b
[start
][1] = p
[1][n_points
-1];
1509 b
[start
][2] = layer_set
.getLayer(p_layer
[n_points
-1]).getZ();
1510 b
[start
][3] = p_width
[n_points
-1];
1514 /** x,y is the cursor position in offscreen coordinates. */
1515 public void snapTo(int cx
, int cy
, int x_p
, int y_p
) { // WARNING: DisplayCanvas is locking at mouseDragged when the cursor is outside the DisplayCanvas Component, so this is useless or even harmful at the moment.
1517 // #$#@$%#$%!!! TODO this doesn't work, although it *should*. The index_l and index_r work, and the mouseEntered coordinates are fine too. Plus it messes up the x,y position or something, for then on reload the pipe is streched or displaced (not clear).
1519 double dx = p_l[0][index] - p[0][index];
1520 double dy = p_l[1][index] - p[1][index];
1521 p_l[0][index] = cx + dx;
1522 p_l[1][index] = cy + dy;
1523 dx = p_r[0][index] - p[0][index];
1524 dy = p_r[1][index] - p[1][index];
1525 p_r[0][index] = cx + dx;
1526 p_r[1][index] = cy + dy;
1530 } else if (-1 != index_l
) {
1531 p_l
[0][index_l
] = cx
;
1532 p_l
[1][index_l
] = cy
;
1533 } else if (-1 != index_r
) {
1534 p_r
[0][index_r
] = cx
;
1535 p_r
[1][index_r
] = cy
;
1537 // drag the whole pipe
1538 // CONCEPTUALLY WRONG, what happens when not dragging the pipe, on mouseEntered? Disaster!
1539 //drag(cx - x_p, cy - y_p);
1543 /** Exports data, the tag is not opened nor closed. */
1544 synchronized public void exportXML(StringBuffer sb_body
, String indent
, Object any
) {
1545 sb_body
.append(indent
).append("<t2_pipe\n");
1546 String in
= indent
+ "\t";
1547 super.exportXML(sb_body
, in
, any
);
1548 if (-1 == n_points
) setupForDisplay(); // reload
1549 //if (0 == n_points) return;
1550 String
[] RGB
= Utils
.getHexRGBColor(color
);
1551 sb_body
.append(in
).append("style=\"fill:none;stroke-opacity:").append(alpha
).append(";stroke:#").append(RGB
[0]).append(RGB
[1]).append(RGB
[2]).append(";stroke-width:1.0px;stroke-opacity:1.0\"\n");
1553 sb_body
.append(in
).append("d=\"M");
1554 for (int i
=0; i
<n_points
-1; i
++) {
1555 sb_body
.append(" ").append(p
[0][i
]).append(",").append(p
[1][i
])
1556 .append(" C ").append(p_r
[0][i
]).append(",").append(p_r
[1][i
])
1557 .append(" ").append(p_l
[0][i
+1]).append(",").append(p_l
[1][i
+1])
1560 sb_body
.append(" ").append(p
[0][n_points
-1]).append(',').append(p
[1][n_points
-1]).append("\"\n");
1561 sb_body
.append(in
).append("layer_ids=\""); // different from 'layer_id' in superclass
1562 for (int i
=0; i
<n_points
; i
++) {
1563 sb_body
.append(p_layer
[i
]);
1564 if (n_points
-1 != i
) sb_body
.append(",");
1566 sb_body
.append("\"\n");
1567 sb_body
.append(in
).append("p_width=\"");
1568 for (int i
=0; i
<n_points
; i
++) {
1569 sb_body
.append(p_width
[i
]);
1570 if (n_points
-1 != i
) sb_body
.append(",");
1572 sb_body
.append("\"\n");
1574 sb_body
.append(indent
).append(">\n");
1575 super.restXML(sb_body
, in
, any
);
1576 sb_body
.append(indent
).append("</t2_pipe>\n");
1579 static public void exportDTD(StringBuffer sb_header
, HashSet hs
, String indent
) {
1580 String type
= "t2_pipe";
1581 if (hs
.contains(type
)) return;
1583 sb_header
.append(indent
).append("<!ELEMENT t2_pipe (").append(Displayable
.commonDTDChildren()).append(")>\n");
1584 Displayable
.exportDTD(type
, sb_header
, hs
, indent
);
1585 sb_header
.append(indent
).append(TAG_ATTR1
).append(type
).append(" d").append(TAG_ATTR2
)
1586 .append(indent
).append(TAG_ATTR1
).append(type
).append(" p_width").append(TAG_ATTR2
)
1590 synchronized public double[][][] generateMesh(double scale
) {
1591 if (-1 == n_points
) setupForDisplay(); //reload
1592 if (0 == n_points
) return null;
1593 // at any given segment (bezier curve defined by 4 points):
1594 // - resample to homogenize point distribution
1595 // - if z doesn't change, use no intermediate sections in the tube
1596 // - for each point:
1597 // - add the section as 12 points, by rotating a perpendicular vector around the direction vector
1598 // - if the point is the first one in the segment, use a direction vector averaged with the previous and the first in the segment (if it's not point 0, that is)
1604 /** Performs a deep copy of this object, without the links. */
1605 synchronized public Displayable
clone(final Project pr
, final boolean copy_id
) {
1606 final long nid
= copy_id ?
this.id
: pr
.getLoader().getNextId();
1607 final Pipe copy
= new Pipe(pr
, nid
, null != title ? title
.toString() : null, width
, height
, alpha
, this.visible
, new Color(color
.getRed(), color
.getGreen(), color
.getBlue()), this.locked
, (AffineTransform
)this.at
.clone());
1609 if (-1 == n_points
) setupForDisplay(); // load data
1610 copy
.n_points
= n_points
;
1611 copy
.p
= new double[][]{(double[])this.p
[0].clone(), (double[])this.p
[1].clone()};
1612 copy
.p_l
= new double[][]{(double[])this.p_l
[0].clone(), (double[])this.p_l
[1].clone()};
1613 copy
.p_r
= new double[][]{(double[])this.p_r
[0].clone(), (double[])this.p_r
[1].clone()};
1614 copy
.p_layer
= (long[])this.p_layer
.clone();
1615 copy
.p_width
= (double[])this.p_width
.clone();
1616 copy
.p_i
= new double[][]{(double[])this.p_i
[0].clone(), (double[])this.p_i
[1].clone()};
1617 copy
.p_width_i
= (double[])this.p_width_i
.clone();
1618 copy
.addToDatabase();
1624 synchronized public List
generateTriangles(double scale
, int parallels
, int resample
) {
1625 if (n_points
< 2) return null;
1626 // check minimum requirements.
1627 if (parallels
< 3) parallels
= 3;
1629 double[][][] all_points
= generateJoints(parallels
, resample
, layer_set
.getCalibrationCopy());
1630 return Pipe
.generateTriangles(all_points
, scale
);
1633 /** Accepts an arrays as that returned from methods generateJoints and makeTube: first dimension is the list of points, second dimension is the number of vertices defining the circular cross section of the tube, and third dimension is the x,y,z of each vertex. */
1634 static public List
generateTriangles(final double[][][] all_points
, final double scale
) {
1635 int n
= all_points
.length
;
1636 final int parallels
= all_points
[0].length
-1;
1637 List list
= new ArrayList();
1638 for (int i
=0; i
<n
-1; i
++) { //minus one since last is made with previous
1639 for (int j
=0; j
<parallels
; j
++) { //there are 12+12 triangles for each joint //it's up to 12+1 because first point is repeated at the end
1640 // first triangle in the quad
1641 list
.add(new Point3f((float)(all_points
[i
][j
][0] * scale
), (float)(all_points
[i
][j
][1] * scale
), (float)(all_points
[i
][j
][2] * scale
)));
1642 list
.add(new Point3f((float)(all_points
[i
][j
+1][0] * scale
), (float)(all_points
[i
][j
+1][1] * scale
), (float)(all_points
[i
][j
+1][2] * scale
)));
1643 list
.add(new Point3f((float)(all_points
[i
+1][j
][0] * scale
), (float)(all_points
[i
+1][j
][1] * scale
), (float)(all_points
[i
+1][j
][2] * scale
)));
1645 // second triangle in the quad
1646 list
.add(new Point3f((float)(all_points
[i
+1][j
][0] * scale
), (float)(all_points
[i
+1][j
][1] * scale
), (float)(all_points
[i
+1][j
][2] * scale
)));
1647 list
.add(new Point3f((float)(all_points
[i
][j
+1][0] * scale
), (float)(all_points
[i
][j
+1][1] * scale
), (float)(all_points
[i
][j
+1][2] * scale
)));
1648 list
.add(new Point3f((float)(all_points
[i
+1][j
+1][0] * scale
), (float)(all_points
[i
+1][j
+1][1] * scale
), (float)(all_points
[i
+1][j
+1][2] * scale
)));
1654 /** From my former program, A_3D_Editing.java and Pipe.java */
1655 private double[][][] generateJoints(final int parallels
, final int resample
, final Calibration cal
) {
1656 if (-1 == n_points
) setupForDisplay();
1658 // local pointers, since they may be transformed
1659 int n_points
= this.n_points
;
1660 double[][] p
= this.p
;
1661 double[][] p_r
= this.p_r
;
1662 double[][] p_l
= this.p_l
;
1663 double[][] p_i
= this.p_i
;
1664 double[] p_width
= this.p_width
;
1665 double[] p_width_i
= this.p_width_i
;
1666 if (!this.at
.isIdentity()) {
1667 final Object
[] ob
= getTransformedData();
1668 p
= (double[][])ob
[0];
1669 n_points
= p
[0].length
;
1670 p_l
= (double[][])ob
[1];
1671 p_r
= (double[][])ob
[2];
1672 p_i
= (double[][])ob
[3];
1673 p_width
= (double[])ob
[4];
1674 p_width_i
= (double[])ob
[5];
1677 int n
= p_i
[0].length
;
1678 final int mm
= n_points
;
1679 final double[] z_values
= new double[n
];
1680 final int interval_points
= n
/ (mm
-1);
1682 double z_val_next
= 0;
1687 for (int j
=0; j
<mm
-1; j
++) {
1688 z_val
= layer_set
.getLayer(p_layer
[j
]).getZ();
1689 z_val_next
= layer_set
.getLayer(p_layer
[j
+1]).getZ();
1690 z_diff
= z_val_next
- z_val
;
1691 delta
= z_diff
/interval_points
;
1692 z_values
[c
] = (0 == j ? z_val
: z_values
[c
-1]) + delta
;
1693 for (int k
=1; k
<interval_points
; k
++) {
1695 z_values
[c
] = z_values
[c
-1] + delta
;
1699 //setting last point
1700 z_values
[n
-1] = layer_set
.getLayer(p_layer
[mm
-1]).getZ();
1703 return makeTube(p_i
[0], p_i
[1], z_values
, p_width_i
, resample
, parallels
, cal
);
1706 static public double[][][] makeTube(double[] px
, double[] py
, double[] pz
, double[] p_width_i
, final int resample
, final int parallels
, final Calibration cal
) {
1710 // Resampling to get a smoother pipe
1712 VectorString3D vs
= new VectorString3D(px
, py
, pz
, false);
1715 for (int i
=0; i
<p_width_i
.length
; i
++)
1716 p_width_i
[i
] *= cal
.pixelWidth
;
1718 vs
.addDependent(p_width_i
);
1719 // Resample to the largest of the two:
1720 double avg_delta
= vs
.getAverageDelta();
1721 double unit
= null != cal ?
1 / cal
.pixelWidth
: 1;
1722 vs
.resample(Math
.max(avg_delta
, unit
) * resample
);
1723 //vs.resample(vs.getAverageDelta() * resample);
1725 px
= vs
.getPoints(0);
1726 py
= vs
.getPoints(1);
1727 pz
= vs
.getPoints(2);
1728 p_width_i
= vs
.getDependent(0);
1729 //Utils.log("lengths: " + px.length + ", " + py.length + ", " + pz.length + ", " + p_width_i.length);
1731 } catch (Exception e
) {
1736 double[][][] all_points
= new double[n
+2][parallels
+1][3];
1737 int extra
= 1; // this was zero when not doing capping
1738 for (int cap
=0; cap
<parallels
+1; cap
++) {
1739 all_points
[0][cap
][0] = px
[0];//p_i[0][0]; //x
1740 all_points
[0][cap
][1] = py
[0]; //p_i[1][0]; //y
1741 all_points
[0][cap
][2] = pz
[0]; //z_values[0];
1742 all_points
[all_points
.length
-1][cap
][0] = px
[n
-1]; //p_i[0][p_i[0].length-1];
1743 all_points
[all_points
.length
-1][cap
][1] = py
[n
-1]; //p_i[1][p_i[0].length-1];
1744 all_points
[all_points
.length
-1][cap
][2] = pz
[n
-1]; //z_values[z_values.length-1];
1746 double angle
= 2*Math
.PI
/parallels
; //Math.toRadians(30);
1750 Vector3
[] circle
= new Vector3
[parallels
+1];
1752 int half_parallels
= parallels
/2;
1753 for (int i
=0; i
<n
-1; i
++) {
1754 //Utils.log2(i + " : " + px[i] + ", " + py[i] + ", " + pz[i]);
1755 //First vector: from one realpoint to the next
1756 //v3_P12 = new Vector3(p_i[0][i+1] - p_i[0][i], p_i[1][i+1] - p_i[1][i], z_values[i+1] - z_values[i]);
1757 v3_P12
= new Vector3(px
[i
+1] - px
[i
], py
[i
+1] - py
[i
], pz
[i
+1] - pz
[i
]);
1759 //Second vector: ortogonal to v3_P12, made by cross product between v3_P12 and a modifies v3_P12 (either y or z set to 0)
1761 //checking that v3_P12 has not z set to 0, in which case it woundn´t be different and then the cross product not give an ortogonal vector as output
1763 //chosen random vector: the same vector, but with x = 0;
1765 1 1 1 1 1 1 1 1 1 1 1 1
1766 v1 v2 v3 P12[0] P12[1] P12[2] P12[0] P12[1] P12[2] P12[0] P12[1] P12[2]
1767 w1 w2 w3 P12[0]+1 P12[1] P12[2] P12[0]+1 P12[1]+1 P12[2]+1 P12[0] P12[1] P12[2]+1
1769 cross product: v ^ w = (v2*w3 - w2*v3, v3*w1 - v1*w3, v1*w2 - w1*v2);
1771 cross product of second: v ^ w = (b*(c+1) - c*(b+1), c*(a+1) - a*(c+1) , a*(b+1) - b*(a+1))
1772 = ( b - c , c - a , a - b )
1774 cross product of third: v ^ w = (b*(c+1) - b*c, c*a - a*(c+1), a*b - b*a)
1776 (v3_P12.y ,-v3_P12.x , 0);
1779 Reasons why I use the third:
1780 -Using the first one modifies the x coord, so it generates a plane the ortogonal of which will be a few degrees different when z != 0 and when z =0,
1781 thus responsible for soft shiftings at joints where z values change
1782 -Adding 1 to the z value will produce the same plane whatever the z value, thus avoiding soft shiftings at joints where z values are different
1783 -Then, the third allows for very fine control of the direction that the ortogonal vector takes: simply manipulating the x coord of v3_PR, voilà .
1787 // BELOW if-else statements needed to correct the orientation of vectors, so there's no discontinuity
1789 v3_PR
= new Vector3(v3_P12
.y
, -v3_P12
.x
, 0);
1790 v3_PR
= v3_PR
.normalize(v3_PR
);
1791 v3_PR
= v3_PR
.scale(p_width_i
[i
], v3_PR
);
1793 //vectors are perfectly normalized and scaled
1794 //The problem then must be that they are not properly ortogonal and so appear to have a smaller width.
1795 // -not only not ortogonal but actually messed up in some way, i.e. bad coords.
1797 circle
[half_parallels
] = v3_PR
;
1798 for (int q
=half_parallels
+1; q
<parallels
+1; q
++) {
1799 sinn
= Math
.sin(angle
*(q
-half_parallels
));
1800 coss
= Math
.cos(angle
*(q
-half_parallels
));
1801 circle
[q
] = rotate_v_around_axis(v3_PR
, v3_P12
, sinn
, coss
);
1803 circle
[0] = circle
[parallels
];
1804 for (int qq
=1; qq
<half_parallels
; qq
++) {
1805 sinn
= Math
.sin(angle
*(qq
+half_parallels
));
1806 coss
= Math
.cos(angle
*(qq
+half_parallels
));
1807 circle
[qq
] = rotate_v_around_axis(v3_PR
, v3_P12
, sinn
, coss
);
1810 v3_PR
= new Vector3(-v3_P12
.y
, v3_P12
.x
, 0); //thining problems disappear when both types of y coord are equal, but then shifting appears
1813 -if y coord shifted, then no thinnings but yes shiftings
1814 -if x coord shifted, THEN PERFECT
1815 -if both shifted, then both thinnings and shiftings
1816 -if none shifted, then no shiftings but yes thinnings
1819 v3_PR
= v3_PR
.normalize(v3_PR
);
1820 if (null == v3_PR
) {
1821 Utils
.log2("vp_3r is null: most likely a point was repeated in the list, and thus the vector has length zero.");
1823 v3_PR
= v3_PR
.scale(
1828 for (int q
=1; q
<parallels
; q
++) {
1829 sinn
= Math
.sin(angle
*q
);
1830 coss
= Math
.cos(angle
*q
);
1831 circle
[q
] = rotate_v_around_axis(v3_PR
, v3_P12
, sinn
, coss
);
1833 circle
[parallels
] = v3_PR
;
1835 // Adding points to main array
1836 for (int j
=0; j
<parallels
+1; j
++) {
1837 all_points
[i
+extra
][j
][0] = /*p_i[0][i]*/ px
[i
] + circle
[j
].x
;
1838 all_points
[i
+extra
][j
][1] = /*p_i[1][i]*/ py
[i
] + circle
[j
].y
;
1839 all_points
[i
+extra
][j
][2] = /*z_values[i]*/ pz
[i
] + circle
[j
].z
;
1842 for (int k
=0; k
<parallels
+1; k
++) {
1843 all_points
[n
-1+extra
][k
][0] = /*p_i[0][n-1]*/ px
[n
-1] + circle
[k
].x
;
1844 all_points
[n
-1+extra
][k
][1] = /*p_i[1][n-1]*/ py
[n
-1] + circle
[k
].y
;
1845 all_points
[n
-1+extra
][k
][2] = /*z_values[n-1]*/ pz
[n
-1] + circle
[k
].z
;
1850 /** From my former program, A_3D_Editing.java and Pipe.java */
1851 static private Vector3
rotate_v_around_axis(final Vector3 v
, final Vector3 axis
, final double sin
, final double cos
) {
1853 final Vector3 result
= new Vector3();
1854 final Vector3 r
= axis
.normalize(axis
);
1856 result
.set((cos
+ (1-cos
) * r
.x
* r
.x
) * v
.x
+ ((1-cos
) * r
.x
* r
.y
- r
.z
* sin
) * v
.y
+ ((1-cos
) * r
.x
* r
.z
+ r
.y
* sin
) * v
.z
,
1857 ((1-cos
) * r
.x
* r
.y
+ r
.z
* sin
) * v
.x
+ (cos
+ (1-cos
) * r
.y
* r
.y
) * v
.y
+ ((1-cos
) * r
.y
* r
.z
- r
.x
* sin
) * v
.z
,
1858 ((1-cos
) * r
.y
* r
.z
- r
.y
* sin
) * v
.x
+ ((1-cos
) * r
.y
* r
.z
+ r
.x
* sin
) * v
.y
+ (cos
+ (1-cos
) * r
.z
* r
.z
) * v
.z
);
1861 result.x += (cos + (1-cos) * r.x * r.x) * v.x;
1862 result.x += ((1-cos) * r.x * r.y - r.z * sin) * v.y;
1863 result.x += ((1-cos) * r.x * r.z + r.y * sin) * v.z;
1865 result.y += ((1-cos) * r.x * r.y + r.z * sin) * v.x;
1866 result.y += (cos + (1-cos) * r.y * r.y) * v.y;
1867 result.y += ((1-cos) * r.y * r.z - r.x * sin) * v.z;
1869 result.z += ((1-cos) * r.y * r.z - r.y * sin) * v.x;
1870 result.z += ((1-cos) * r.y * r.z + r.x * sin) * v.y;
1871 result.z += (cos + (1-cos) * r.z * r.z) * v.z;
1876 synchronized private Object
[] getTransformedData() {
1877 final int n_points
= this.n_points
;
1878 final double[][] p
= transformPoints(this.p
, n_points
);
1879 final double[][] p_l
= transformPoints(this.p_l
, n_points
);
1880 final double[][] p_r
= transformPoints(this.p_r
, n_points
);
1881 final double[][] p_i
= transformPoints(this.p_i
, this.p_i
[0].length
); // whatever length it has
1882 final double[] p_width
= new double[n_points
]; // first contains the data, then the transformed data
1883 System
.arraycopy(this.p_width
, 0, p_width
, 0, n_points
);
1884 final double[] p_width_i
= new double[this.p_width_i
.length
]; // first contains the data, then the transformed data
1885 System
.arraycopy(this.p_width_i
, 0, p_width_i
, 0, p_width_i
.length
);
1886 // p_width: same rule as for Ball: average of x and y
1887 double[][] pw
= new double[2][n_points
];
1888 for (int i
=0; i
<n_points
; i
++) {
1889 pw
[0][i
] = this.p
[0][i
] + p_width
[i
]; //built relative to the untransformed points!
1890 pw
[1][i
] = this.p
[1][i
] + p_width
[i
];
1892 pw
= transformPoints(pw
);
1893 //final double[] p_width = new double[n_points];
1894 for (int i
=0; i
<n_points
; i
++) {
1895 // plain average of differences in X and Y axis, relative to the transformed points.
1896 p_width
[i
] = (Math
.abs(pw
[0][i
] - p
[0][i
]) + Math
.abs(pw
[1][i
] - p
[1][i
])) / 2;
1898 // same with p_width_i
1899 double[][] pwi
= new double[2][p_i
[0].length
];
1900 for (int i
=0; i
<p_i
[0].length
; i
++) {
1901 pwi
[0][i
] = this.p_i
[0][i
] + p_width_i
[i
]; //built relative to the untransformed points!
1902 pwi
[1][i
] = this.p_i
[1][i
] + p_width_i
[i
];
1904 pwi
= transformPoints(pwi
);
1905 //final double[] p_width_i = new double[p_i[0].length];
1906 for (int i
=0; i
<p_i
[0].length
; i
++) {
1907 // plain average of differences in X and Y axis, relative to the transformed points.
1908 p_width_i
[i
] = (Math
.abs(pwi
[0][i
] - p_i
[0][i
]) + Math
.abs(pwi
[1][i
] - p_i
[1][i
])) / 2;
1911 return new Object
[]{p
, p_l
, p_r
, p_i
, p_width
, p_width_i
};
1914 /** Returns a non-calibrated VectorString3D. */
1915 synchronized public VectorString3D
asVectorString3D() {
1916 // local pointers, since they may be transformed
1917 int n_points
= this.n_points
;
1918 double[][] p
= this.p
;
1919 double[][] p_r
= this.p_r
;
1920 double[][] p_l
= this.p_l
;
1921 double[][] p_i
= this.p_i
;
1922 double[] p_width
= this.p_width
;
1923 double[] p_width_i
= this.p_width_i
;
1924 if (!this.at
.isIdentity()) {
1925 final Object
[] ob
= getTransformedData();
1926 p
= (double[][])ob
[0];
1927 n_points
= p
[0].length
;
1928 p_l
= (double[][])ob
[1];
1929 p_r
= (double[][])ob
[2];
1930 p_i
= (double[][])ob
[3];
1931 p_width
= (double[])ob
[4];
1932 p_width_i
= (double[])ob
[5];
1935 final int n
= p_i
[0].length
;
1936 final int mm
= n_points
;
1937 final double[] z_values
= new double[n
];
1938 final int interval_points
= n
/ (mm
-1);
1940 double z_val_next
= 0;
1945 for (int j
=0; j
<mm
-1; j
++) {
1946 z_val
= layer_set
.getLayer(p_layer
[j
]).getZ();
1947 z_val_next
= layer_set
.getLayer(p_layer
[j
+1]).getZ();
1948 z_diff
= z_val_next
- z_val
;
1949 delta
= z_diff
/interval_points
;
1950 z_values
[c
] = (0 == j ? z_val
: z_values
[c
-1]) + delta
;
1951 for (int k
=1; k
<interval_points
; k
++) {
1953 z_values
[c
] = z_values
[c
-1] + delta
;
1957 //setting last point
1958 z_values
[n
-1] = layer_set
.getLayer(p_layer
[mm
-1]).getZ();
1960 final double[] px
= p_i
[0];
1961 final double[] py
= p_i
[1];
1962 final double[] pz
= z_values
;
1963 VectorString3D vs
= null;
1965 vs
= new VectorString3D(px
, py
, pz
, false);
1966 vs
.addDependent(p_width_i
);
1967 } catch (Exception e
) { IJError
.print(e
); }
1971 public String
getInfo() {
1972 if (-1 == n_points
) setupForDisplay(); //reload
1976 VectorString3D vs
= asVectorString3D();
1977 vs
.calibrate(this.layer_set
.getCalibration());
1978 len
= vs
.computeLength(); // no resampling
1980 return new StringBuffer("Length: ").append(Utils
.cutNumber(len
, 2, true)).append(' ').append(this.layer_set
.getCalibration().getUnits()).append('\n').toString();
1983 /** @param area is expected in world coordinates. */
1984 public boolean intersects(final Area area
, final double z_first
, final double z_last
) {
1985 // find lowest and highest Z
1986 double min_z
= Double
.MAX_VALUE
;
1988 for (int i
=0; i
<n_points
; i
++) {
1989 double laz
=layer_set
.getLayer(p_layer
[i
]).getZ();
1990 if (laz
< min_z
) min_z
= laz
;
1991 if (laz
> max_z
) max_z
= laz
;
1993 if (z_last
< min_z
|| z_first
> max_z
) return false;
1995 for (int i
=0; i
<n_points
; i
++) {
1996 final Polygon
[] pol
= getSubPerimeters(layer_set
.getLayer(p_layer
[i
]));
1997 if (null == pol
) continue;
1998 for (int k
=0; k
<pol
.length
; k
++) {
1999 Area a
= new Area(pol
[k
]); // perimeters already in world coords
2001 Rectangle r
= a
.getBounds();
2002 if (0 != r
.width
&& 0 != r
.height
) return true;
2008 /** Expects Rectangle in world coords. */
2009 public boolean intersects(final Layer layer
, final Rectangle r
) {
2010 final Polygon
[] pol
= getSubPerimeters(layer
); // transformed
2011 if (null == pol
) return false;
2012 for (Polygon p
: pol
) if (new Area(p
).intersects(r
.x
, r
.y
, r
.width
, r
.height
)) return true;
2015 /** Expects Area in world coords. */
2016 public boolean intersects(final Layer layer
, final Area area
) {
2017 final Polygon
[] pol
= getSubPerimeters(layer
); // transformed
2018 if (null == pol
) return false;
2019 for (Polygon p
: pol
) if (M
.intersects(new Area(p
), area
)) return true;
2023 /** Returns the bounds of this object as it shows in the given layer, set into @param r. */
2024 public Rectangle
getBounds(final Rectangle r
, final Layer layer
) {
2025 // obtain the already transformed subperimeters
2026 final Polygon
[] pol
= getSubPerimeters(layer
);
2028 if (null == r
) return new Rectangle();
2035 final Area area
= new Area();
2036 for (Polygon p
: pol
) {
2037 area
.add(new Area(p
));
2039 final Rectangle b
= area
.getBounds();
2040 if (null == r
) return b
;
2041 r
.setBounds(b
.x
, b
.y
, b
.width
, b
.height
);
2046 // debug ---------------
2047 static private void showShape(final Shape shape
) {
2048 Area area
= new Area(shape
);
2049 Rectangle b
= area
.getBounds();
2050 AffineTransform trans
= new AffineTransform();
2051 trans
.translate(-b
.x
, -b
.y
);
2052 area
= area
.createTransformedArea(trans
);
2053 ij
.process
.ByteProcessor bp
= new ij
.process
.ByteProcessor(b
.width
, b
.height
);
2054 ij
.gui
.ShapeRoi sr
= new ij
.gui
.ShapeRoi(area
);
2055 ij
.ImagePlus imp
= new ij
.ImagePlus("pipe area", bp
);
2062 public ResultsTable
measure(ResultsTable rt
) {
2063 if (-1 == n_points
) setupForDisplay(); //reload
2064 if (0 == n_points
) return rt
;
2065 if (null == rt
) rt
= Utils
.createResultsTable("Pipe results", new String
[]{"id", "length", "name-id"});
2068 Calibration cal
= layer_set
.getCalibration();
2070 VectorString3D vs
= asVectorString3D();
2072 len
= vs
.computeLength(); // no resampling
2074 rt
.incrementCounter();
2075 rt
.addLabel("units", cal
.getUnit());
2076 rt
.addValue(0, this.id
);
2077 rt
.addValue(1, len
);
2078 rt
.addValue(2, getNameId());
2083 final Class
getInternalDataPackageClass() {
2084 return DPPipe
.class;
2088 synchronized Object
getDataPackage() {
2089 return new DPPipe(this);
2092 static private final class DPPipe
extends Displayable
.DataPackage
{
2093 final double[][] p
, p_l
, p_r
, p_i
;
2094 final double[] p_width
, p_width_i
;
2095 final long[] p_layer
;
2097 DPPipe(final Pipe pipe
) {
2099 // store copies of all arrays
2100 this.p
= new double[][]{Utils
.copy(pipe
.p
[0], pipe
.n_points
), Utils
.copy(pipe
.p
[1], pipe
.n_points
)};
2101 this.p_r
= new double[][]{Utils
.copy(pipe
.p_r
[0], pipe
.n_points
), Utils
.copy(pipe
.p_r
[1], pipe
.n_points
)};
2102 this.p_l
= new double[][]{Utils
.copy(pipe
.p_l
[0], pipe
.n_points
), Utils
.copy(pipe
.p_l
[1], pipe
.n_points
)};
2103 this.p_width
= Utils
.copy(pipe
.p_width
, pipe
.n_points
);
2104 this.p_i
= new double[][]{Utils
.copy(pipe
.p_i
[0], pipe
.p_i
[0].length
), Utils
.copy(pipe
.p_i
[1], pipe
.p_i
[0].length
)};
2105 this.p_width_i
= Utils
.copy(pipe
.p_width_i
, pipe
.p_width_i
.length
);
2106 this.p_layer
= new long[pipe
.n_points
]; System
.arraycopy(pipe
.p_layer
, 0, this.p_layer
, 0, pipe
.n_points
);
2109 final boolean to2(final Displayable d
) {
2111 final Pipe pipe
= (Pipe
)d
;
2112 final int len
= p
[0].length
; // == n_points, since it was cropped on copy
2113 pipe
.p
= new double[][]{Utils
.copy(p
[0], len
), Utils
.copy(p
[1], len
)};
2114 pipe
.n_points
= p
[0].length
;
2115 pipe
.p_r
= new double[][]{Utils
.copy(p_r
[0], len
), Utils
.copy(p_r
[1], len
)};
2116 pipe
.p_l
= new double[][]{Utils
.copy(p_l
[0], len
), Utils
.copy(p_l
[1], len
)};
2117 pipe
.p_layer
= new long[len
]; System
.arraycopy(p_layer
, 0, pipe
.p_layer
, 0, len
);
2118 pipe
.p_width
= Utils
.copy(p_width
, len
);
2119 pipe
.p_i
= new double[][]{Utils
.copy(p_i
[0], p_i
[0].length
), Utils
.copy(p_i
[1], p_i
[1].length
)};
2120 pipe
.p_width_i
= Utils
.copy(p_width_i
, p_width_i
.length
);