Major cleanup of Utils class.
[trakem2.git] / ini / trakem2 / display / Pipe.java
blob2da57cb20c316bdad036190fc1c4a6baf27a52d7
1 /**
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.
21 **/
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;
45 import java.util.Map;
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);
85 n_points = 0;
86 p = new double[2][5];
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];
91 addToDatabase();
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;
98 this.alpha = alpha;
99 this.visible = visible;
100 this.color = color;
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")) {
113 // parse the points
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]
119 // first point:
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();
123 al_p.add(point);
124 boolean go = true;
125 while (go) {
126 i_start = i_end;
127 i_end = data.indexOf('C', i_end+1);
128 if (-1 == i_end) {
129 i_end = data.length() -1;
130 go = false;
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(", ", ",");
140 // cut by spaces
141 String[] points = txt.split(" ");
142 if (3 == points.length) {
143 al_p_r.add(points[0]);
144 al_p_l.add(points[1]);
145 al_p.add(points[2]);
146 } else {
147 // error
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));
155 // sanity check:
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]);
190 // finish up
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) {
195 this.n_points = 0;
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];
202 // sanity check:
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() {
211 //catch length
212 int length = p[0].length;
213 //make copies
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];
219 //copy values
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);
228 //assign them
229 this.p = p_copy;
230 this.p_l = p_l_copy;
231 this.p_r = p_r_copy;
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) {
238 int index = -1;
239 double d = (10.0D / magnification);
240 if (d < 2) d = 2;
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) {
246 min_dist = dist;
247 index = i;
250 return index;
253 /**Remove a point from the bezier backbone and its two associated control points.*/
254 synchronized protected void removePoint(int index) {
255 // check preconditions:
256 if (index < 0) {
257 return;
258 } else if (n_points - 1 == index) {
259 //last point out
260 n_points--;
261 } else {
262 //one point out (but not the last)
263 --n_points;
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.
268 p[1][i] = p[1][i+1];
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];
278 //update in database
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) {
289 p[0][index] += dx;
290 p[1][index] += dy;
291 p_l[0][index] += dx;
292 p_l[1][index] += dy;
293 p_r[0][index] += dx;
294 p_r[1][index] += 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
307 double hypothenusa;
308 if (symmetric) {
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]);
311 } else {
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;
317 //apply
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);
335 //check array size
336 if (p[0].length == n_points) {
337 enlargeArrays();
339 //decide:
340 if (0 == n_points || 1 == n_points || index + 1 == n_points) {
341 //append at the end
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
346 index = n_points;
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) {
361 //append at the end
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];
366 index = n_points;
367 } else {
368 // prepend at the beginning
369 for (int i=n_points-1; i>-1; i--) {
370 p[0][i+1] = p[0][i];
371 p[1][i+1] = p[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;
383 index = 0;
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);
387 } else {
388 //insert at index:
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);
427 //add one up
428 this.n_points++;
430 return index;
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) {
434 int index = -1;
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) {
443 index = i;
444 distance_sq = distance_sq_i;
447 if (-1 != index) {
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)) {
451 index_found--;
453 index = index_found;
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;
457 return index;
459 synchronized protected void generateInterpolatedPoints(double bezier_finess) {
460 if (0 == n_points) {
461 return;
464 int n = n_points;
466 // case there's only one point
467 if (1 == n_points) {
468 p_i = new double[2][1];
469 p_i[0][0] = p[0][0];
470 p_i[1][0] = p[1][0];
471 p_width_i = new double[1];
472 p_width_i[0] = p_width[0];
473 return;
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;
479 int next = 0;
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);
484 f2 = 3*t*t*(1-t);
485 f3 = t*t*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;
489 next++;
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);
497 p_i = p_i_copy;
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);
509 p_i = p_i_copy;
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];
515 next++;
517 if (p_i[0].length != next) { // 'next' works as a length here
518 //resize back
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);
524 p_i = p_i_copy;
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
534 setupForDisplay();
536 //arrange transparency
537 Composite original_composite = null;
538 if (alpha != 1.0f) {
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();
566 if (active) {
567 // draw/fill points
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);
574 g.setColor(color);
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.
585 double angle = 0;
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];
595 int m = n-1;
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();
615 if (no_color_cues) {
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
621 if (no_color_cues) {
622 if (layer_id == p_layer[j]) {
623 // paint normally
624 } else {
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
632 } else {
633 continue;
636 } else {
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);
643 int fi = 0;
644 int la = j * 20 -1;
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;
650 try {
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);
685 x_p = (int)po.x;
686 y_p = (int)po.y;
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);
695 } else {
696 index = findPoint(p, x_p, y_p, mag);
699 if (-1 != index) {
700 if (Utils.isControlDown(me) && me.isShiftDown() && p_layer[index] == Display.getFrontLayer(this.project).getId()) {
701 //delete point
702 removePoint(index);
703 index = index_r = index_l = -1;
704 repaint(false);
705 return;
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);
712 return;
716 // find if click is on a left control point
717 index_l = findPoint(p_l, x_p, y_p, mag);
718 index_r = -1;
719 // if not, then try on the set of right control points
720 if (-1 == index_l) {
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);
733 is_new_point = true;
734 if (0 == index_l) { //1 == n_points)
735 //for the very first point, drag the right control point, not the left.
736 index_r = index_l;
737 index_l = -1;
738 } else {
739 generateInterpolatedPoints(0.05);
741 repaint(false);
742 return;
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);
751 x_p = (int)p.x;
752 y_p = (int)p.y;
753 final Point2D.Double pd = inverseTransformPoint(x_d, y_d);
754 x_d = (int)pd.x;
755 y_d = (int)pd.y;
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:
765 if (-1 != index) {
766 if (!me.isAltDown() && !me.isShiftDown()) {
767 dragPoint(index, x_d - x_d_old, y_d - y_d_old);
768 } else if (me.isShiftDown()) {
769 // resize radius
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);
778 repaint(false);
779 return;
782 //if a control point is found, then drag it, adjusting the other control point non-symmetrically
783 if (-1 != index_r) {
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);
786 repaint(false);
787 return;
789 if (-1 != index_l) {
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);
792 repaint(false);
793 return;
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");
811 if (is_new_point) {
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
821 // update all
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
830 repaint(true);
832 // reset
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;
840 double max_x = 0.0D;
841 double max_y = 0.0D;
842 if (0 == n_points) {
843 this.width = this.height = 0;
844 layer_set.updateBucket(this);
845 return;
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) {
850 // check the tube
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() {
893 super.destroy();
894 p = null;
895 p_l = null;
896 p_r = null;
897 p_layer = null;
898 p_width = null;
899 p_i = null;
900 p_width_i = null;
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() {
905 p = null;
906 p_l = null;
907 p_r = null;
908 p_i = null;
909 p_width = null;
910 p_layer = null;
911 n_points = -1; // flag that points exist but are not loaded
914 public void repaint() {
915 repaint(true);
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() {
929 // load points
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();
939 int i = 0;
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();
950 i++;
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;
960 double angle = 0;
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];
969 int m = n-1;
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];
980 // last point
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(",")
1076 .append(p_layer[i])
1077 .append(")");
1078 ; //end
1079 sql[i] = sb.toString();
1081 return sql;
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)
1098 ; //end
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)
1110 ; //end
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)
1122 ; //end
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();
1133 return n_points;
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;
1140 // make x,y local
1141 final Point2D.Double po = inverseTransformPoint(x, y);
1142 x = (int)po.x;
1143 y = (int)po.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
1146 else return false;
1148 final boolean no_color_cues = "true".equals(project.getProperty("no_color_cues"));
1150 double angle = 0;
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];
1160 int m = n-1;
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];
1172 // last point
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
1192 continue;
1194 last = 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]) {
1197 add_pol = true;
1199 int fi=0, la=0;
1200 if (no_color_cues) {
1201 // else if crossed the current layer, check segment as well
1202 if (0 == j) continue;
1203 first = j -1;
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) {
1215 add_pol = true;
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) ) {
1220 add_pol = true;
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) ) {
1225 add_pol = true;
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) ) {
1230 add_pol = true;
1231 // crossing by without a point: short polygons
1232 fi = ((j-1) * 20) + 10;
1233 la = ( j * 20) - 10;
1234 } else {
1235 //add_pol = false;
1236 continue;
1238 // correct ends
1239 if (0 == j-1) fi = 0;
1240 if (n_points-1 == j) la = n_points * 20;
1242 if (add_pol) {
1243 // compute sub polygon
1244 if (!no_color_cues) {
1245 fi = 0;
1246 la = last * 20 -1;
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;
1249 } else {
1250 if (fi < 0) fi = 0;
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);
1266 //showShape(pol);
1267 return true;
1269 // reset
1270 first = j + 1;
1271 add_pol = false;
1274 return false;
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];
1300 double angle = 0;
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];
1310 int m = n-1;
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]);
1315 // side points
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];
1322 // last point
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();
1333 //double z = 0;
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
1343 continue;
1345 last = 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]) {
1348 add_pol = true;
1350 if (add_pol) {
1351 // compute sub polygon
1352 int fi = 0;
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));
1367 // reset
1368 first = j + 1;
1369 add_pol = false;
1372 if (al.isEmpty()) return null;
1373 else {
1374 final Polygon[] pols = new Polygon[al.size()];
1375 al.toArray(pols);
1376 return pols;
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;
1392 else hs.add(lo);
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))) {
1410 // Link the patch
1411 this.link(displ);
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;
1429 return la;
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];
1438 at.getMatrix(a);
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]);
1458 data.append("\"\n")
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(",");
1469 data.append("\"\n")
1470 .append(in).append("links=\"");
1471 if (null != hs_linked && 0 != hs_linked.size()) {
1472 int ii = 0;
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(",");
1478 ii++;
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;
1489 int start = 0;
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;
1503 start += ni;
1505 // last point
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];
1511 return b;
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.
1516 if (-1 != index) {
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;
1527 p[0][index] = cx;
1528 p[1][index] = cy;
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;
1536 } else {
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");
1552 if (n_points > 0) {
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;
1582 hs.add(type);
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)
1600 // debug:
1601 return null;
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());
1608 // The data:
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();
1620 return copy;
1623 /** Calibrated. */
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)));
1651 return list;
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);
1681 double z_val = 0;
1682 double z_val_next = 0;
1683 double z_diff = 0;
1684 int c = 0;
1685 double delta = 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++) {
1694 c++;
1695 z_values[c] = z_values[c-1] + delta;
1697 c++;
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) {
1708 int n = px.length;
1710 // Resampling to get a smoother pipe
1711 try {
1712 VectorString3D vs = new VectorString3D(px, py, pz, false);
1713 if (null != cal) {
1714 vs.calibrate(cal);
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);
1730 n = vs.length();
1731 } catch (Exception e) {
1732 IJError.print(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);
1748 Vector3 v3_P12;
1749 Vector3 v3_PR;
1750 Vector3[] circle = new Vector3[parallels+1];
1751 double sinn, coss;
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;
1764 /* matrix:
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)
1775 (b ,-a , 0);
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
1788 if (v3_P12.y < 0) {
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);
1809 } else {
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
1812 Observations:
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(
1824 p_width_i[i],
1825 v3_PR);
1827 circle[0] = v3_PR;
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;
1847 return all_points;
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;
1873 return result;
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);
1939 double z_val = 0;
1940 double z_val_next = 0;
1941 double z_diff = 0;
1942 int c = 0;
1943 double delta = 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++) {
1952 c++;
1953 z_values[c] = z_values[c-1] + delta;
1955 c++;
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;
1964 try {
1965 vs = new VectorString3D(px, py, pz, false);
1966 vs.addDependent(p_width_i);
1967 } catch (Exception e) { IJError.print(e); }
1968 return vs;
1971 public String getInfo() {
1972 if (-1 == n_points) setupForDisplay(); //reload
1973 // measure length
1974 double len = 0;
1975 if (n_points > 1) {
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;
1987 double max_z = 0;
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;
1994 // check the roi
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
2000 a.intersect(area);
2001 Rectangle r = a.getBounds();
2002 if (0 != r.width && 0 != r.height) return true;
2005 return false;
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;
2013 return false;
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;
2020 return false;
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);
2027 if (null == pol) {
2028 if (null == r) return new Rectangle();
2029 r.x = 0;
2030 r.y = 0;
2031 r.width = 0;
2032 r.height = 0;
2033 return r;
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);
2043 return r;
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);
2056 imp.setRoi(sr);
2057 bp.setValue(255);
2058 sr.drawPixels(bp);
2059 imp.show();
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"});
2066 // measure length
2067 double len = 0;
2068 Calibration cal = layer_set.getCalibration();
2069 if (n_points > 1) {
2070 VectorString3D vs = asVectorString3D();
2071 vs.calibrate(cal);
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());
2079 return rt;
2082 @Override
2083 final Class getInternalDataPackageClass() {
2084 return DPPipe.class;
2087 @Override
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) {
2098 super(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);
2108 @Override
2109 final boolean to2(final Displayable d) {
2110 super.to1(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);
2121 return true;