1 package ini
.trakem2
.display
;
3 import ini
.trakem2
.tree
.*;
4 import ini
.trakem2
.utils
.*;
5 import ini
.trakem2
.imaging
.PatchStack
;
6 import ini
.trakem2
.vector
.VectorString3D
;
10 import ij
.process
.ImageProcessor
;
11 import ij
.process
.ByteProcessor
;
12 import ij
.gui
.ShapeRoi
;
13 import ij
.gui
.GenericDialog
;
14 import ij
.io
.DirectoryChooser
;
15 import ij
.measure
.Calibration
;
17 import java
.awt
.Color
;
18 import java
.awt
.Cursor
;
19 import java
.awt
.Rectangle
;
20 import java
.awt
.geom
.Area
;
21 import java
.awt
.MenuBar
;
23 import java
.awt
.MenuItem
;
24 import java
.awt
.CheckboxMenuItem
;
25 import java
.awt
.event
.ActionListener
;
26 import java
.awt
.event
.ItemListener
;
27 import java
.awt
.event
.ActionEvent
;
28 import java
.awt
.event
.ItemEvent
;
31 import java
.awt
.geom
.AffineTransform
;
33 import java
.awt
.event
.WindowAdapter
;
34 import java
.awt
.event
.WindowEvent
;
36 import javax
.vecmath
.Point3f
;
37 import javax
.vecmath
.Color3f
;
38 import javax
.vecmath
.Vector3f
;
39 import javax
.media
.j3d
.View
;
40 import javax
.media
.j3d
.Transform3D
;
42 import ij3d
.ImageWindow3D
;
43 import ij3d
.Image3DUniverse
;
45 import ij3d
.Image3DMenubar
;
47 import java
.lang
.reflect
.Field
;
50 /** One Display3D instance for each LayerSet (maximum). */
51 public final class Display3D
{
53 /** The threading is so poorly done ... it BARELY works, it's fragile. */
55 /** Table of LayerSet and Display3D - since there is a one to one relationship. */
56 static private Hashtable ht_layer_sets
= new Hashtable();
57 /**Control calls to new Display3D. */
58 static private Lock htlock
= new Lock();
60 /** The sky will fall on your head if you modify any of the objects contained in this table -- which is a copy of the original, but the objects are the originals. */
61 static public Hashtable
getMasterTable() {
62 return (Hashtable
)ht_layer_sets
.clone();
65 /** Table of ProjectThing keys versus meshes, the latter represented by List of triangles in the form of thre econsecutive Point3f in the List.*/
66 private Hashtable ht_pt_meshes
= new Hashtable();
68 private Image3DUniverse universe
;
70 private Lock u_lock
= new Lock();
72 private LayerSet layer_set
;
73 private double width
, height
;
74 private int resample
= -1; // unset
75 static private final int DEFAULT_RESAMPLE
= 4;
76 /** If the LayerSet dimensions are too large, then limit to max 2048 for width or height and setup a scale.*/
77 private double scale
= 1.0;
78 static private final int MAX_DIMENSION
= 1024;
80 private String selected
= null;
82 /** Defaults to parallel projection. */
83 private Display3D(final LayerSet ls
) {
85 this.universe
= new Image3DUniverse(512, 512); // size of the initial canvas, not the universe itself
86 this.universe
.getViewer().getView().setProjectionPolicy(View
.PERSPECTIVE_PROJECTION
); // (View.PERSPECTIVE_PROJECTION);
89 this.universe
.getWindow().addWindowListener(new IW3DListener(ls
));
92 Display3D
.ht_layer_sets
.put(ls
, this);
95 public Image3DUniverse
getUniverse() {
99 /* Take a snapshot know-it-all mode. Each Transform3D given as argument gets assigned to the (nearly) homonimous TransformGroup, which have the following relationships:
101 * scaleTG contains rotationsTG
102 * rotationsTG contains translateTG
103 * translateTG contains centerTG
104 * centerTG contains the whole scene, with all meshes, etc.
106 * Any null arguments imply the current transform in the open Display3D.
108 * By default, a newly created Display3D has the scale and center transforms modified to make the scene fit nicely centered (and a bit scaled down) in the given Display3D window. The translate and rotate transforms are set to identity.
110 * The TransformGroup instances may be reached like this:
112 * LayerSet layer_set = Display.getFrontLayer().getParent();
113 * Display3D d3d = Display3D.getDisplay(layer_set);
114 * TransformGroup scaleTG = d3d.getUniverse().getGlobalScale();
115 * TransformGroup rotationsTG = d3d.getUniverse().getGlobalRotate();
116 * TransformGroup translateTG = d3d.getUniverse().getGlobalTranslate();
117 * TransformGroup centerTG = d3d.getUniverse().getCenterTG();
119 * ... and the Transform3D from each may be read out indirectly like this:
121 * Transform3D t_scale = new Transform3D();
122 * scaleTG.getTransform(t_scale);
125 * WARNING: if your java3d setup does not support offscreen rendering, the Display3D window will be brought to the front and a screen snapshot cropped to it to perform the snapshot capture. Don't cover the Display3D window with any other windows (not even an screen saver).
128 /*public ImagePlus makeSnapshot(final Transform3D scale, final Transform3D rotate, final Transform3D translate, final Transform3D center) {
129 return universe.makeSnapshot(scale, rotate, translate, center);
132 /** Uses current scaling, translation and centering transforms! */
133 /*public ImagePlus makeSnapshotXY() { // aka posterior
135 return universe.makeSnapshot(null, new Transform3D(), null, null);
137 /** Uses current scaling, translation and centering transforms! */
138 /*public ImagePlus makeSnapshotXZ() { // aka dorsal
139 Transform3D rot1 = new Transform3D();
140 rot1.rotZ(-Math.PI/2);
141 Transform3D rot2 = new Transform3D();
142 rot2.rotX(Math.PI/2);
144 return universe.makeSnapshot(null, rot1, null, null);
147 /** Uses current scaling, translation and centering transforms! */
149 public ImagePlus makeSnapshotYZ() { // aka lateral
150 Transform3D rot = new Transform3D();
152 return universe.makeSnapshot(null, rot, null, null);
156 public ImagePlus makeSnapshotZX() { // aka frontal
157 Transform3D rot = new Transform3D();
158 rot.rotX(-Math.PI/2);
159 return universe.makeSnapshot(null, rot, null, null);
163 /** Uses current scaling, translation and centering transforms! Opposite side of XZ. */
165 public ImagePlus makeSnapshotXZOpp() {
166 Transform3D rot1 = new Transform3D();
167 rot1.rotX(-Math.PI/2); // 90 degrees clockwise
168 Transform3D rot2 = new Transform3D();
169 rot2.rotY(Math.PI); // 180 degrees around Y, to the other side.
171 return universe.makeSnapshot(null, rot1, null, null);
174 private class IW3DListener
extends WindowAdapter
{
176 IW3DListener(LayerSet ls
) {
179 public void windowClosing(WindowEvent we
) {
180 Utils
.log2("Display3D.windowClosing");
181 /*Object ob =*/ ht_layer_sets
.remove(ls
);
183 Utils.log2("Removed Display3D from table for LayerSet " + ls);
186 public void windowClosed(WindowEvent we
) {
187 Utils
.log2("Display3D.windowClosed");
188 ht_layer_sets
.remove(ls
);
192 /** Reads the #ID in the name, which is immutable. */
193 private ProjectThing
find(String name
) {
194 long id
= Long
.parseLong(name
.substring(name
.lastIndexOf('#')+1));
195 for (Iterator it
= ht_pt_meshes
.keySet().iterator(); it
.hasNext(); ) {
196 ProjectThing pt
= (ProjectThing
)it
.next();
197 Displayable d
= (Displayable
)pt
.getObject();
198 if (d
.getId() == id
) {
205 /** If the layer set is too large in width and height, then set a scale that makes it maximum MAX_DIMENSION in any of the two dimensions. */
206 private void computeScale(LayerSet ls
) {
207 this.width
= ls
.getLayerWidth();
208 this.height
= ls
.getLayerHeight();
209 if (width
> MAX_DIMENSION
) {
210 scale
= MAX_DIMENSION
/ width
;
212 width
= MAX_DIMENSION
;
214 if (height
> MAX_DIMENSION
) {
215 scale
= MAX_DIMENSION
/ height
;
217 height
= MAX_DIMENSION
;
219 //Utils.log2("scale, width, height: " + scale + ", " + width + ", " + height);
222 static private boolean check_j3d
= true;
223 static private boolean has_j3d_3dviewer
= false;
225 static private boolean hasLibs() {
229 Class p3f
= Class
.forName("javax.vecmath.Point3f");
230 has_j3d_3dviewer
= true;
231 } catch (ClassNotFoundException cnfe
) {
232 Utils
.log("Java 3D not installed.");
233 has_j3d_3dviewer
= false;
237 Class ij3d
= Class
.forName("ij3d.ImageWindow3D");
238 has_j3d_3dviewer
= true;
239 } catch (ClassNotFoundException cnfe
) {
240 Utils
.log("3D Viewer not installed.");
241 has_j3d_3dviewer
= false;
245 return has_j3d_3dviewer
;
248 /** Get an existing Display3D for the given LayerSet, or create a new one for it (and cache it). */
249 static private Display3D
get(final LayerSet ls
) {
250 synchronized (htlock
) {
254 if (!hasLibs()) return null;
256 Object ob
= ht_layer_sets
.get(ls
);
258 final boolean[] done
= new boolean[]{false};
259 javax
.swing
.SwingUtilities
.invokeAndWait(new Runnable() { public void run() {
260 Display3D ob
= new Display3D(ls
);
261 ht_layer_sets
.put(ls
, ob
);
264 // wait to avoid crashes in amd64
265 // try { Thread.sleep(500); } catch (Exception e) {}
267 try { Thread
.sleep(50); } catch (Exception e
) {}
269 ob
= ht_layer_sets
.get(ls
);
271 return (Display3D
)ob
;
272 } catch (Exception e
) {
275 // executed even when returning from within the try-catch block
282 /** Get the Display3D instance that exists for the given LayerSet, if any. */
283 static public Display3D
getDisplay(final LayerSet ls
) {
284 return (Display3D
)ht_layer_sets
.get(ls
);
287 static public void setWaitingCursor() {
288 for (Iterator it
= ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
289 ((Display3D
)it
.next()).universe
.getWindow().setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
293 static public void doneWaiting() {
294 for (Iterator it
= ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
295 ((Display3D
)it
.next()).universe
.getWindow().setCursor(Cursor
.getDefaultCursor());
299 static public void show(ProjectThing pt
) {
303 /** Scan the ProjectThing children and assign the renderable ones to an existing Display3D for their LayerSet, or open a new one. If true == wait && -1 != resample, then the method returns only when the mesh/es have been added. */
304 static public void show(ProjectThing pt
, boolean wait
, int resample
) {
305 if (null == pt
) return;
307 // scan the given ProjectThing for 3D-viewable items not present in the ht_meshes
308 // So: find arealist, pipe, ball, and profile_list types
309 HashSet hs
= pt
.findBasicTypeChildren();
310 if (null == hs
|| 0 == hs
.size()) {
311 Utils
.log("Node " + pt
+ " contains no 3D-displayable children");
315 for (Iterator it
= hs
.iterator(); it
.hasNext(); ) {
316 // obtain the Displayable object under the node
317 ProjectThing child
= (ProjectThing
)it
.next();
318 Object obc
= child
.getObject();
319 Displayable displ
= obc
.getClass().equals(String
.class) ?
null : (Displayable
)obc
;
321 if (displ
.getClass().equals(Profile
.class)) {
322 //Utils.log("Display3D can't handle Bezier profiles at the moment.");
323 // handled by profile_list Thing
326 if (!displ
.isVisible()) {
327 Utils
.log("Skipping non-visible node " + displ
);
331 //StopWatch sw = new StopWatch();
332 // obtain the containing LayerSet
333 Display3D d3d
= null;
334 if (null != displ
) d3d
= Display3D
.get(displ
.getLayerSet());
335 else if (child
.getType().equals("profile_list")) {
336 ArrayList al_children
= child
.getChildren();
337 if (null == al_children
|| 0 == al_children
.size()) continue;
338 // else, get the first Profile and get its LayerSet
339 d3d
= Display3D
.get(((Displayable
)((ProjectThing
)al_children
.get(0)).getObject()).getLayerSet());
341 Utils
.log("Don't know what to do with node " + child
);
344 Utils
.log("Could not get a proper 3D display for node " + displ
);
345 return; // java3D not installed most likely
347 if (d3d
.ht_pt_meshes
.contains(child
)) {
348 Utils
.log2("Already here: " + child
);
349 continue; // already here
351 setWaitingCursor(); // the above may be creating a display
352 //sw.elapsed("after creating and/or retrieving Display3D");
353 Thread t
= d3d
.addMesh(child
, displ
, resample
);
354 if (wait
&& -1 != d3d
.resample
) {
355 Utils
.log("joining...");
356 try { t
.join(); } catch (Exception e
) { e
.printStackTrace(); }
358 //sw.elapsed("after creating mesh");
360 } catch (Exception e
) {
367 static public void showOrthoslices(Patch p
) {
368 Display3D d3d
= get(p
.getLayerSet());
369 d3d
.adjustResampling();
370 //d3d.universe.resetView();
371 String title
= makeTitle(p
) + " orthoslices";
373 d3d
.universe
.removeContent(title
);
374 PatchStack ps
= p
.makePatchStack();
375 ImagePlus imp
= get8BitStack(ps
);
376 d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
377 Content ct
= d3d
.universe
.getContent(title
);
378 setTransform(ct
, ps
.getPatch(0));
379 ct
.toggleLock(); // locks the added content
382 static public void showVolume(Patch p
) {
383 Display3D d3d
= get(p
.getLayerSet());
384 d3d
.adjustResampling();
385 //d3d.universe.resetView();
386 String title
= makeTitle(p
) + " volume";
388 d3d
.universe
.removeContent(title
);
389 PatchStack ps
= p
.makePatchStack();
390 ImagePlus imp
= get8BitStack(ps
);
391 d3d
.universe
.addVoltex(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
392 Content ct
= d3d
.universe
.getContent(title
);
393 setTransform(ct
, ps
.getPatch(0));
394 ct
.toggleLock(); // locks the added content
397 static private void setTransform(Content ct
, Patch p
) {
398 final double[] a
= new double[6];
399 p
.getAffineTransform().getMatrix(a
);
400 Calibration cal
= p
.getLayerSet().getCalibration();
401 // a is: m00 m10 m01 m11 m02 m12
402 // d expects: m01 m02 m03 m04, m11 m12 ...
403 ct
.applyTransform(new Transform3D(new double[]{a
[0], a
[2], 0, a
[4] * cal
.pixelWidth
,
404 a
[1], a
[3], 0, a
[5] * cal
.pixelWidth
,
405 0, 0, 1, p
.getLayer().getZ() * cal
.pixelWidth
,
409 /** Returns a stack suitable for the ImageJ 3D Viewer, either 8-bit gray or 8-bit color.
410 * If the PatchStach is already of the right type, it is returned,
411 * otherwise a copy is made in the proper type.
413 static private ImagePlus
get8BitStack(final PatchStack ps
) {
414 switch (ps
.getType()) {
415 case ImagePlus
.COLOR_RGB
:
416 // convert stack to 8-bit color
417 return ps
.createColor256Copy();
418 case ImagePlus
.GRAY16
:
419 case ImagePlus
.GRAY32
:
420 // convert stack to 8-bit
421 return ps
.createGray8Copy();
427 /** A Material, but avoiding name colisions. */
428 static private int mat_index
= 1;
429 static private class Mtl
{
435 Mtl(float alpha
, float R
, float G
, float B
) {
440 name
= "mat_" + mat_index
;
443 public boolean equals(Object ob
) {
444 if (ob
instanceof Display3D
.Mtl
) {
446 if (mat
.alpha
== alpha
455 void fill(StringBuffer sb
) {
456 sb
.append("\nnewmtl ").append(name
).append('\n')
457 .append("Ns 96.078431\n")
458 .append("Ka 0.0 0.0 0.0\n")
459 .append("Kd ").append(R
).append(' ').append(G
).append(' ').append(B
).append('\n') // this is INCORRECT but I'll figure out the conversion later
460 .append("Ks 0.5 0.5 0.5\n")
462 .append("d ").append(alpha
).append('\n')
463 .append("illum 2\n\n");
466 return (int)((R
+ G
+ B
) / 3 * 255); // something silly
470 /** Generates DXF file from a table of ProjectThing and their associated triangles. */
471 private String
createDXF(Hashtable ht_content
) {
472 StringBuffer sb_data
= new StringBuffer("0\nSECTION\n2\nENTITIES\n"); //header of file
473 for (Iterator it
= ht_content
.entrySet().iterator(); it
.hasNext(); ) {
474 Map
.Entry entry
= (Map
.Entry
)it
.next();
475 ProjectThing pt
= (ProjectThing
)entry
.getKey();
476 Displayable displ
= (Displayable
)pt
.getObject();
477 List triangles
= (List
)entry
.getValue();
478 float[] color
= displ
.getColor().getColorComponents(null);
479 Mtl mtl
= new Mtl(displ
.getAlpha(), color
[0], color
[1], color
[2]);
480 writeTrianglesDXF(sb_data
, triangles
, mtl
.name
, Integer
.toString(mtl
.getAsSingle()));
482 sb_data
.append("0\nENDSEC\n0\nEOF\n"); //TRAILER of the file
483 return sb_data
.toString();
486 /** @param format works as extension as well. */
487 private void export(final ProjectThing pt
, final String format
) {
488 if (0 == ht_pt_meshes
.size()) return;
490 File file
= Utils
.chooseFile("untitled", format
);
491 if (null == file
) return;
492 final String name
= file
.getName();
494 if (!name2
.endsWith("." + format
)) {
495 name2
+= "." + format
;
497 File f2
= new File(file
.getParent() + "/" + name2
);
499 while (f2
.exists()) {
500 name2
= name
+ "_" + i
+ "." + format
;
501 f2
= new File(name2
);
503 Hashtable ht_content
= ht_pt_meshes
;
505 ht_content
= new Hashtable();
506 ht_content
.put(pt
, ht_pt_meshes
.get(pt
));
508 if (format
.equals("obj")) {
509 String
[] data
= createObjAndMtl(name2
, ht_content
);
510 Utils
.saveToFile(f2
, data
[0]);
511 Utils
.saveToFile(new File(f2
.getParent() + "/" + name2
+ ".mtl"), data
[1]);
512 } else if (format
.equals("dxf")) {
513 Utils
.saveToFile(f2
, createDXF(ht_content
));
517 /** Wavefront format. Returns the contents of two files: one for materials, another for meshes*/
518 private String
[] createObjAndMtl(final String file_name
, final Hashtable ht_content
) {
519 StringBuffer sb_obj
= new StringBuffer("# TrakEM2 OBJ File\n");
520 sb_obj
.append("mtllib ").append(file_name
).append(".mtl").append('\n');
522 Hashtable ht_mat
= new Hashtable();
524 int j
= 1; // Vert indices in .obj files are global, not reset for every object.
525 // starting at '1' because vert indices start at one.
527 for (Iterator it
= ht_content
.entrySet().iterator(); it
.hasNext(); ) {
528 Map
.Entry entry
= (Map
.Entry
)it
.next(); // I hate java's gratuituous verbosity
529 ProjectThing pt
= (ProjectThing
)entry
.getKey();
530 Displayable displ
= (Displayable
)pt
.getObject();
531 List triangles
= (List
)entry
.getValue();
532 // make material, and see whether it exists already
533 float[] color
= displ
.getColor().getColorComponents(null);
534 Mtl mat
= new Mtl(displ
.getAlpha(), color
[0], color
[1], color
[2]);
535 Object mat2
= ht_mat
.get(mat
);
536 if (null != mat2
) mat
= (Mtl
)mat2
; // recycling
537 else ht_mat
.put(mat
, mat
); // !@#$% Can't get the object in a HashSet easily
538 // make list of vertices
539 String title
= displ
.getProject().getMeaningfulTitle(displ
).replaceAll(" ", "_").replaceAll("#", "--");
540 Hashtable ht_points
= new Hashtable(); // because we like inefficiency
541 sb_obj
.append("o ").append(title
).append('\n');
542 final int len
= triangles
.size();
543 int[] index
= new int[len
];
544 int k
= 0; // iterate over index array, to make faces later
545 // j is tag for each new vert, which start at 1 (for some ridiculous reason)
546 for (Iterator tt
= triangles
.iterator(); tt
.hasNext(); ) {
547 Point3f p
= (Point3f
)tt
.next();
548 //no need if coords are not displaced//p = (Point3f)p.clone();
549 // check if point already exists
550 Object ob
= ht_points
.get(p
);
552 index
[k
] = ((Integer
)ob
).intValue();
557 ht_points
.put(p
, new Integer(j
));
559 sb_obj
.append('v').append(' ').append(p
.x
)
560 .append(' ').append(p
.y
)
561 .append(' ').append(p
.z
).append('\n');
566 sb_obj
.append("usemtl ").append(mat
.name
).append('\n');
567 sb_obj
.append("s 1\n");
568 if (0 != len
% 3) Utils
.log2("WARNING: list of triangles not multiple of 3");
570 int len_p
= ht_points
.size();
571 for (int i
=0; i
<len
; i
+=3) {
572 sb_obj
.append('f').append(' ').append(index
[i
])
573 .append(' ').append(index
[i
+1])
574 .append(' ').append(index
[i
+2]).append('\n');
575 //if (index[i] > len_p) Utils.log2("WARNING: face vert index beyond range"); // range is from 1 to len_p inclusive
576 //if (index[i+1] > len_p) Utils.log2("WARNING: face vert index beyond range");
577 //if (index[i+2] > len_p) Utils.log2("WARNING: face vert index beyond range");
578 //Utils.log2("j: " + index[i]);
584 StringBuffer sb_mtl
= new StringBuffer("# TrakEM2 MTL File\n");
585 for (Iterator it
= ht_mat
.keySet().iterator(); it
.hasNext(); ) {
586 Mtl mat
= (Mtl
)it
.next();
590 return new String
[]{sb_obj
.toString(), sb_mtl
.toString()};
593 /** Considers there is only one Display3D for each LayerSet. */
594 static public void remove(ProjectThing pt
) {
595 if (null == pt
) return;
596 if (null == pt
.getObject()) return;
597 Object ob
= pt
.getObject();
598 if (!(ob
instanceof Displayable
)) return;
599 Displayable displ
= (Displayable
)ob
;
600 Object d3ob
= ht_layer_sets
.get(displ
.getLayerSet()); // TODO profile_list is going to fail here
602 // there is no Display3D showing the pt to remove
603 Utils
.log2("No Display3D contains ProjectThing: " + pt
);
606 Display3D d3d
= (Display3D
)d3ob
;
607 Object ob_mesh
= d3d
.ht_pt_meshes
.remove(pt
);
608 if (null == ob_mesh
) {
609 Utils
.log2("No mesh contained within " + d3d
+ " for ProjectThing " + pt
);
610 return; // not contained here
612 String title
= makeTitle(displ
);
613 //Utils.log(d3d.universe.contains(title) + ": Universe contains " + displ);
614 d3d
.universe
.removeContent(title
); // WARNING if the title changes, problems: will need a table of pt vs title as it was when added to the universe. At the moment titles are not editable for basic types, but this may change in the future. TODO the future is here: titles are editable for basic types.
617 static private void writeTrianglesDXF(final StringBuffer sb
, final List triangles
, final String the_group
, final String the_color
) {
620 final String s10
= "10\n"; final String s11
= "11\n"; final String s12
= "12\n"; final String s13
= "13\n";
621 final String s20
= "20\n"; final String s21
= "21\n"; final String s22
= "22\n"; final String s23
= "23\n";
622 final String s30
= "30\n"; final String s31
= "31\n"; final String s32
= "32\n"; final String s33
= "33\n";
623 final String triangle_header
= "0\n3DFACE\n8\n" + the_group
+ "\n6\nCONTINUOUS\n62\n" + the_color
+ L
;
625 final int len
= triangles
.size();
626 final Point3f
[] vert
= new Point3f
[len
];
627 triangles
.toArray(vert
);
628 for (int i
=0; i
<len
; i
+=3) {
630 sb
.append(triangle_header
)
632 .append(s10
).append(vert
[i
].x
).append(L
)
633 .append(s20
).append(vert
[i
].y
).append(L
)
634 .append(s30
).append(vert
[i
].z
).append(L
)
636 .append(s11
).append(vert
[i
+1].x
).append(L
)
637 .append(s21
).append(vert
[i
+1].y
).append(L
)
638 .append(s31
).append(vert
[i
+1].z
).append(L
)
640 .append(s12
).append(vert
[i
+2].x
).append(L
)
641 .append(s22
).append(vert
[i
+2].y
).append(L
)
642 .append(s32
).append(vert
[i
+2].z
).append(L
)
644 .append(s13
).append(vert
[i
+2].x
).append(L
) // repeated point
645 .append(s23
).append(vert
[i
+2].y
).append(L
)
646 .append(s33
).append(vert
[i
+2].z
).append(L
);
650 static private final int MAX_THREADS
= Runtime
.getRuntime().availableProcessors();
651 static private final Vector v_threads
= new Vector(MAX_THREADS
); // synchronized
653 /** Creates a mesh for the given Displayable in a separate Thread. */
654 private Thread
addMesh(final ProjectThing pt
, final Displayable displ
, final int resample
) {
655 final double scale
= this.scale
;
656 Thread thread
= new Thread() {
658 setPriority(Thread
.NORM_PRIORITY
);
659 while (v_threads
.size() >= MAX_THREADS
) { // this is crude. Could do much better now ... much better! Properly queued tasks.
660 try { Thread
.sleep(400); } catch (InterruptedException ie
) {}
665 // the list 'triangles' is really a list of Point3f, which define a triangle every 3 consecutive points. (TODO most likely Bene Schmid got it wrong: I don't think there's any need to have the points duplicated if they overlap in space but belong to separate triangles.)
666 List triangles
= null;
667 if (displ
instanceof AreaList
) {
669 if (-1 == resample
) rs
= Display3D
.this.resample
= adjustResampling(); // will adjust this.resample, and return it (even if it's a default value)
670 else rs
= Display3D
.this.resample
;
671 triangles
= ((AreaList
)displ
).generateTriangles(scale
, rs
);
672 //triangles = removeNonManifold(triangles);
673 } else if (displ
instanceof Ball
) {
674 double[][][] globe
= Ball
.generateGlobe(12, 12);
675 triangles
= ((Ball
)displ
).generateTriangles(scale
, globe
);
676 } else if (displ
instanceof Line3D
) {
678 // adjustResampling(); // fails horribly, needs first to correct mesh-generation code
679 triangles
= ((Line3D
)displ
).generateTriangles(scale
, 12, 1 /*Display3D.this.resample*/);
680 } else if (null == displ
&& pt
.getType().equals("profile_list")) {
681 triangles
= Profile
.generateTriangles(pt
, scale
);
684 if (null == triangles
) {
685 Utils
.log("Some error ocurred: can't create triangles for " + displ
);
688 if (0 == triangles
.size()) {
689 Utils
.log2("Skipping empty mesh for " + displ
.getTitle());
692 if (0 != triangles
.size() % 3) {
693 Utils
.log2("Skipping non-multiple-of-3 vertices list generated for " + displ
.getTitle());
699 color
= displ
.getColor();
700 alpha
= displ
.getAlpha();
702 // for profile_list: get from the first (what a kludge)
703 Object obp
= ((ProjectThing
)pt
.getChildren().get(0)).getObject();
704 if (null == obp
) return;
705 Displayable di
= (Displayable
)obp
;
706 color
= di
.getColor();
707 alpha
= di
.getAlpha();
709 // add to 3D view (synchronized)
710 synchronized (u_lock
) {
713 // craft a unique title (id is always unique)
714 String title
= null == displ ? pt
.toString() + " #" + pt
.getId() : makeTitle(displ
);
715 if (ht_pt_meshes
.contains(pt
)) {
716 // remove content from universe
717 universe
.removeContent(title
);
718 // no need to remove entry from table, it's overwritten below
721 ht_pt_meshes
.put(pt
, triangles
);
722 // ensure proper default transform
723 //universe.resetView();
725 //universe.ensureScale((float)(width*scale));
726 universe
.addMesh(triangles
, new Color3f(color
), title
, 1); // had a (float)(width*scale) param in there before the 1
727 Content ct
= universe
.getContent(title
);
728 ct
.setTransparency(1f
- alpha
);
730 } catch (Exception e
) {
736 Utils
.log2(pt
.toString() + " n points: " + triangles
.size());
738 } catch (Exception e
) {
741 v_threads
.remove(this);
750 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
751 static public Thread
addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
) {
752 return addMesh(ref_ls
, vs
, title
, color
, null, 1.0f
);
755 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
756 static public Thread
addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
, final double[] widths
, final float alpha
) {
757 Thread thread
= new Thread() {
759 setPriority(Thread
.NORM_PRIORITY
);
760 while (v_threads
.size() >= MAX_THREADS
) { // this is crude. Could do much better now ... much better! Properly queued tasks.
761 try { Thread
.sleep(50); } catch (InterruptedException ie
) {}
766 Display3D d3d
= null;
767 d3d
= Display3D
.get(ref_ls
);
768 final double scale
= d3d
.scale
;
769 final double width
= d3d
.width
;
770 float transp
= 1 - alpha
;
771 if (transp
< 0) transp
= 0;
772 if (transp
> 1) transp
= 1;
774 Utils
.log("WARNING: adding a 3D object fully transparent.");
777 double[] wi
= widths
;
778 if (null == widths
) {
779 wi
= new double[vs
.getPoints(0).length
];
780 //Utils.log2("len: " + wi.length + vs.getPoints(0).length + vs.getPoints(1).length);
781 Arrays
.fill(wi
, 2.0);
782 } else if (widths
.length
!= vs
.length()) {
783 Utils
.log("ERROR: widths.length != VectorString3D.length()");
787 List triangles
= Pipe
.generateTriangles(Pipe
.makeTube(vs
.getPoints(0), vs
.getPoints(1), vs
.getPoints(2), wi
, 1, 12, null), scale
);
789 // add to 3D view (synchronized)
790 synchronized (d3d
.u_lock
) {
793 // ensure proper default transform
794 //d3d.universe.resetView();
796 //Utils.log2(title + " : vertex count % 3 = " + triangles.size() % 3 + " for " + triangles.size() + " vertices");
797 //d3d.universe.ensureScale((float)(width*scale));
798 d3d
.universe
.addMesh(triangles
, new Color3f(color
), title
, /*(float)(width*scale),*/ 1);
799 Content ct
= d3d
.universe
.getContent(title
);
800 ct
.setTransparency(transp
);
802 } catch (Exception e
) {
809 } catch (Exception e
) {
812 v_threads
.remove(this);
821 // This method has the exclusivity in adjusting the resampling value.
822 synchronized private final int adjustResampling() {
823 if (resample
> 0) return resample
;
824 final GenericDialog gd
= new GenericDialog("Resample");
825 gd
.addSlider("Resample: ", 1, 20, -1 != resample ? resample
: DEFAULT_RESAMPLE
);
827 if (gd
.wasCanceled()) {
828 resample
= -1 != resample ? resample
: DEFAULT_RESAMPLE
; // current or default value
831 resample
= ((java
.awt
.Scrollbar
)gd
.getSliders().get(0)).getValue();
835 /** Checks if there is any Display3D instance currently showing the given Displayable. */
836 static public boolean isDisplayed(final Displayable d
) {
837 if (null == d
) return false;
838 final String title
= makeTitle(d
);
839 for (Iterator it
= Display3D
.ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
840 Display3D d3d
= (Display3D
)it
.next();
841 if (null != d3d
.universe
.getContent(title
)) return true;
846 static public void setColor(final Displayable d
, final Color color
) {
847 if (!isDisplayed(d
)) return;
848 Display3D d3d
= get(d
.getLayer().getParent());
849 if (null == d3d
) return; // no 3D displays open
850 Content content
= d3d
.universe
.getContent(makeTitle(d
));
851 //Utils.log2("content: " + content);
852 if (null != content
) content
.setColor(new Color3f(color
));
855 static public void setTransparency(final Displayable d
, final float alpha
) {
856 if (null == d
) return;
857 Layer layer
= d
.getLayer();
858 if (null == layer
) return; // some objects have no layer, such as the parent LayerSet.
859 Object ob
= ht_layer_sets
.get(layer
.getParent());
860 if (null == ob
) return;
861 Display3D d3d
= (Display3D
)ob
;
862 String title
= makeTitle(d
);
863 Content content
= d3d
.universe
.getContent(title
);
864 if (null != content
) content
.setTransparency(1 - alpha
);
865 else if (null == content
&& d
.getClass().equals(Patch
.class)) {
868 title
= pa
.getProject().getLoader().getFileName(pa
);
869 for (Iterator it
= Display3D
.ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
870 d3d
= (Display3D
)it
.next();
871 for (Iterator cit
= d3d
.universe
.getContents().iterator(); cit
.hasNext(); ) {
872 Content c
= (Content
)cit
.next();
873 if (c
.getName().startsWith(title
)) {
874 c
.setTransparency(1 - alpha
);
875 // no break, since there could be a volume and an orthoslice
883 static public String
makeTitle(final Displayable d
) {
884 return d
.getProject().getMeaningfulTitle(d
) + " #" + d
.getId();
886 static public String
makeTitle(final Patch p
) {
887 return new File(p
.getProject().getLoader().getAbsolutePath(p
)).getName()
888 + " #" + p
.getProject().getLoader().getNextId();
891 /** Remake the mesh for the Displayable in a separate Thread, if it's included in a Display3D
892 * (otherwise returns null). */
893 static public Thread
update(final Displayable d
) {
894 Layer layer
= d
.getLayer();
895 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
896 Object ob
= ht_layer_sets
.get(layer
.getParent());
897 if (null == ob
) return null;
898 Display3D d3d
= (Display3D
)ob
;
899 return d3d
.addMesh(d
.getProject().findProjectThing(d
), d
, d3d
.resample
);
903 static public final double computeTriangleArea() {
904 return 0.5 * Math.sqrt(Math.pow(xA*yB + xB*yC + xC*yA, 2) +
905 Math.pow(yA*zB + yB*zC + yC*zA, 2) +
906 Math.pow(zA*xB + zB*xC + zC*xA, 2));
910 static public final boolean contains(final LayerSet ls
, final String title
) {
911 final Display3D d3d
= getDisplay(ls
);
912 if (null == d3d
) return false;
913 return null != d3d
.universe
.getContent(title
);