3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 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
;
27 import ini
.trakem2
.Project
;
28 import ini
.trakem2
.persistence
.XMLOptions
;
29 import ini
.trakem2
.utils
.IJError
;
30 import ini
.trakem2
.utils
.M
;
31 import ini
.trakem2
.utils
.ProjectToolbar
;
32 import ini
.trakem2
.utils
.Utils
;
33 import ini
.trakem2
.utils
.Vector3
;
34 import ini
.trakem2
.vector
.VectorString3D
;
36 import java
.awt
.AlphaComposite
;
37 import java
.awt
.Color
;
38 import java
.awt
.Composite
;
39 import java
.awt
.Graphics2D
;
40 import java
.awt
.Polygon
;
41 import java
.awt
.Rectangle
;
42 import java
.awt
.event
.KeyEvent
;
43 import java
.awt
.event
.MouseEvent
;
44 import java
.awt
.geom
.AffineTransform
;
45 import java
.awt
.geom
.Area
;
46 import java
.awt
.geom
.Point2D
;
47 import java
.util
.ArrayList
;
48 import java
.util
.Collection
;
49 import java
.util
.HashMap
;
50 import java
.util
.HashSet
;
51 import java
.util
.Iterator
;
52 import java
.util
.List
;
54 import javax
.vecmath
.Point3f
;
57 public class Pipe
extends ZDisplayable
implements Line3D
, VectorData
{
59 /**The number of points.*/
60 protected int n_points
;
61 /**The array of clicked points.*/
62 protected double[][] p
;
63 /**The array of left control points, one for each clicked point.*/
64 protected double[][] p_l
;
65 /**The array of right control points, one for each clicked point.*/
66 protected double[][] p_r
;
67 /**The array of interpolated points generated from p, p_l and p_r.*/
68 protected double[][] p_i
= new double[2][0];
69 /**The array of Layers over which the points of this pipe live */
70 protected long[] p_layer
;
71 /**The width of each point. */
72 protected double[] p_width
;
73 /**The interpolated width for each interpolated point. */
74 protected double[] p_width_i
= new double[0];
76 /** Every new Pipe will have, for its first point, the last user-adjusted radius value. */
77 static private double last_radius
= -1;
79 public Pipe(Project project
, String title
, double x
, double y
) {
80 super(project
, title
, x
, y
);
83 p_l
= new double[2][5];
84 p_r
= new double[2][5];
85 p_layer
= new long[5]; // the ids of the layers in which each point lays
86 p_width
= new double[5];
90 /** Construct an unloaded Pipe from the database. Points will be loaded later, when needed. */
91 public Pipe(Project project
, long id
, String title
, float width
, float height
, float alpha
, boolean visible
, Color color
, boolean locked
, AffineTransform at
) {
92 super(project
, id
, title
, locked
, at
, width
, height
);
93 this.visible
= visible
;
95 this.visible
= visible
;
97 this.n_points
= -1; //used as a flag to signal "I have points, but unloaded"
100 /** Construct a Pipe from an XML entry. */
101 public Pipe(Project project
, long id
, HashMap
<String
,String
> ht
, HashMap
<Displayable
,String
> ht_links
) {
102 super(project
, id
, ht
, ht_links
);
103 // parse specific data
105 if (null != (data
= ht
.get("d"))) {
107 // parse the SVG points data
108 ArrayList
<String
> al_p
= new ArrayList
<String
>();
109 ArrayList
<String
> al_p_r
= new ArrayList
<String
>();
110 ArrayList
<String
> al_p_l
= new ArrayList
<String
>();// needs shifting, inserting one point at the beginning if not closed.
111 // 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]
113 int i_start
= data
.indexOf('M');
114 int i_end
= data
.indexOf('C');
115 final String point
= data
.substring(i_start
+1, i_end
).trim();
120 i_end
= data
.indexOf('C', i_end
+1);
122 i_end
= data
.length() -1;
125 String txt
= data
.substring(i_start
+1, i_end
).trim();
126 // eliminate double spaces
127 while (-1 != txt
.indexOf(" ")) {
128 txt
= txt
.replaceAll(" ", " ");
130 // reduce ", " and " ," to ","
131 txt
= txt
.replaceAll(" ,", ",");
132 txt
= txt
.replaceAll(", ", ",");
134 String
[] points
= txt
.split(" ");
135 if (3 == points
.length
) {
136 al_p_r
.add(points
[0]);
137 al_p_l
.add(points
[1]);
141 Utils
.log("Pipe constructor from XML: error at parsing points.");
143 // example: C 34.5,45.6 45.7,23.0 34.8, 78.0 C ..
145 // fix missing control points
146 al_p_l
.add(0, al_p
.get(0));
147 al_p_r
.add(al_p
.get(al_p
.size() -1));
149 if (!(al_p
.size() == al_p_l
.size() && al_p_l
.size() == al_p_r
.size())) {
150 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());
152 // Now parse the points
153 this.n_points
= al_p
.size();
154 this.p
= new double[2][n_points
];
155 this.p_l
= new double[2][n_points
];
156 this.p_r
= new double[2][n_points
];
157 for (int i
=0; i
<n_points
; i
++) {
158 String
[] sp
= ((String
)al_p
.get(i
)).split(",");
159 p
[0][i
] = Double
.parseDouble(sp
[0]);
160 p
[1][i
] = Double
.parseDouble(sp
[1]);
161 sp
= ((String
)al_p_l
.get(i
)).split(",");
162 p_l
[0][i
] = Double
.parseDouble(sp
[0]);
163 p_l
[1][i
] = Double
.parseDouble(sp
[1]);
164 sp
= ((String
)al_p_r
.get(i
)).split(",");
165 p_r
[0][i
] = Double
.parseDouble(sp
[0]);
166 p_r
[1][i
] = Double
.parseDouble(sp
[1]);
169 if(null != (data
= ht
.get("layer_ids"))) {
170 // parse comma-separated list of layer ids. Creates empty Layer instances with the proper id, that will be replaced later.
171 final String
[] layer_ids
= data
.replaceAll(" ", "").trim().split(",");
172 this.p_layer
= new long[layer_ids
.length
];
173 for (int i
=0; i
<layer_ids
.length
; i
++) {
174 this.p_layer
[i
] = Long
.parseLong(layer_ids
[i
]);
177 if (null != (data
= ht
.get("p_width"))) {
178 final String
[] widths
= data
.replaceAll(" ", "").trim().split(",");
179 this.p_width
= new double[widths
.length
];
180 for (int i
=0; i
<widths
.length
; i
++) {
181 this.p_width
[i
] = Double
.parseDouble(widths
[i
]);
185 this.p_i
= new double[2][0]; // empty
186 this.p_width_i
= new double[0];
187 generateInterpolatedPoints(0.05);
188 if (null == this.p
) {
190 this.p
= new double[2][0];
191 this.p_l
= new double[2][0];
192 this.p_r
= new double[2][0];
193 this.p_width
= new double[0];
194 this.p_layer
= new long[0];
197 if (!(n_points
== p
[0].length
&& p
[0].length
== p_width
.length
&& p_width
.length
== p_layer
.length
)) {
198 Utils
.log2("Pipe at parsing XML: inconsistent number of points for id=" + id
);
199 Utils
.log2("\tn_points: " + n_points
+ " p.length: " + p
[0].length
+ " p_width.length: " + p_width
.length
+ " p_layer.length: " + p_layer
.length
);
203 /**Increase the size of the arrays by 5.*/
204 synchronized private void enlargeArrays() {
206 int length
= p
[0].length
;
208 double[][] p_copy
= new double[2][length
+ 5];
209 double[][] p_l_copy
= new double[2][length
+ 5];
210 double[][] p_r_copy
= new double[2][length
+ 5];
211 long[] p_layer_copy
= new long[length
+ 5];
212 double[] p_width_copy
= new double[length
+ 5];
214 System
.arraycopy(p
[0], 0, p_copy
[0], 0, length
);
215 System
.arraycopy(p
[1], 0, p_copy
[1], 0, length
);
216 System
.arraycopy(p_l
[0], 0, p_l_copy
[0], 0, length
);
217 System
.arraycopy(p_l
[1], 0, p_l_copy
[1], 0, length
);
218 System
.arraycopy(p_r
[0], 0, p_r_copy
[0], 0, length
);
219 System
.arraycopy(p_r
[1], 0, p_r_copy
[1], 0, length
);
220 System
.arraycopy(p_layer
, 0, p_layer_copy
, 0, length
);
221 System
.arraycopy(p_width
, 0, p_width_copy
, 0, length
);
226 this.p_layer
= p_layer_copy
;
227 this.p_width
= p_width_copy
;
230 /**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. */
231 synchronized protected int findPoint(double[][] a
, long[] p_layer
, int x_p
, int y_p
, long lid
, double magnification
) {
233 double d
= (10.0D
/ magnification
);
235 double min_dist
= Double
.MAX_VALUE
;
236 long i_layer
= Display
.getFrontLayer(this.project
).getId();
237 for (int i
=0; i
<n_points
; i
++) {
238 if (p_layer
[i
] != lid
) continue;
239 double dist
= Math
.abs(x_p
- a
[0][i
]) + Math
.abs(y_p
- a
[1][i
]);
240 if (i_layer
== p_layer
[i
] && dist
<= d
&& dist
<= min_dist
) {
248 /**Remove a point from the bezier backbone and its two associated control points.*/
249 synchronized protected void removePoint(int index
) {
250 // check preconditions:
253 } else if (n_points
- 1 == index
) {
257 //one point out (but not the last)
260 // shift all points after 'index' one position to the left:
261 for (int i
=index
; i
<n_points
; i
++) {
262 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.
264 p_l
[0][i
] = p_l
[0][i
+1];
265 p_l
[1][i
] = p_l
[1][i
+1];
266 p_r
[0][i
] = p_r
[0][i
+1];
267 p_r
[1][i
] = p_r
[1][i
+1];
268 p_layer
[i
] = p_layer
[i
+1];
269 p_width
[i
] = p_width
[i
+1];
274 updateInDatabase("points");
276 /**Calculate distance from one point to another.*/
277 static public double distance(final double x1
, final double y1
,
278 final double x2
, final double y2
) {
279 return (Math
.sqrt((x2
-x1
)*(x2
-x1
) + (y2
-y1
)*(y2
-y1
)));
282 /**Move backbone point by the given deltas.*/
283 protected void dragPoint(int index
, int dx
, int dy
) {
292 /**Set the control points to the same value as the backbone point which they control.*/
293 protected void resetControlPoints(int index
) {
294 p_l
[0][index
] = p
[0][index
];
295 p_l
[1][index
] = p
[1][index
];
296 p_r
[0][index
] = p
[0][index
];
297 p_r
[1][index
] = p
[1][index
];
299 /**Drag a control point and adjust the other, dependent one, in a symmetric way or not.*/
300 protected void dragControlPoint(int index
, int x_d
, int y_d
, double[][] p_dragged
, double[][] p_adjusted
, boolean symmetric
) {
301 //measure hypothenusa: from p to p control
304 //make both points be dragged in parallel, the same distance
305 hypothenusa
= distance(p
[0][index
], p
[1][index
], p_dragged
[0][index
], p_dragged
[1][index
]);
307 //make each point be dragged with its own distance
308 hypothenusa
= distance(p
[0][index
], p
[1][index
], p_adjusted
[0][index
], p_adjusted
[1][index
]);
310 //measure angle: use the point being dragged
311 double angle
= Math
.atan2(p_dragged
[0][index
] - p
[0][index
], p_dragged
[1][index
] - p
[1][index
]) + Math
.PI
;
313 p_dragged
[0][index
] = x_d
;
314 p_dragged
[1][index
] = y_d
;
315 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
316 p_adjusted
[1][index
] = p
[1][index
] + hypothenusa
* Math
.cos(angle
);
319 static private double getFirstWidth() {
320 if (null == Display
.getFront()) return 1;
321 if (-1 != last_radius
) return last_radius
;
322 return 10 / Display
.getFront().getCanvas().getMagnification(); // 10 pixels in the screen
325 /**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.*/
326 synchronized protected int addPoint(int x_p
, int y_p
, double magnification
, double bezier_finess
, long layer_id
) {
327 if (-1 == n_points
) setupForDisplay(); //reload
328 //lookup closest interpolated point and then get the closest clicked point to it
329 int index
= findClosestPoint(x_p
, y_p
, magnification
, bezier_finess
);
331 if (p
[0].length
== n_points
) {
335 if (0 == n_points
|| 1 == n_points
|| index
+ 1 == n_points
) {
337 p
[0][n_points
] = p_l
[0][n_points
] = p_r
[0][n_points
] = x_p
;
338 p
[1][n_points
] = p_l
[1][n_points
] = p_r
[1][n_points
] = y_p
;
339 p_layer
[n_points
] = layer_id
;
340 p_width
[n_points
] = (0 == n_points ? Pipe
.getFirstWidth() : p_width
[n_points
-1]); // either 1.0 or the same as the last point
342 } else if (-1 == index
) {
343 // decide whether to append at the end or prepend at the beginning
344 // compute distance in the 3D space to the first and last points
345 final Calibration cal
= layer_set
.getCalibration();
346 final double lz
= layer_set
.getLayer(layer_id
).getZ();
347 final double p0z
=layer_set
.getLayer(p_layer
[0]).getZ();
348 final double pNz
=layer_set
.getLayer(p_layer
[n_points
-1]).getZ();
349 double sqdist0
= (p
[0][0] - x_p
) * (p
[0][0] - x_p
) * cal
.pixelWidth
* cal
.pixelWidth
350 + (p
[1][0] - y_p
) * (p
[1][0] - y_p
) * cal
.pixelHeight
* cal
.pixelHeight
351 + (lz
- p0z
) * (lz
- p0z
) * cal
.pixelWidth
* cal
.pixelWidth
;
352 double sqdistN
= (p
[0][n_points
-1] - x_p
) * (p
[0][n_points
-1] - x_p
) * cal
.pixelWidth
* cal
.pixelWidth
353 + (p
[1][n_points
-1] - y_p
) * (p
[1][n_points
-1] - y_p
) * cal
.pixelHeight
* cal
.pixelHeight
354 + (lz
- pNz
) * (lz
- pNz
) * cal
.pixelWidth
* cal
.pixelWidth
;
355 if (sqdistN
< sqdist0
) {
357 p
[0][n_points
] = p_l
[0][n_points
] = p_r
[0][n_points
] = x_p
;
358 p
[1][n_points
] = p_l
[1][n_points
] = p_r
[1][n_points
] = y_p
;
359 p_layer
[n_points
] = layer_id
;
360 p_width
[n_points
] = p_width
[n_points
-1];
363 // prepend at the beginning
364 for (int i
=n_points
-1; i
>-1; i
--) {
367 p_l
[0][i
+1] = p_l
[0][i
];
368 p_l
[1][i
+1] = p_l
[1][i
];
369 p_r
[0][i
+1] = p_r
[0][i
];
370 p_r
[1][i
+1] = p_r
[1][i
];
371 p_width
[i
+1] = p_width
[i
];
372 p_layer
[i
+1] = p_layer
[i
];
374 p
[0][0] = p_l
[0][0] = p_r
[0][0] = x_p
;
375 p
[1][0] = p_l
[1][0] = p_r
[1][0] = y_p
;
376 p_width
[0] = p_width
[1];
377 p_layer
[0] = layer_id
;
380 //debug: I miss gdb/ddd !
381 //Utils.log("p_width.length = " + p_width.length + " || n_points = " + n_points + " || p[0].length = " + p[0].length);
385 Utils.log(" p length = " + p[0].length
386 + "\np_l length = " + p_l[0].length
387 + "\np_r length = " + p_r[0].length
388 + "\np_layer len= " + p_layer.length);
390 index
++; //so it is added after the closest point;
391 // 1 - copy second half of array
392 int sh_length
= n_points
-index
;
393 double[][] p_copy
= new double[2][sh_length
];
394 double[][] p_l_copy
= new double[2][sh_length
];
395 double[][] p_r_copy
= new double[2][sh_length
];
396 long[] p_layer_copy
= new long[sh_length
];
397 double[] p_width_copy
= new double[sh_length
];
398 System
.arraycopy(p
[0], index
, p_copy
[0], 0, sh_length
);
399 System
.arraycopy(p
[1], index
, p_copy
[1], 0, sh_length
);
400 System
.arraycopy(p_l
[0], index
, p_l_copy
[0], 0, sh_length
);
401 System
.arraycopy(p_l
[1], index
, p_l_copy
[1], 0, sh_length
);
402 System
.arraycopy(p_r
[0], index
, p_r_copy
[0], 0, sh_length
);
403 System
.arraycopy(p_r
[1], index
, p_r_copy
[1], 0, sh_length
);
404 //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);
405 System
.arraycopy(p_layer
, index
, p_layer_copy
, 0, sh_length
);
406 System
.arraycopy(p_width
, index
, p_width_copy
, 0, sh_length
);
407 // 2 - insert value into 'p' (the two control arrays get the same value)
408 p
[0][index
] = p_l
[0][index
] = p_r
[0][index
] = x_p
;
409 p
[1][index
] = p_l
[1][index
] = p_r
[1][index
] = y_p
;
410 p_layer
[index
] = layer_id
;
411 p_width
[index
] = p_width
[index
-1]; // -1 because the index has been increased by 1 above
412 // 3 - copy second half into the array
413 System
.arraycopy(p_copy
[0], 0, p
[0], index
+1, sh_length
);
414 System
.arraycopy(p_copy
[1], 0, p
[1], index
+1, sh_length
);
415 System
.arraycopy(p_l_copy
[0], 0, p_l
[0], index
+1, sh_length
);
416 System
.arraycopy(p_l_copy
[1], 0, p_l
[1], index
+1, sh_length
);
417 System
.arraycopy(p_r_copy
[0], 0, p_r
[0], index
+1, sh_length
);
418 System
.arraycopy(p_r_copy
[1], 0, p_r
[1], index
+1, sh_length
);
419 System
.arraycopy(p_layer_copy
, 0, p_layer
, index
+1, sh_length
);
420 System
.arraycopy(p_width_copy
, 0, p_width
, index
+1, sh_length
);
427 /**Find the closest point to an interpolated point with precision depending upon magnification.*/
428 synchronized protected int findClosestPoint(int x_p
, int y_p
, double magnification
, double bezier_finess
) {
430 double distance_sq
= Double
.MAX_VALUE
;
431 double distance_sq_i
;
432 double max
= 12.0D
/ magnification
;
433 max
= max
* max
; //squaring it
434 for (int i
=0; i
<p_i
[0].length
; i
++) {
435 //see which point is closer (there's no need to calculate the distance by multiplying squares and so on).
436 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
);
437 if (distance_sq_i
< max
&& distance_sq_i
< distance_sq
) {
439 distance_sq
= distance_sq_i
;
443 int index_found
= (int)Math
.round((double)index
* bezier_finess
);
444 // correct to be always the one after: count the number of points before reaching the next point
445 if (index
< (index_found
/ bezier_finess
)) {
449 // allow only when the closest index is visible in the current layer
450 // handled at mousePressed now//if (Display.getFrontLayer(this.project).getId() != p_layer[index]) return -1;
454 synchronized protected void generateInterpolatedPoints(double bezier_finess
) {
461 // case there's only one point
463 p_i
= new double[2][1];
466 p_width_i
= new double[1];
467 p_width_i
[0] = p_width
[0];
470 // case there's more: interpolate!
471 p_i
= new double[2][(int)(n
* (1.0D
/bezier_finess
))];
472 p_width_i
= new double[p_i
[0].length
];
473 double t
, f0
, f1
, f2
, f3
;
475 for (int i
=0; i
<n
-1; i
++) {
476 for (t
=0.0D
; t
<1.0D
; t
+= bezier_finess
) {
477 f0
= (1-t
)*(1-t
)*(1-t
);
478 f1
= 3*t
*(1-t
)*(1-t
);
481 p_i
[0][next
] = f0
*p
[0][i
] + f1
*p_r
[0][i
] + f2
*p_l
[0][i
+1] + f3
*p
[0][i
+1];
482 p_i
[1][next
] = f0
*p
[1][i
] + f1
*p_r
[1][i
] + f2
*p_l
[1][i
+1] + f3
*p
[1][i
+1];
483 p_width_i
[next
] = p_width
[i
]*(1-t
) + p_width
[i
+1]*t
;
485 //enlarge if needed (when bezier_finess is not 0.05, it's difficult to predict because of int loss of precision.
486 if (p_i
[0].length
== next
) {
487 double[][] p_i_copy
= new double[2][p_i
[0].length
+ 5];
488 double[] p_width_i_copy
= new double[p_width_i
.length
+ 5];
489 System
.arraycopy(p_i
[0], 0, p_i_copy
[0], 0, p_i
[0].length
);
490 System
.arraycopy(p_i
[1], 0, p_i_copy
[1], 0, p_i
[1].length
);
491 System
.arraycopy(p_width_i
, 0, p_width_i_copy
, 0, p_width_i
.length
);
493 p_width_i
= p_width_i_copy
;
497 // add the last point
498 if (p_i
[0].length
== next
) {
499 double[][] p_i_copy
= new double[2][p_i
[0].length
+ 1];
500 double[] p_width_i_copy
= new double[p_width_i
.length
+ 1];
501 System
.arraycopy(p_i
[0], 0, p_i_copy
[0], 0, p_i
[0].length
);
502 System
.arraycopy(p_i
[1], 0, p_i_copy
[1], 0, p_i
[1].length
);
503 System
.arraycopy(p_width_i
, 0, p_width_i_copy
, 0, p_width_i
.length
);
505 p_width_i
= p_width_i_copy
;
507 p_i
[0][next
] = p
[0][n_points
-1];
508 p_i
[1][next
] = p
[1][n_points
-1];
509 p_width_i
[next
] = p_width
[n_points
-1];
512 if (p_i
[0].length
!= next
) { // 'next' works as a length here
514 double[][] p_i_copy
= new double[2][next
];
515 double[] p_width_i_copy
= new double[next
];
516 System
.arraycopy(p_i
[0], 0, p_i_copy
[0], 0, next
);
517 System
.arraycopy(p_i
[1], 0, p_i_copy
[1], 0, next
);
518 System
.arraycopy(p_width_i
, 0, p_width_i_copy
, 0, next
);
520 p_width_i
= p_width_i_copy
;
524 // synchronizing to protect n_points ... need to wrap it in a lock
526 public void paint(final Graphics2D g
, final Rectangle srcRect
, final double magnification
, final boolean active
, final int channels
, final Layer active_layer
, final List
<Layer
> layers
) {
527 if (0 == n_points
) return;
528 if (-1 == n_points
) {
529 // load points from the database
532 //arrange transparency
533 Composite original_composite
= null;
535 original_composite
= g
.getComposite();
536 g
.setComposite(AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, alpha
));
539 // local pointers, since they may be transformed
540 int n_points
= this.n_points
;
541 double[][] p
= this.p
;
542 double[][] p_r
= this.p_r
;
543 double[][] p_l
= this.p_l
;
544 double[][] p_i
= this.p_i
;
545 //double[] p_width = this.p_width;
546 double[] p_width_i
= this.p_width_i
;
547 if (!this.at
.isIdentity()) {
548 final Object
[] ob
= getTransformedData();
549 p
= (double[][])ob
[0];
550 n_points
= p
[0].length
;
551 p_l
= (double[][])ob
[1];
552 p_r
= (double[][])ob
[2];
553 p_i
= (double[][])ob
[3];
554 //p_width = (double[])ob[4];
555 p_width_i
= (double[])ob
[5];
558 final boolean no_color_cues
= !layer_set
.color_cues
;
559 final Color below
, above
;
560 if (layer_set
.use_color_cue_colors
) {
568 final long layer_id
= active_layer
.getId();
572 final int oval_radius
= (int)Math
.ceil(4 / magnification
);
573 final int oval_corr
= (int)Math
.ceil(3 / magnification
);
574 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.
575 if (layer_id
!= p_layer
[j
]) continue;
576 //draw big ovals at backbone points
577 DisplayCanvas
.drawHandle(g
, (int)p
[0][j
], (int)p
[1][j
], magnification
);
579 //fill small ovals at control points
580 g
.fillOval((int)p_l
[0][j
] -oval_corr
, (int)p_l
[1][j
] -oval_corr
, oval_radius
, oval_radius
);
581 g
.fillOval((int)p_r
[0][j
] -oval_corr
, (int)p_r
[1][j
] -oval_corr
, oval_radius
, oval_radius
);
582 //draw lines between backbone and control points
583 g
.drawLine((int)p
[0][j
], (int)p
[1][j
], (int)p_l
[0][j
], (int)p_l
[1][j
]);
584 g
.drawLine((int)p
[0][j
], (int)p
[1][j
], (int)p_r
[0][j
], (int)p_r
[1][j
]);
586 // label the first point distinctively:
588 Composite comp
= g
.getComposite();
589 g
.setColor(Color
.white
);
590 g
.setXORMode(Color
.green
);
591 g
.drawString("1", (int)(p
[0][0] + (4.0 / magnification
)), (int)p
[1][0]); // displaced 4 screen pixels to the right
592 g
.setComposite(comp
);
596 // paint the tube in 2D:
597 if (n_points
> 1 && p_i
[0].length
> 1) { // need the second check for repaints that happen before generating the interpolated points.
599 //double a0 = Math.toRadians(0);
600 double a90
= Math
.toRadians(90);
601 //double a180 = Math.toRadians(180);
602 //double a270 = Math.toRadians(270);
603 final int n
= p_i
[0].length
;
604 final double[] r_side_x
= new double[n
];
605 final double[] r_side_y
= new double[n
];
606 final double[] l_side_x
= new double[n
];
607 final double[] l_side_y
= new double[n
];
610 for (int i
=0; i
<n
-1; i
++) {
611 angle
= Math
.atan2(p_i
[0][i
+1] - p_i
[0][i
], p_i
[1][i
+1] - p_i
[1][i
]);
613 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 ??
614 r_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
+a90
) * p_width_i
[i
];
615 l_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
-a90
) * p_width_i
[i
];
616 l_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
-a90
) * p_width_i
[i
];
619 angle
= Math
.atan2(p_i
[0][m
] - p_i
[0][m
-1], p_i
[1][m
] - p_i
[1][m
-1]);
621 r_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
+a90
) * p_width_i
[m
];
622 r_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
+a90
) * p_width_i
[m
];
623 l_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
-a90
) * p_width_i
[m
];
624 l_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
-a90
) * p_width_i
[m
];
626 final double z_current
= active_layer
.getZ();
629 // paint a tiny bit where it should!
630 g
.setColor(this.color
);
633 for (int j
=0; j
<n_points
; j
++) { // at least looping through 2 points, as guaranteed by the preconditions checking
635 if (layer_id
== p_layer
[j
]) {
638 // else if crossed the current layer, paint segment as well
639 if (0 == j
) continue;
640 double z1
= layer_set
.getLayer(p_layer
[j
-1]).getZ();
641 double z2
= layer_set
.getLayer(p_layer
[j
]).getZ();
642 if ( (z1
< z_current
&& z_current
< z2
)
643 || (z2
< z_current
&& z_current
< z1
) ) {
644 // paint normally, in this pipe's color
650 double z
= layer_set
.getLayer(p_layer
[j
]).getZ();
651 if (z
< z_current
) g
.setColor(below
);
652 else if (z
== z_current
) g
.setColor(this.color
);
653 else g
.setColor(above
);
658 if (0 != j
) fi
= (j
* 20) - 10; // 10 is half a segment
659 if (n_points
-1 != j
) la
+= 10; // same //= j * 20 + 9;
660 if (la
>= r_side_x
.length
) la
= r_side_x
.length
-2; // quick fix. -2 so that the k+1 below can work
661 if (fi
> la
) fi
= la
;
665 for (int k
=fi
; k
<=la
; k
++) {
666 g
.drawLine((int)r_side_x
[k
], (int)r_side_y
[k
], (int)r_side_x
[k
+1], (int)r_side_y
[k
+1]);
667 g
.drawLine((int)l_side_x
[k
], (int)l_side_y
[k
], (int)l_side_x
[k
+1], (int)l_side_y
[k
+1]);
670 } catch (Exception ee
) {
671 Utils
.log2("Pipe paint failed with: fi=" + fi
+ " la=" + la
+ " n_points=" + n_points
+ " r_side_x.length=" + r_side_x
.length
);
672 // WARNING still something is wrong with the synchronization over the arrays ... despite this error being patched with the line above:
673 // if (la >= r_side_x.length) r_side_x.length-2; // quick fix
675 // 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.
680 //Transparency: fix alpha composite back to original.
681 if (null != original_composite
) {
682 g
.setComposite(original_composite
);
686 public void keyPressed(KeyEvent ke
) {
687 //Utils.log2("Pipe.keyPressed not implemented.");
690 /**Helper vars for mouse events. It's safe to have them static since only one Pipe will be edited at a time.*/
691 static private int index
, index_l
, index_r
;
692 static private boolean is_new_point
= false;
694 public void mousePressed(MouseEvent me
, Layer layer
, int x_p
, int y_p
, double mag
) {
695 // transform the x_p, y_p to the local coordinates
696 if (!this.at
.isIdentity()) {
697 final Point2D
.Double po
= inverseTransformPoint(x_p
, y_p
);
702 final int tool
= ProjectToolbar
.getToolId();
704 if (ProjectToolbar
.PEN
== tool
) {
706 if (Utils
.isControlDown(me
) && me
.isShiftDown()) {
707 index
= Displayable
.findNearestPoint(p
, p_layer
, n_points
, x_p
, y_p
, layer
.getId());
709 index
= findPoint(p
, p_layer
, x_p
, y_p
, layer
.getId(), mag
);
713 if (Utils
.isControlDown(me
) && me
.isShiftDown() && p_layer
[index
] == Display
.getFrontLayer(this.project
).getId()) {
716 index
= index_r
= index_l
= -1;
717 repaint(false, layer
);
720 // Make the radius for newly added point that of the last added
721 last_radius
= p_width
[index
];
723 if (me
.isAltDown()) {
724 resetControlPoints(index
);
729 // find if click is on a left control point
730 index_l
= findPoint(p_l
, p_layer
, x_p
, y_p
, layer
.getId(), mag
);
732 // if not, then try on the set of right control points
734 index_r
= findPoint(p_r
, p_layer
, x_p
, y_p
, layer
.getId(), mag
);
737 final long layer_id
= layer
.getId();
739 if (-1 != index
&& layer_id
!= p_layer
[index
]) index
= -1; // disable!
740 else if (-1 != index_l
&& layer_id
!= p_layer
[index_l
]) index_l
= -1;
741 else if (-1 != index_r
&& layer_id
!= p_layer
[index_r
]) index_r
= -1;
742 //if no conditions are met, attempt to add point
743 else if (-1 == index
&& -1 == index_l
&& -1 == index_r
&& !me
.isShiftDown() && !me
.isAltDown()) {
744 //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.
745 index_l
= addPoint(x_p
, y_p
, mag
, 0.05, layer_id
);
747 if (0 == index_l
) { //1 == n_points)
748 //for the very first point, drag the right control point, not the left.
753 generateInterpolatedPoints(0.05);
755 repaint(false, layer
);
761 public void mouseDragged(MouseEvent me
, Layer layer
, int x_p
, int y_p
, int x_d
, int y_d
, int x_d_old
, int y_d_old
) {
762 // transform to the local coordinates
763 if (!this.at
.isIdentity()) {
764 final Point2D
.Double p
= inverseTransformPoint(x_p
, y_p
);
767 final Point2D
.Double pd
= inverseTransformPoint(x_d
, y_d
);
770 final Point2D
.Double pdo
= inverseTransformPoint(x_d_old
, y_d_old
);
771 x_d_old
= (int)pdo
.x
;
772 y_d_old
= (int)pdo
.y
;
775 final int tool
= ProjectToolbar
.getToolId();
777 if (ProjectToolbar
.PEN
== tool
) {
778 //if a point in the backbone is found, then:
780 if (!me
.isAltDown() && !me
.isShiftDown()) {
781 dragPoint(index
, x_d
- x_d_old
, y_d
- y_d_old
);
782 } else if (me
.isShiftDown()) {
784 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
]));
785 last_radius
= p_width
[index
];
786 Utils
.showStatus("radius: " + p_width
[index
], false);
787 } 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!
788 //drag both control points symmetrically
789 dragControlPoint(index
, x_d
, y_d
, p_l
, p_r
, true);
791 generateInterpolatedPoints(0.05);
792 repaint(false, layer
);
796 //if a control point is found, then drag it, adjusting the other control point non-symmetrically
798 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
799 generateInterpolatedPoints(0.05);
800 repaint(false, layer
);
804 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
805 generateInterpolatedPoints(0.05);
806 repaint(false, layer
);
812 public void mouseReleased(MouseEvent me
, Layer layer
, int x_p
, int y_p
, int x_d
, int y_d
, int x_r
, int y_r
) {
814 final int tool
= ProjectToolbar
.getToolId();
816 if (ProjectToolbar
.PEN
== tool
) {
817 //generate interpolated points
818 generateInterpolatedPoints(0.05);
819 repaint(true, layer
); //needed at least for the removePoint
822 //update points in database if there was any change
823 if (-1 != index
|| -1 != index_r
|| -1 != index_l
) {
824 //updateInDatabase("position");
826 // update all points, since the index may have changed
827 updateInDatabase("points");
828 } else if (-1 != index
&& index
!= n_points
) { //second condition happens when the last point has been removed
829 updateInDatabase(getUpdatePointForSQL(index
));
830 } else if (-1 != index_r
) {
831 updateInDatabase(getUpdateRightControlPointForSQL(index_r
));
832 } else if (-1 != index_l
) {
833 updateInDatabase(getUpdateLeftControlPointForSQL(index_l
));
834 } else if (index
!= n_points
) { // don't do it when the last point is removed
836 updateInDatabase("points");
838 updateInDatabase("dimensions");
839 } else if (x_r
!= x_p
|| y_r
!= y_p
) {
840 updateInDatabase("dimensions");
843 //Display.repaint(layer, this, 5); // the entire Displayable object
844 repaint(true, layer
);
847 is_new_point
= false;
848 index
= index_r
= index_l
= -1;
852 protected boolean calculateBoundingBox(final Layer la
) {
853 return calculateBoundingBox(true, la
);
856 synchronized protected boolean calculateBoundingBox(final boolean adjust_position
, final Layer la
) {
857 double min_x
= Double
.MAX_VALUE
;
858 double min_y
= Double
.MAX_VALUE
;
862 this.width
= this.height
= 0;
866 // get perimeter of the tube, without the transform
867 final Polygon pol
= Pipe
.getRawPerimeter(p_i
, p_width_i
);
868 if (null != pol
&& 0 != pol
.npoints
) {
870 for (int i
=0; i
<pol
.npoints
; i
++) {
871 if (pol
.xpoints
[i
] < min_x
) min_x
= pol
.xpoints
[i
];
872 if (pol
.ypoints
[i
] < min_y
) min_y
= pol
.ypoints
[i
];
873 if (pol
.xpoints
[i
] > max_x
) max_x
= pol
.xpoints
[i
];
874 if (pol
.ypoints
[i
] > max_y
) max_y
= pol
.ypoints
[i
];
877 // check the control points
878 for (int i
=0; i
<n_points
; i
++) {
879 if (p_l
[0][i
] < min_x
) min_x
= p_l
[0][i
];
880 if (p_r
[0][i
] < min_x
) min_x
= p_r
[0][i
];
881 if (p_l
[1][i
] < min_y
) min_y
= p_l
[1][i
];
882 if (p_r
[1][i
] < min_y
) min_y
= p_r
[1][i
];
883 if (p_l
[0][i
] > max_x
) max_x
= p_l
[0][i
];
884 if (p_r
[0][i
] > max_x
) max_x
= p_r
[0][i
];
885 if (p_l
[1][i
] > max_y
) max_y
= p_l
[1][i
];
886 if (p_r
[1][i
] > max_y
) max_y
= p_r
[1][i
];
889 this.width
= (float)(max_x
- min_x
);
890 this.height
= (float)(max_y
- min_y
);
892 if (adjust_position
) {
893 // now readjust points to make min_x,min_y be the x,y
894 for (int i
=0; i
<n_points
; i
++) {
895 p
[0][i
] -= min_x
; p
[1][i
] -= min_y
;
896 p_l
[0][i
] -= min_x
; p_l
[1][i
] -= min_y
;
897 p_r
[0][i
] -= min_x
; p_r
[1][i
] -= min_y
;
899 for (int i
=0; i
<p_i
[0].length
; i
++) {
900 p_i
[0][i
] -= min_x
; p_i
[1][i
] -= min_y
;
902 this.at
.translate(min_x
, min_y
); // not using super.translate(...) because a preConcatenation is not needed; here we deal with the data.
903 updateInDatabase("transform");
905 updateInDatabase("dimensions");
911 /**Release all memory resources taken by this object.*/
912 synchronized public void destroy() {
923 /**Release memory resources used by this object: namely the arrays of points, which can be reloaded with a call to setupForDisplay()*/
924 synchronized public void flush() {
931 n_points
= -1; // flag that points exist but are not loaded
934 /**Repaints in the given ImageCanvas only the area corresponding to the bounding box of this Pipe. */
935 public void repaint(boolean repaint_navigator
, Layer la
) {
936 //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.
937 Rectangle box
= getBoundingBox(null);
938 calculateBoundingBox(true, la
);
939 box
.add(getBoundingBox(null));
940 Display
.repaint(layer_set
, this, box
, 5, repaint_navigator
);
943 /**Make this object ready to be painted.*/
944 synchronized private void setupForDisplay() {
946 if (null == p
|| null == p_l
|| null == p_r
) {
947 ArrayList
<?
> al
= project
.getLoader().fetchPipePoints(id
);
948 n_points
= al
.size();
949 p
= new double[2][n_points
];
950 p_l
= new double[2][n_points
];
951 p_r
= new double[2][n_points
];
952 p_layer
= new long[n_points
];
953 p_width
= new double[n_points
];
954 Iterator
<?
> it
= al
.iterator();
956 while (it
.hasNext()) {
957 Object
[] ob
= (Object
[])it
.next();
958 p
[0][i
] = ((Double
)ob
[0]).doubleValue();
959 p
[1][i
] = ((Double
)ob
[1]).doubleValue();
960 p_r
[0][i
] = ((Double
)ob
[2]).doubleValue();
961 p_r
[1][i
] = ((Double
)ob
[3]).doubleValue();
962 p_l
[0][i
] = ((Double
)ob
[4]).doubleValue();
963 p_l
[1][i
] = ((Double
)ob
[5]).doubleValue();
964 p_width
[i
] = ((Double
)ob
[6]).doubleValue();
965 p_layer
[i
] = ((Long
)ob
[7]).longValue();
968 // recreate interpolated points
969 generateInterpolatedPoints(0.05); //TODO adjust this or make it read the value from the Project perhaps.
973 static private final Polygon
getRawPerimeter(final double[][] p_i
, final double[] p_width_i
) {
974 final int n
= p_i
[0].length
; // the number of interpolated points
975 if (n
< 2) return null;
977 //final double a0 = Math.toRadians(0);
978 final double a90
= Math
.toRadians(90);
979 //final double a180 = Math.toRadians(180);
980 //final double a270 = Math.toRadians(270);
981 double[] r_side_x
= new double[n
];
982 double[] r_side_y
= new double[n
];
983 double[] l_side_x
= new double[n
];
984 double[] l_side_y
= new double[n
];
987 for (int i
=0; i
<n
-1; i
++) {
988 angle
= Math
.atan2(p_i
[0][i
+1] - p_i
[0][i
], p_i
[1][i
+1] - p_i
[1][i
]);
990 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 ??
991 r_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
+a90
) * p_width_i
[i
];
992 l_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
-a90
) * p_width_i
[i
];
993 l_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
-a90
) * p_width_i
[i
];
997 angle
= Math
.atan2(p_i
[0][m
] - p_i
[0][m
-1], p_i
[1][m
] - p_i
[1][m
-1]);
999 r_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
+a90
) * p_width_i
[m
];
1000 r_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
+a90
) * p_width_i
[m
];
1001 l_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
-a90
) * p_width_i
[m
];
1002 l_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
-a90
) * p_width_i
[m
];
1004 int[] pol_x
= new int[n
* 2];
1005 int[] pol_y
= new int[n
* 2];
1006 for (int j
=0; j
<n
; j
++) {
1007 pol_x
[j
] = (int)r_side_x
[j
];
1008 pol_y
[j
] = (int)r_side_y
[j
];
1009 pol_x
[n
+ j
] = (int)l_side_x
[m
-j
];
1010 pol_y
[n
+ j
] = (int)l_side_y
[m
-j
];
1012 return new Polygon(pol_x
, pol_y
, pol_x
.length
);
1015 /** The exact perimeter of this pipe, in integer precision. */
1016 synchronized public Polygon
getPerimeter() {
1017 if (null == p_i
|| p_i
[0].length
< 2) return new Polygon(); // meaning: if there aren't any interpolated points
1019 // local pointers, since they may be transformed
1020 //int n_points = this.n_points;
1021 //double[][] p = this.p;
1022 //double[][] p_r = this.p_r;
1023 //double[][] p_l = this.p_l;
1024 double[][] p_i
= this.p_i
;
1025 //double[] p_width = this.p_width;
1026 double[] p_width_i
= this.p_width_i
;
1027 if (!this.at
.isIdentity()) {
1028 final Object
[] ob
= getTransformedData();
1029 //p = (double[][])ob[0];
1030 //n_points = p[0].length;
1031 //p_l = (double[][])ob[1];
1032 //p_r = (double[][])ob[2];
1033 p_i
= (double[][])ob
[3];
1034 //p_width = (double[])ob[4];
1035 p_width_i
= (double[])ob
[5];
1037 return Pipe
.getRawPerimeter(p_i
, p_width_i
);
1040 /** Writes the data of this object as a Pipe object in the .shapes file represented by the 'data' StringBuffer. */
1041 synchronized public void toShapesFile(StringBuffer data
, String group
, String color
, double z_scale
) {
1042 if (-1 == n_points
) setupForDisplay(); // reload
1043 final char l
= '\n';
1044 // local pointers, since they may be transformed
1045 int n_points
= this.n_points
;
1046 double[][] p
= this.p
;
1047 double[][] p_r
= this.p_r
;
1048 double[][] p_l
= this.p_l
;
1049 double[] p_width
= this.p_width
;
1050 if (!this.at
.isIdentity()) {
1051 final Object
[] ob
= getTransformedData();
1052 p
= (double[][])ob
[0];
1053 n_points
= p
[0].length
;
1054 p_l
= (double[][])ob
[1];
1055 p_r
= (double[][])ob
[2];
1056 p_width
= (double[])ob
[4];
1058 data
.append("type=pipe").append(l
)
1059 .append("name=").append(project
.getMeaningfulTitle(this)).append(l
)
1060 .append("group=").append(group
).append(l
)
1061 .append("color=").append(color
).append(l
)
1062 .append("supergroup=").append("null").append(l
)
1063 .append("supercolor=").append("null").append(l
)
1065 for (int i
=0; i
<n_points
; i
++) {
1066 data
.append("p x=").append(p
[0][i
]).append(l
)
1067 .append("p y=").append(p
[1][i
]).append(l
)
1068 .append("p_r x=").append(p_r
[0][i
]).append(l
)
1069 .append("p_r y=").append(p_r
[1][i
]).append(l
)
1070 .append("p_l x=").append(p_l
[0][i
]).append(l
)
1071 .append("p_l y=").append(p_l
[1][i
]).append(l
)
1072 .append("z=").append(layer_set
.getLayer(p_layer
[i
]).getZ() * z_scale
).append(l
)
1073 .append("width=").append(p_width
[i
]).append(l
)
1078 /** Return the list of query statements needed to insert all the points in the database. */
1079 synchronized public String
[] getPointsForSQL() {
1080 String
[] sql
= new String
[n_points
];
1081 for (int i
=0; i
<n_points
; i
++) {
1082 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 (");
1083 sb
.append(this.id
).append(",")
1084 .append(i
).append(",")
1085 .append(p
[0][i
]).append(",")
1086 .append(p
[1][i
]).append(",")
1087 .append(p_r
[0][i
]).append(",")
1088 .append(p_r
[1][i
]).append(",")
1089 .append(p_l
[0][i
]).append(",")
1090 .append(p_l
[1][i
]).append(",")
1091 .append(p_width
[i
]).append(",")
1095 sql
[i
] = sb
.toString();
1100 synchronized public String
getUpdatePointForSQL(int index
) {
1101 if (index
< 0 || index
> n_points
-1) return null;
1103 StringBuffer sb
= new StringBuffer("UPDATE ab_pipe_points SET ");
1104 sb
.append("x=").append(p
[0][index
])
1105 .append(", y=").append(p
[1][index
])
1106 .append(", x_r=").append(p_r
[0][index
])
1107 .append(", y_r=").append(p_r
[1][index
])
1108 .append(", x_l=").append(p_l
[0][index
])
1109 .append(", y_l=").append(p_l
[1][index
])
1110 .append(", width=").append(p_width
[index
])
1111 .append(", layer_id=").append(p_layer
[index
])
1112 .append(" WHERE pipe_id=").append(this.id
)
1113 .append(" AND index=").append(index
)
1115 return sb
.toString();
1118 String
getUpdateLeftControlPointForSQL(int index
) {
1119 if (index
< 0 || index
> n_points
-1) return null;
1121 StringBuffer sb
= new StringBuffer("UPDATE ab_pipe_points SET ");
1122 sb
.append("x_l=").append(p_l
[0][index
])
1123 .append(", y_l=").append(p_l
[1][index
])
1124 .append(" WHERE pipe_id=").append(this.id
)
1125 .append(" AND index=").append(index
)
1127 return sb
.toString();
1130 String
getUpdateRightControlPointForSQL(int index
) {
1131 if (index
< 0 || index
> n_points
-1) return null;
1133 StringBuffer sb
= new StringBuffer("UPDATE ab_pipe_points SET ");
1134 sb
.append("x_r=").append(p_r
[0][index
])
1135 .append(", y_r=").append(p_r
[1][index
])
1136 .append(" WHERE pipe_id=").append(this.id
)
1137 .append(" AND index=").append(index
)
1139 return sb
.toString();
1142 public boolean isDeletable() {
1143 return 0 == n_points
;
1146 /** The number of clicked, backbone points in this pipe. */
1147 public int length() {
1148 if (-1 == n_points
) setupForDisplay();
1152 /** 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. */
1154 public boolean contains(final Layer layer
, double x
, double y
) {
1155 if (-1 == n_points
) setupForDisplay(); // reload points
1156 if (0 == n_points
) return false;
1158 final Point2D
.Double po
= inverseTransformPoint(x
, y
);
1161 if (1 == n_points
) {
1162 if (Math
.abs(p
[0][0] - x
) < 3 && Math
.abs(p
[1][0] - y
) < 3) return true; // error in clicked precision of 3 pixels
1165 final boolean no_color_cues
= "true".equals(project
.getProperty("no_color_cues"));
1168 //double a0 = Math.toRadians(0);
1169 double a90
= Math
.toRadians(90);
1170 //double a180 = Math.toRadians(180);
1171 //double a270 = Math.toRadians(270);
1172 int n
= p_i
[0].length
; // the number of interpolated points
1173 final double[] r_side_x
= new double[n
];
1174 final double[] r_side_y
= new double[n
];
1175 final double[] l_side_x
= new double[n
];
1176 final double[] l_side_y
= new double[n
];
1179 for (int i
=0; i
<n
-1; i
++) {
1180 angle
= Math
.atan2(p_i
[0][i
+1] - p_i
[0][i
], p_i
[1][i
+1] - p_i
[1][i
]);
1182 // side points, displaced by this.x, this.y
1183 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 ??
1184 r_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
+a90
) * p_width_i
[i
];
1185 l_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
-a90
) * p_width_i
[i
];
1186 l_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
-a90
) * p_width_i
[i
];
1190 angle
= Math
.atan2(p_i
[0][m
] - p_i
[0][m
-1], p_i
[1][m
] - p_i
[1][m
-1]);
1192 // side points, displaced by this.x, this.y
1193 r_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
+a90
) * p_width_i
[m
];
1194 r_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
+a90
) * p_width_i
[m
];
1195 l_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
-a90
) * p_width_i
[m
];
1196 l_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
-a90
) * p_width_i
[m
];
1198 final long layer_id
= layer
.getId();
1199 final double z_current
= layer
.getZ();
1201 int first
= 0; // the first backbone point in the subpolygon present in the layer
1202 int last
= 0; // the last backbone point in the subpolygon present in the layer
1204 boolean add_pol
= false;
1206 for (int j
=0; j
<n_points
; j
++) { // at least looping through 2 points, as guaranteed by the preconditions checking
1207 if (!no_color_cues
&& layer_id
!= p_layer
[j
]) {
1208 first
= j
+ 1; // perhaps next, not this j
1212 // if j is last point, or the next point won't be in the same layer:
1213 if (j
== n_points
-1 || layer_id
!= p_layer
[j
+1]) {
1217 if (no_color_cues
) {
1218 // else if crossed the current layer, check segment as well
1219 if (0 == j
) continue;
1221 final double z1
= layer_set
.getLayer(p_layer
[j
-1]).getZ();
1222 final double z2
= layer_set
.getLayer(p_layer
[j
]).getZ();
1224 // 20 points is the length of interpolated points between any two backbone bezier 'j' points.
1225 // 10 points is half that.
1227 // 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.
1229 // 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.
1231 if (z1
== z_current
&& z_current
== z2
) {
1233 fi
= ((j
-1) * 20) - 10;
1234 la
= ( j
* 20) + 10;
1235 } else if ( (z1
< z_current
&& z_current
== z2
)
1236 || (z1
> z_current
&& z_current
== z2
) ) {
1238 fi
= ((j
-1) * 20) + 10;
1239 la
= ( j
* 20) + 10;
1240 } else if ( (z1
== z_current
&& z_current
< z2
)
1241 || (z1
== z_current
&& z_current
> z2
) ) {
1243 fi
= ((j
-1) * 20) - 10;
1244 la
= ( j
* 20) - 10;
1245 } else if ( (z1
< z_current
&& z_current
< z2
)
1246 || (z1
> z_current
&& z_current
> z2
) ) {
1248 // crossing by without a point: short polygons
1249 fi
= ((j
-1) * 20) + 10;
1250 la
= ( j
* 20) - 10;
1256 if (0 == j
-1) fi
= 0;
1257 if (n_points
-1 == j
) la
= n_points
* 20;
1260 // compute sub polygon
1261 if (!no_color_cues
) {
1264 if (0 != first
) fi
= (first
* 20) - 10; // 10 is half a segment
1265 if (n_points
-1 != last
) la
+= 10; // same //= last * 20 + 9;
1269 if (la
>= r_side_x
.length
) la
= r_side_x
.length
-1; // quick fix
1270 final int length
= la
- fi
+ 1; // +1 because fi and la are indices
1272 final int [] pol_x
= new int[length
* 2];
1273 final int [] pol_y
= new int[length
* 2];
1274 for (int k
=0, g
=fi
; g
<=la
; g
++, k
++) {
1275 pol_x
[k
] = (int)r_side_x
[g
];
1276 pol_y
[k
] = (int)r_side_y
[g
];
1277 pol_x
[length
+ k
] = (int)l_side_x
[la
- k
];
1278 pol_y
[length
+ k
] = (int)l_side_y
[la
- k
];
1280 final Polygon pol
= new Polygon(pol_x
, pol_y
, pol_x
.length
);
1281 if (pol
.contains(x
, y
)) {
1282 //Utils.log2("first, last : " + first + ", " + last);
1294 /** Get the perimeter of all parts that show in the given layer (as defined by its Z). Returns null if none found. */
1295 private Polygon
[] getSubPerimeters(final Layer layer
) {
1296 if (n_points
<= 1) return null;
1298 // local pointers, since they may be transformed
1299 int n_points
= this.n_points
;
1300 double[][] p
= this.p
;
1301 //double[][] p_r = this.p_r;
1302 //double[][] p_l = this.p_l;
1303 double[][] p_i
= this.p_i
;
1304 //double[] p_width = this.p_width;
1305 double[] p_width_i
= this.p_width_i
;
1306 if (!this.at
.isIdentity()) {
1307 final Object
[] ob
= getTransformedData();
1308 p
= (double[][])ob
[0];
1309 n_points
= p
[0].length
;
1310 //p_l = (double[][])ob[1];
1311 //p_r = (double[][])ob[2];
1312 p_i
= (double[][])ob
[3];
1313 //p_width = (double[])ob[4];
1314 p_width_i
= (double[])ob
[5];
1318 //double a0 = Math.toRadians(0);
1319 double a90
= Math
.toRadians(90);
1320 //double a180 = Math.toRadians(180);
1321 //double a270 = Math.toRadians(270);
1322 int n
= p_i
[0].length
; // the number of interpolated points
1323 double[] r_side_x
= new double[n
];
1324 double[] r_side_y
= new double[n
];
1325 double[] l_side_x
= new double[n
];
1326 double[] l_side_y
= new double[n
];
1329 for (int i
=0; i
<n
-1; i
++) {
1330 angle
= Math
.atan2(p_i
[0][i
+1] - p_i
[0][i
], p_i
[1][i
+1] - p_i
[1][i
]);
1333 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 ??
1334 r_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
+a90
) * p_width_i
[i
];
1335 l_side_x
[i
] = p_i
[0][i
] + Math
.sin(angle
-a90
) * p_width_i
[i
];
1336 l_side_y
[i
] = p_i
[1][i
] + Math
.cos(angle
-a90
) * p_width_i
[i
];
1340 angle
= Math
.atan2(p_i
[0][m
] - p_i
[0][m
-1], p_i
[1][m
] - p_i
[1][m
-1]);
1342 // side points, displaced by this.x, this.y
1343 r_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
+a90
) * p_width_i
[m
];
1344 r_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
+a90
) * p_width_i
[m
];
1345 l_side_x
[m
] = p_i
[0][m
] + Math
.sin(angle
-a90
) * p_width_i
[m
];
1346 l_side_y
[m
] = p_i
[1][m
] + Math
.cos(angle
-a90
) * p_width_i
[m
];
1348 final long layer_id
= layer
.getId();
1349 //double z_given = layer.getZ();
1351 int first
= 0; // the first backbone point in the subpolygon present in the layer
1352 int last
= 0; // the last backbone point in the subpolygon present in the layer
1354 boolean add_pol
= false;
1355 final ArrayList
<Polygon
> al
= new ArrayList
<Polygon
>();
1357 for (int j
=0; j
<n_points
; j
++) {
1358 if (layer_id
!= p_layer
[j
]) {
1359 first
= j
+ 1; // perhaps next, not this j
1363 // if j is last point, or the next point won't be in the same layer:
1364 if (j
== n_points
-1 || layer_id
!= p_layer
[j
+1]) {
1368 // compute sub polygon
1370 int la
= last
* 20 -1;
1371 if (0 != first
) fi
= (first
* 20) - 10; // 10 is half a segment
1372 if (n_points
-1 != last
) la
+= 10; // same //= last * 20 + 9;
1373 int length
= la
- fi
+ 1; // +1 because fi and la are indexes
1375 int [] pol_x
= new int[length
* 2];
1376 int [] pol_y
= new int[length
* 2];
1377 for (int k
=0, g
=fi
; g
<=la
; g
++, k
++) {
1378 pol_x
[k
] = (int)r_side_x
[g
];
1379 pol_y
[k
] = (int)r_side_y
[g
];
1380 pol_x
[length
+ k
] = (int)l_side_x
[la
- k
];
1381 pol_y
[length
+ k
] = (int)l_side_y
[la
- k
];
1383 al
.add(new Polygon(pol_x
, pol_y
, pol_x
.length
));
1389 if (al
.isEmpty()) return null;
1391 final Polygon
[] pols
= new Polygon
[al
.size()];
1397 /** Scan the Display and link Patch objects that lay under this Pipe's bounding box. */
1398 public boolean linkPatches() {
1399 // 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)
1401 // 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:
1402 unlinkAll(Patch
.class);
1404 HashSet
<Long
> hs
= new HashSet
<Long
>();
1405 boolean must_lock
= false;
1406 for (int l
=0; l
<n_points
; l
++) {
1407 // avoid repeating the ones that have been done
1408 Long lo
= new Long(p_layer
[l
]); // in blankets ...
1409 if (hs
.contains(lo
)) continue;
1412 Layer layer
= layer_set
.getLayer(p_layer
[l
]);
1413 if (null == layer
) {
1414 Utils
.log2("Pipe.linkPatches: ignoring null layer for id " + p_layer
[l
]);
1418 // this bounding box as in the current layer
1419 final Polygon
[] perimeters
= getSubPerimeters(layer
);
1420 if (null == perimeters
) continue;
1422 // for each Patch, check if it underlays this profile's bounding box
1423 final Rectangle box
= new Rectangle();
1424 for (final Displayable displ
: layer
.getDisplayables(Patch
.class)) {
1425 // 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
1426 for (int i
=0; i
<perimeters
.length
; i
++) {
1427 if (perimeters
[i
].intersects(displ
.getBoundingBox(box
))) {
1430 if (displ
.locked
) must_lock
= true;
1431 break; // no need to check more perimeters
1437 // set the locked flag to this and all linked ones
1438 if (must_lock
&& !locked
) {
1446 /** Returns the layer of lowest Z coordinate where this ZDisplayable has a point in, or the creation layer if no points yet. */
1447 public Layer
getFirstLayer() {
1448 if (0 == n_points
) return this.layer
;
1449 if (-1 == n_points
) setupForDisplay(); //reload
1450 Layer la
= this.layer
;
1451 double z
= Double
.MAX_VALUE
;
1452 for (int i
=0; i
<n_points
; i
++) {
1453 Layer layer
= layer_set
.getLayer(p_layer
[i
]);
1454 if (layer
.getZ() < z
) la
= layer
;
1459 synchronized public void exportSVG(StringBuffer data
, double z_scale
, String indent
) {
1460 String in
= indent
+ "\t";
1461 if (-1 == n_points
) setupForDisplay(); // reload
1462 if (0 == n_points
) return;
1463 String
[] RGB
= Utils
.getHexRGBColor(color
);
1464 final double[] a
= new double[6];
1466 data
.append(indent
).append("<path\n")
1467 .append(in
).append("type=\"pipe\"\n")
1468 .append(in
).append("id=\"").append(id
).append("\"\n")
1469 .append(in
).append("transform=\"matrix(").append(a
[0]).append(',')
1470 .append(a
[1]).append(',')
1471 .append(a
[2]).append(',')
1472 .append(a
[3]).append(',')
1473 .append(a
[4]).append(',')
1474 .append(a
[5]).append(")\"\n")
1475 .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")
1476 .append(in
).append("d=\"M")
1478 for (int i
=0; i
<n_points
-1; i
++) {
1479 data
.append(" ").append(p
[0][i
]).append(",").append(p
[1][i
])
1480 .append(" C ").append(p_r
[0][i
]).append(",").append(p_r
[1][i
])
1481 .append(" ").append(p_l
[0][i
+1]).append(",").append(p_l
[1][i
+1])
1484 data
.append(" ").append(p
[0][n_points
-1]).append(',').append(p
[1][n_points
-1]);
1486 .append(in
).append("z=\"")
1488 for (int i
=0; i
<n_points
; i
++) {
1489 data
.append(layer_set
.getLayer(p_layer
[i
]).getZ() * z_scale
).append(",");
1491 data
.append(in
).append("\"\n");
1492 data
.append(in
).append("p_width=\"");
1493 for (int i
=0; i
<n_points
; i
++) {
1494 data
.append(p_width
[i
]).append(",");
1497 .append(in
).append("links=\"");
1498 if (null != hs_linked
&& 0 != hs_linked
.size()) {
1500 int len
= hs_linked
.size();
1501 for (final Displayable d
: hs_linked
) {
1502 data
.append(d
.getId());
1503 if (ii
!= len
-1) data
.append(",");
1507 data
.append(indent
).append("\"\n/>\n");
1510 /** Returns a [p_i[0].length][4] array, with x,y,z,radius on the second part. Not translated to x,y but local!*/
1511 public double[][] getBackbone() {
1512 if (-1 == n_points
) setupForDisplay(); // reload
1513 double[][] b
= new double[p_i
[0].length
][4];
1514 int ni
= 20; // 1/0.05;
1516 for (int j
=0; j
<n_points
-1; j
++) {
1517 double z1
= layer_set
.getLayer(p_layer
[j
]).getZ();
1518 double z2
= layer_set
.getLayer(p_layer
[j
+1]).getZ();
1519 double depth
= z2
- z1
;
1520 double radius1
= p_width
[j
];
1521 double radius2
= p_width
[j
+1];
1522 double dif
= radius2
- radius1
;
1523 for (int i
=start
, k
=0; i
<start
+ ni
; i
++, k
++) {
1524 b
[i
][0] = p_i
[0][i
];
1525 b
[i
][1] = p_i
[1][i
];
1526 b
[i
][2] = z1
+ (k
* depth
) / (double)ni
;
1527 b
[i
][3] = radius1
+ (k
* dif
) / (double)ni
;
1532 start
= p_i
[0].length
-1; // recycling start
1533 b
[start
][0] = p
[0][n_points
-1];
1534 b
[start
][1] = p
[1][n_points
-1];
1535 b
[start
][2] = layer_set
.getLayer(p_layer
[n_points
-1]).getZ();
1536 b
[start
][3] = p_width
[n_points
-1];
1540 /** x,y is the cursor position in offscreen coordinates. */
1541 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.
1543 // #$#@$%#$%!!! 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).
1545 double dx = p_l[0][index] - p[0][index];
1546 double dy = p_l[1][index] - p[1][index];
1547 p_l[0][index] = cx + dx;
1548 p_l[1][index] = cy + dy;
1549 dx = p_r[0][index] - p[0][index];
1550 dy = p_r[1][index] - p[1][index];
1551 p_r[0][index] = cx + dx;
1552 p_r[1][index] = cy + dy;
1556 } else if (-1 != index_l
) {
1557 p_l
[0][index_l
] = cx
;
1558 p_l
[1][index_l
] = cy
;
1559 } else if (-1 != index_r
) {
1560 p_r
[0][index_r
] = cx
;
1561 p_r
[1][index_r
] = cy
;
1563 // drag the whole pipe
1564 // CONCEPTUALLY WRONG, what happens when not dragging the pipe, on mouseEntered? Disaster!
1565 //drag(cx - x_p, cy - y_p);
1569 /** Exports data, the tag is not opened nor closed. */
1570 synchronized public void exportXML(final StringBuilder sb_body
, final String indent
, final XMLOptions options
) {
1571 sb_body
.append(indent
).append("<t2_pipe\n");
1572 final String in
= indent
+ "\t";
1573 super.exportXML(sb_body
, in
, options
);
1574 if (-1 == n_points
) setupForDisplay(); // reload
1575 //if (0 == n_points) return;
1576 final String
[] RGB
= Utils
.getHexRGBColor(color
);
1577 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");
1579 sb_body
.append(in
).append("d=\"M");
1580 for (int i
=0; i
<n_points
-1; i
++) {
1581 sb_body
.append(" ").append(p
[0][i
]).append(",").append(p
[1][i
])
1582 .append(" C ").append(p_r
[0][i
]).append(",").append(p_r
[1][i
])
1583 .append(" ").append(p_l
[0][i
+1]).append(",").append(p_l
[1][i
+1])
1586 sb_body
.append(" ").append(p
[0][n_points
-1]).append(',').append(p
[1][n_points
-1]).append("\"\n");
1587 sb_body
.append(in
).append("layer_ids=\""); // different from 'layer_id' in superclass
1588 for (int i
=0; i
<n_points
; i
++) {
1589 sb_body
.append(p_layer
[i
]);
1590 if (n_points
-1 != i
) sb_body
.append(",");
1592 sb_body
.append("\"\n");
1593 sb_body
.append(in
).append("p_width=\"");
1594 for (int i
=0; i
<n_points
; i
++) {
1595 sb_body
.append(p_width
[i
]);
1596 if (n_points
-1 != i
) sb_body
.append(",");
1598 sb_body
.append("\"\n");
1600 sb_body
.append(indent
).append(">\n");
1601 super.restXML(sb_body
, in
, options
);
1602 sb_body
.append(indent
).append("</t2_pipe>\n");
1605 static public void exportDTD(final StringBuilder sb_header
, final HashSet
<String
> hs
, final String indent
) {
1606 final String type
= "t2_pipe";
1607 if (hs
.contains(type
)) return;
1609 sb_header
.append(indent
).append("<!ELEMENT t2_pipe (").append(Displayable
.commonDTDChildren()).append(")>\n");
1610 Displayable
.exportDTD(type
, sb_header
, hs
, indent
);
1611 sb_header
.append(indent
).append(TAG_ATTR1
).append(type
).append(" d").append(TAG_ATTR2
)
1612 .append(indent
).append(TAG_ATTR1
).append(type
).append(" p_width").append(TAG_ATTR2
)
1613 .append(indent
).append(TAG_ATTR1
).append(type
).append(" layer_ids").append(TAG_ATTR2
)
1618 synchronized public double[][][] generateMesh(double scale
) {
1619 if (-1 == n_points
) setupForDisplay(); //reload
1620 if (0 == n_points
) return null;
1621 // at any given segment (bezier curve defined by 4 points):
1622 // - resample to homogenize point distribution
1623 // - if z doesn't change, use no intermediate sections in the tube
1624 // - for each point:
1625 // - add the section as 12 points, by rotating a perpendicular vector around the direction vector
1626 // - 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)
1628 Utils
.log2("Pipe.generateMesh is not implemented yet.");
1633 /** Performs a deep copy of this object, without the links. */
1634 synchronized public Displayable
clone(final Project pr
, final boolean copy_id
) {
1635 final long nid
= copy_id ?
this.id
: pr
.getLoader().getNextId();
1636 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());
1638 if (-1 == n_points
) setupForDisplay(); // load data
1639 copy
.n_points
= n_points
;
1640 copy
.p
= new double[][]{(double[])this.p
[0].clone(), (double[])this.p
[1].clone()};
1641 copy
.p_l
= new double[][]{(double[])this.p_l
[0].clone(), (double[])this.p_l
[1].clone()};
1642 copy
.p_r
= new double[][]{(double[])this.p_r
[0].clone(), (double[])this.p_r
[1].clone()};
1643 copy
.p_layer
= (long[])this.p_layer
.clone();
1644 copy
.p_width
= (double[])this.p_width
.clone();
1645 copy
.p_i
= new double[][]{(double[])this.p_i
[0].clone(), (double[])this.p_i
[1].clone()};
1646 copy
.p_width_i
= (double[])this.p_width_i
.clone();
1647 copy
.addToDatabase();
1653 synchronized public List
<Point3f
> generateTriangles(double scale
, int parallels
, int resample
) {
1654 if (n_points
< 2) return null;
1655 // check minimum requirements.
1656 if (parallels
< 3) parallels
= 3;
1658 double[][][] all_points
= generateJoints(parallels
, resample
, layer_set
.getCalibrationCopy());
1659 return Pipe
.generateTriangles(all_points
, scale
);
1662 /** 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. */
1663 static public List
<Point3f
> generateTriangles(final double[][][] all_points
, final double scale
) {
1664 int n
= all_points
.length
;
1665 final int parallels
= all_points
[0].length
-1;
1666 List
<Point3f
> list
= new ArrayList
<Point3f
>();
1667 for (int i
=0; i
<n
-1; i
++) { //minus one since last is made with previous
1668 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
1669 // first triangle in the quad
1670 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
)));
1671 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
)));
1672 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
)));
1674 // second triangle in the quad
1675 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
)));
1676 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
)));
1677 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
)));
1683 /** From my former program, A_3D_Editing.java and Pipe.java */
1684 private double[][][] generateJoints(final int parallels
, final int resample
, final Calibration cal
) {
1685 if (-1 == n_points
) setupForDisplay();
1687 // local pointers, since they may be transformed
1688 int n_points
= this.n_points
;
1689 double[][] p
= this.p
;
1690 //double[][] p_r = this.p_r;
1691 //double[][] p_l = this.p_l;
1692 double[][] p_i
= this.p_i
;
1693 //double[] p_width = this.p_width;
1694 double[] p_width_i
= this.p_width_i
;
1695 if (!this.at
.isIdentity()) {
1696 final Object
[] ob
= getTransformedData();
1697 p
= (double[][])ob
[0];
1698 n_points
= p
[0].length
;
1699 //p_l = (double[][])ob[1];
1700 //p_r = (double[][])ob[2];
1701 p_i
= (double[][])ob
[3];
1702 //p_width = (double[])ob[4];
1703 p_width_i
= (double[])ob
[5];
1706 int n
= p_i
[0].length
;
1707 final int mm
= n_points
;
1708 final double[] z_values
= new double[n
];
1709 final int interval_points
= n
/ (mm
-1);
1711 double z_val_next
= 0;
1716 for (int j
=0; j
<mm
-1; j
++) {
1717 z_val
= layer_set
.getLayer(p_layer
[j
]).getZ();
1718 z_val_next
= layer_set
.getLayer(p_layer
[j
+1]).getZ();
1719 z_diff
= z_val_next
- z_val
;
1720 delta
= z_diff
/interval_points
;
1721 z_values
[c
] = (0 == j ? z_val
: z_values
[c
-1]) + delta
;
1722 for (int k
=1; k
<interval_points
; k
++) {
1724 z_values
[c
] = z_values
[c
-1] + delta
;
1728 //setting last point
1729 z_values
[n
-1] = layer_set
.getLayer(p_layer
[mm
-1]).getZ();
1732 return makeTube(p_i
[0], p_i
[1], z_values
, p_width_i
, resample
, parallels
, cal
);
1735 static public double[][][] makeTube(double[] px
, double[] py
, double[] pz
, double[] p_width_i
, final int resample
, final int parallels
, final Calibration cal
) {
1739 // Resampling to get a smoother pipe
1741 VectorString3D vs
= new VectorString3D(px
, py
, pz
, false);
1744 for (int i
=0; i
<p_width_i
.length
; i
++)
1745 p_width_i
[i
] *= cal
.pixelWidth
;
1747 vs
.addDependent(p_width_i
);
1748 // Resample to the largest of the two:
1749 double avg_delta
= vs
.getAverageDelta(); // calibrated
1750 double delta
= Math
.max(avg_delta
, 1); // can't use resample, pipes would look very segmented
1752 //vs.resample(vs.getAverageDelta() * resample);
1754 px
= vs
.getPoints(0);
1755 py
= vs
.getPoints(1);
1756 pz
= vs
.getPoints(2);
1757 p_width_i
= vs
.getDependent(0);
1758 //Utils.log("lengths: " + px.length + ", " + py.length + ", " + pz.length + ", " + p_width_i.length);
1760 } catch (Exception e
) {
1765 double[][][] all_points
= new double[n
+2][parallels
+1][3];
1766 int extra
= 1; // this was zero when not doing capping
1767 for (int cap
=0; cap
<parallels
+1; cap
++) {
1768 all_points
[0][cap
][0] = px
[0];//p_i[0][0]; //x
1769 all_points
[0][cap
][1] = py
[0]; //p_i[1][0]; //y
1770 all_points
[0][cap
][2] = pz
[0]; //z_values[0];
1771 all_points
[all_points
.length
-1][cap
][0] = px
[n
-1]; //p_i[0][p_i[0].length-1];
1772 all_points
[all_points
.length
-1][cap
][1] = py
[n
-1]; //p_i[1][p_i[0].length-1];
1773 all_points
[all_points
.length
-1][cap
][2] = pz
[n
-1]; //z_values[z_values.length-1];
1775 double angle
= 2*Math
.PI
/parallels
; //Math.toRadians(30);
1779 Vector3
[] circle
= new Vector3
[parallels
+1];
1781 int half_parallels
= parallels
/2;
1782 for (int i
=0; i
<n
-1; i
++) {
1783 //Utils.log2(i + " : " + px[i] + ", " + py[i] + ", " + pz[i]);
1784 //First vector: from one realpoint to the next
1785 //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]);
1786 v3_P12
= new Vector3(px
[i
+1] - px
[i
], py
[i
+1] - py
[i
], pz
[i
+1] - pz
[i
]);
1788 //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)
1790 //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
1792 //chosen random vector: the same vector, but with x = 0;
1794 1 1 1 1 1 1 1 1 1 1 1 1
1795 v1 v2 v3 P12[0] P12[1] P12[2] P12[0] P12[1] P12[2] P12[0] P12[1] P12[2]
1796 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
1798 cross product: v ^ w = (v2*w3 - w2*v3, v3*w1 - v1*w3, v1*w2 - w1*v2);
1800 cross product of second: v ^ w = (b*(c+1) - c*(b+1), c*(a+1) - a*(c+1) , a*(b+1) - b*(a+1))
1801 = ( b - c , c - a , a - b )
1803 cross product of third: v ^ w = (b*(c+1) - b*c, c*a - a*(c+1), a*b - b*a)
1805 (v3_P12.y ,-v3_P12.x , 0);
1808 Reasons why I use the third:
1809 -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,
1810 thus responsible for soft shiftings at joints where z values change
1811 -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
1812 -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à .
1816 // BELOW if-else statements needed to correct the orientation of vectors, so there's no discontinuity
1818 v3_PR
= new Vector3(v3_P12
.y
, -v3_P12
.x
, 0);
1819 v3_PR
= v3_PR
.normalize(v3_PR
);
1820 v3_PR
= v3_PR
.scale(p_width_i
[i
], v3_PR
);
1822 //vectors are perfectly normalized and scaled
1823 //The problem then must be that they are not properly ortogonal and so appear to have a smaller width.
1824 // -not only not ortogonal but actually messed up in some way, i.e. bad coords.
1826 circle
[half_parallels
] = v3_PR
;
1827 for (int q
=half_parallels
+1; q
<parallels
+1; q
++) {
1828 sinn
= Math
.sin(angle
*(q
-half_parallels
));
1829 coss
= Math
.cos(angle
*(q
-half_parallels
));
1830 circle
[q
] = Vector3
.rotate_v_around_axis(v3_PR
, v3_P12
, sinn
, coss
);
1832 circle
[0] = circle
[parallels
];
1833 for (int qq
=1; qq
<half_parallels
; qq
++) {
1834 sinn
= Math
.sin(angle
*(qq
+half_parallels
));
1835 coss
= Math
.cos(angle
*(qq
+half_parallels
));
1836 circle
[qq
] = Vector3
.rotate_v_around_axis(v3_PR
, v3_P12
, sinn
, coss
);
1839 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
1842 -if y coord shifted, then no thinnings but yes shiftings
1843 -if x coord shifted, THEN PERFECT
1844 -if both shifted, then both thinnings and shiftings
1845 -if none shifted, then no shiftings but yes thinnings
1848 v3_PR
= v3_PR
.normalize(v3_PR
);
1849 if (null == v3_PR
) {
1850 Utils
.log2("vp_3r is null: most likely a point was repeated in the list, and thus the vector has length zero.");
1852 v3_PR
= v3_PR
.scale(
1857 for (int q
=1; q
<parallels
; q
++) {
1858 sinn
= Math
.sin(angle
*q
);
1859 coss
= Math
.cos(angle
*q
);
1860 circle
[q
] = Vector3
.rotate_v_around_axis(v3_PR
, v3_P12
, sinn
, coss
);
1862 circle
[parallels
] = v3_PR
;
1864 // Adding points to main array
1865 for (int j
=0; j
<parallels
+1; j
++) {
1866 all_points
[i
+extra
][j
][0] = /*p_i[0][i]*/ px
[i
] + circle
[j
].x
;
1867 all_points
[i
+extra
][j
][1] = /*p_i[1][i]*/ py
[i
] + circle
[j
].y
;
1868 all_points
[i
+extra
][j
][2] = /*z_values[i]*/ pz
[i
] + circle
[j
].z
;
1871 for (int k
=0; k
<parallels
+1; k
++) {
1872 all_points
[n
-1+extra
][k
][0] = /*p_i[0][n-1]*/ px
[n
-1] + circle
[k
].x
;
1873 all_points
[n
-1+extra
][k
][1] = /*p_i[1][n-1]*/ py
[n
-1] + circle
[k
].y
;
1874 all_points
[n
-1+extra
][k
][2] = /*z_values[n-1]*/ pz
[n
-1] + circle
[k
].z
;
1879 synchronized private Object
[] getTransformedData() {
1880 final int n_points
= this.n_points
;
1881 final double[][] p
= transformPoints(this.p
, n_points
);
1882 final double[][] p_l
= transformPoints(this.p_l
, n_points
);
1883 final double[][] p_r
= transformPoints(this.p_r
, n_points
);
1884 final double[][] p_i
= transformPoints(this.p_i
, this.p_i
[0].length
); // whatever length it has
1885 final double[] p_width
= new double[n_points
]; // first contains the data, then the transformed data
1886 System
.arraycopy(this.p_width
, 0, p_width
, 0, n_points
);
1887 final double[] p_width_i
= new double[this.p_width_i
.length
]; // first contains the data, then the transformed data
1888 System
.arraycopy(this.p_width_i
, 0, p_width_i
, 0, p_width_i
.length
);
1889 // p_width: same rule as for Ball: average of x and y
1890 double[][] pw
= new double[2][n_points
];
1891 for (int i
=0; i
<n_points
; i
++) {
1892 pw
[0][i
] = this.p
[0][i
] + p_width
[i
]; //built relative to the untransformed points!
1893 pw
[1][i
] = this.p
[1][i
] + p_width
[i
];
1895 pw
= transformPoints(pw
);
1896 //final double[] p_width = new double[n_points];
1897 for (int i
=0; i
<n_points
; i
++) {
1898 // plain average of differences in X and Y axis, relative to the transformed points.
1899 p_width
[i
] = (Math
.abs(pw
[0][i
] - p
[0][i
]) + Math
.abs(pw
[1][i
] - p
[1][i
])) / 2;
1901 // same with p_width_i
1902 double[][] pwi
= new double[2][p_i
[0].length
];
1903 for (int i
=0; i
<p_i
[0].length
; i
++) {
1904 pwi
[0][i
] = this.p_i
[0][i
] + p_width_i
[i
]; //built relative to the untransformed points!
1905 pwi
[1][i
] = this.p_i
[1][i
] + p_width_i
[i
];
1907 pwi
= transformPoints(pwi
);
1908 //final double[] p_width_i = new double[p_i[0].length];
1909 for (int i
=0; i
<p_i
[0].length
; i
++) {
1910 // plain average of differences in X and Y axis, relative to the transformed points.
1911 p_width_i
[i
] = (Math
.abs(pwi
[0][i
] - p_i
[0][i
]) + Math
.abs(pwi
[1][i
] - p_i
[1][i
])) / 2;
1914 return new Object
[]{p
, p_l
, p_r
, p_i
, p_width
, p_width_i
};
1917 /** Returns a non-calibrated VectorString3D. */
1918 synchronized public VectorString3D
asVectorString3D() {
1919 // local pointers, since they may be transformed
1920 int n_points
= this.n_points
;
1921 double[][] p
= this.p
;
1922 //double[][] p_r = this.p_r;
1923 //double[][] p_l = this.p_l;
1924 double[][] p_i
= this.p_i
;
1925 //double[] p_width = this.p_width;
1926 double[] p_width_i
= this.p_width_i
;
1927 if (!this.at
.isIdentity()) {
1928 final Object
[] ob
= getTransformedData();
1929 p
= (double[][])ob
[0];
1930 n_points
= p
[0].length
;
1931 //p_l = (double[][])ob[1];
1932 //p_r = (double[][])ob[2];
1933 p_i
= (double[][])ob
[3];
1934 //p_width = (double[])ob[4];
1935 p_width_i
= (double[])ob
[5];
1938 final int n
= p_i
[0].length
;
1939 final int mm
= n_points
;
1940 final double[] z_values
= new double[n
];
1941 final int interval_points
= n
/ (mm
-1);
1943 double z_val_next
= 0;
1948 for (int j
=0; j
<mm
-1; j
++) {
1949 z_val
= layer_set
.getLayer(p_layer
[j
]).getZ();
1950 z_val_next
= layer_set
.getLayer(p_layer
[j
+1]).getZ();
1951 z_diff
= z_val_next
- z_val
;
1952 delta
= z_diff
/interval_points
;
1953 z_values
[c
] = (0 == j ? z_val
: z_values
[c
-1]) + delta
;
1954 for (int k
=1; k
<interval_points
; k
++) {
1956 z_values
[c
] = z_values
[c
-1] + delta
;
1960 //setting last point
1961 z_values
[n
-1] = layer_set
.getLayer(p_layer
[mm
-1]).getZ();
1963 final double[] px
= p_i
[0];
1964 final double[] py
= p_i
[1];
1965 final double[] pz
= z_values
;
1966 VectorString3D vs
= null;
1968 vs
= new VectorString3D(px
, py
, pz
, false);
1969 vs
.addDependent(p_width_i
);
1970 } catch (Exception e
) { IJError
.print(e
); }
1974 public String
getInfo() {
1975 if (-1 == n_points
) setupForDisplay(); //reload
1979 VectorString3D vs
= asVectorString3D();
1980 vs
.calibrate(this.layer_set
.getCalibration());
1981 len
= vs
.computeLength(); // no resampling
1983 return new StringBuilder("Length: ").append(Utils
.cutNumber(len
, 2, true)).append(' ').append(this.layer_set
.getCalibration().getUnits()).append('\n').toString();
1986 /** @param area is expected in world coordinates. */
1987 public boolean intersects(final Area area
, final double z_first
, final double z_last
) {
1988 // find lowest and highest Z
1989 double min_z
= Double
.MAX_VALUE
;
1991 for (int i
=0; i
<n_points
; i
++) {
1992 double laz
=layer_set
.getLayer(p_layer
[i
]).getZ();
1993 if (laz
< min_z
) min_z
= laz
;
1994 if (laz
> max_z
) max_z
= laz
;
1996 if (z_last
< min_z
|| z_first
> max_z
) return false;
1998 for (int i
=0; i
<n_points
; i
++) {
1999 final Polygon
[] pol
= getSubPerimeters(layer_set
.getLayer(p_layer
[i
]));
2000 if (null == pol
) continue;
2001 for (int k
=0; k
<pol
.length
; k
++) {
2002 Area a
= new Area(pol
[k
]); // perimeters already in world coords
2004 Rectangle r
= a
.getBounds();
2005 if (0 != r
.width
&& 0 != r
.height
) return true;
2011 /** Expects Rectangle in world coords. */
2012 public boolean intersects(final Layer layer
, final Rectangle r
) {
2013 final Polygon
[] pol
= getSubPerimeters(layer
); // transformed
2014 if (null == pol
) return false;
2015 for (Polygon p
: pol
) if (new Area(p
).intersects(r
.x
, r
.y
, r
.width
, r
.height
)) return true;
2018 /** Expects Area in world coords. */
2019 public boolean intersects(final Layer layer
, final Area area
) {
2020 final Polygon
[] pol
= getSubPerimeters(layer
); // transformed
2021 if (null == pol
) return false;
2022 for (Polygon p
: pol
) if (M
.intersects(new Area(p
), area
)) return true;
2026 /** Returns the bounds of this object as it shows in the given layer, set into @param r. */
2027 public Rectangle
getBounds(final Rectangle r
, final Layer layer
) {
2028 // obtain the already transformed subperimeters
2029 final Polygon
[] pol
= getSubPerimeters(layer
);
2031 if (null == r
) return new Rectangle();
2038 final Area area
= new Area();
2039 for (Polygon p
: pol
) {
2040 area
.add(new Area(p
));
2042 final Rectangle b
= area
.getBounds();
2043 if (null == r
) return b
;
2044 r
.setBounds(b
.x
, b
.y
, b
.width
, b
.height
);
2050 // debug ---------------
2051 static private void showShape(final Shape shape) {
2052 Area area = new Area(shape);
2053 Rectangle b = area.getBounds();
2054 AffineTransform trans = new AffineTransform();
2055 trans.translate(-b.x, -b.y);
2056 area = area.createTransformedArea(trans);
2057 ij.process.ByteProcessor bp = new ij.process.ByteProcessor(b.width, b.height);
2058 ij.gui.ShapeRoi sr = new ij.gui.ShapeRoi(area);
2059 ij.ImagePlus imp = new ij.ImagePlus("pipe area", bp);
2068 public ResultsTable
measure(ResultsTable rt
) {
2069 if (-1 == n_points
) setupForDisplay(); //reload
2070 if (0 == n_points
) return rt
;
2071 if (null == rt
) rt
= Utils
.createResultsTable("Pipe results", new String
[]{"id", "length", "name-id"});
2074 Calibration cal
= layer_set
.getCalibration();
2076 VectorString3D vs
= asVectorString3D();
2078 len
= vs
.computeLength(); // no resampling
2080 rt
.incrementCounter();
2081 rt
.addLabel("units", cal
.getUnit());
2082 rt
.addValue(0, this.id
);
2083 rt
.addValue(1, len
);
2084 rt
.addValue(2, getNameId());
2089 final Class
<?
> getInternalDataPackageClass() {
2090 return DPPipe
.class;
2094 synchronized Object
getDataPackage() {
2095 return new DPPipe(this);
2098 static private final class DPPipe
extends Displayable
.DataPackage
{
2099 final double[][] p
, p_l
, p_r
, p_i
;
2100 final double[] p_width
, p_width_i
;
2101 final long[] p_layer
;
2103 DPPipe(final Pipe pipe
) {
2105 // store copies of all arrays
2106 this.p
= new double[][]{Utils
.copy(pipe
.p
[0], pipe
.n_points
), Utils
.copy(pipe
.p
[1], pipe
.n_points
)};
2107 this.p_r
= new double[][]{Utils
.copy(pipe
.p_r
[0], pipe
.n_points
), Utils
.copy(pipe
.p_r
[1], pipe
.n_points
)};
2108 this.p_l
= new double[][]{Utils
.copy(pipe
.p_l
[0], pipe
.n_points
), Utils
.copy(pipe
.p_l
[1], pipe
.n_points
)};
2109 this.p_width
= Utils
.copy(pipe
.p_width
, pipe
.n_points
);
2110 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
)};
2111 this.p_width_i
= Utils
.copy(pipe
.p_width_i
, pipe
.p_width_i
.length
);
2112 this.p_layer
= new long[pipe
.n_points
]; System
.arraycopy(pipe
.p_layer
, 0, this.p_layer
, 0, pipe
.n_points
);
2115 final boolean to2(final Displayable d
) {
2117 final Pipe pipe
= (Pipe
)d
;
2118 final int len
= p
[0].length
; // == n_points, since it was cropped on copy
2119 pipe
.p
= new double[][]{Utils
.copy(p
[0], len
), Utils
.copy(p
[1], len
)};
2120 pipe
.n_points
= p
[0].length
;
2121 pipe
.p_r
= new double[][]{Utils
.copy(p_r
[0], len
), Utils
.copy(p_r
[1], len
)};
2122 pipe
.p_l
= new double[][]{Utils
.copy(p_l
[0], len
), Utils
.copy(p_l
[1], len
)};
2123 pipe
.p_layer
= new long[len
]; System
.arraycopy(p_layer
, 0, pipe
.p_layer
, 0, len
);
2124 pipe
.p_width
= Utils
.copy(p_width
, len
);
2125 pipe
.p_i
= new double[][]{Utils
.copy(p_i
[0], p_i
[0].length
), Utils
.copy(p_i
[1], p_i
[1].length
)};
2126 pipe
.p_width_i
= Utils
.copy(p_width_i
, p_width_i
.length
);
2131 /** Reverses the order of the points in the arrays. */
2132 synchronized public void reverse() {
2133 for (int i
=0; i
<n_points
/2; i
++) {
2134 final int j
= n_points
-1 -i
;
2138 long l
= p_layer
[i
]; // we love java and it's lack of primitive abstraction.
2139 p_layer
[i
] = p_layer
[j
];
2141 double r
= p_width
[i
];
2142 p_width
[i
] = p_width
[j
];
2145 // what was left is now right:
2149 // Nothing should change, but let's see it:
2150 generateInterpolatedPoints(0.05);
2153 /** Helper function to swap both X and Y from index i to j. */
2154 static private final void _swap(final double[][] a
, final int i
, final int j
) {
2155 for (int k
=0; k
<2; k
++) {
2156 double tmp
= a
[k
][i
];
2162 /** Retain the data within the layer range, and through out all the rest. */
2163 synchronized public boolean crop(List
<Layer
> range
) {
2164 if (-1 == n_points
) setupForDisplay();
2165 HashSet
<Long
> lids
= new HashSet
<Long
>();
2166 for (Layer l
: range
) {
2167 lids
.add(l
.getId());
2169 for (int i
=0; i
<n_points
; i
++) {
2170 if (!lids
.contains(p_layer
[i
])) {
2175 generateInterpolatedPoints(0.05);
2176 calculateBoundingBox(true, null);
2180 synchronized protected boolean layerRemoved(Layer la
) {
2181 super.layerRemoved(la
);
2182 for (int i
=0; i
<p_layer
.length
; i
++) {
2183 if (la
.getId() == p_layer
[i
]) {
2191 synchronized public boolean apply(final Layer la
, final Area roi
, final mpicbg
.models
.CoordinateTransform ict
) throws Exception
{
2192 float[] fp
= new float[2];
2193 mpicbg
.models
.CoordinateTransform chain
= null;
2194 Area localroi
= null;
2195 AffineTransform inverse
= null;
2196 for (int i
=0; i
<n_points
; i
++) {
2197 if (p_layer
[i
] == la
.getId()) {
2198 if (null == localroi
) {
2199 inverse
= this.at
.createInverse();
2200 localroi
= roi
.createTransformedArea(inverse
);
2202 if (localroi
.contains(p
[0][i
], p
[1][i
])) {
2203 if (null == chain
) {
2204 chain
= M
.wrap(this.at
, ict
, inverse
);
2208 double ox
= p
[0][i
],
2210 // Transform the point
2211 M
.apply(chain
, p
, i
, fp
);
2212 // For radius, assume it's a point to the right of the center point
2213 fp
[0] = (float)(ox
+ p_width
[i
]);
2215 chain
.applyInPlace(fp
);
2216 p_width
[i
] = Math
.abs(fp
[0] - p
[0][i
]);
2217 // The two associated control points:
2218 M
.apply(chain
, p_l
, i
, fp
);
2219 M
.apply(chain
, p_r
, i
, fp
);
2223 if (null != chain
) {
2224 generateInterpolatedPoints(0.05);
2225 calculateBoundingBox(true, la
);
2230 public boolean apply(final VectorDataTransform vdt
) throws Exception
{
2231 final float[] fp
= new float[2];
2232 final VectorDataTransform vlocal
= vdt
.makeLocalTo(this);
2233 for (int i
=0; i
<n_points
; i
++) {
2234 if (vdt
.layer
.getId() == p_layer
[i
]) {
2235 for (final VectorDataTransform
.ROITransform rt
: vlocal
.transforms
) {
2236 if (rt
.roi
.contains(p
[0][i
], p
[1][i
])) {
2238 double ox
= p
[0][i
],
2240 // Transform the point
2241 M
.apply(rt
.ct
, p
, i
, fp
);
2242 // For radius, assume it's a point to the right of the center point
2243 fp
[0] = (float)(ox
+ p_width
[i
]);
2245 rt
.ct
.applyInPlace(fp
);
2246 p_width
[i
] = Math
.abs(fp
[0] - p
[0][i
]);
2247 // The two associated control points:
2248 M
.apply(rt
.ct
, p_l
, i
, fp
);
2249 M
.apply(rt
.ct
, p_r
, i
, fp
);
2255 generateInterpolatedPoints(0.05);
2256 calculateBoundingBox(true, vlocal
.layer
);
2261 synchronized public Collection
<Long
> getLayerIds() {
2262 return Utils
.asList(p_layer
, 0, n_points
);
2266 synchronized public Area
getAreaAt(final Layer layer
) {
2267 final Area a
= new Area();
2268 final Polygon
[] pols
= getSubPerimeters(layer
); // in world coords
2269 if (null == pols
) return a
;
2270 for (final Polygon pol
: pols
) a
.add(new Area(pol
));
2275 synchronized public boolean isRoughlyInside(final Layer layer
, final Rectangle r
) {
2276 final Polygon
[] pols
= getSubPerimeters(layer
); // in world coords
2277 if (null == pols
) return false;
2278 for (final Polygon pol
: pols
) {
2279 if (pol
.intersects(r
)) return true;