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
.Component
;
26 import java
.awt
.event
.ActionListener
;
27 import java
.awt
.event
.ItemListener
;
28 import java
.awt
.event
.ActionEvent
;
29 import java
.awt
.event
.ItemEvent
;
32 import java
.awt
.geom
.AffineTransform
;
34 import java
.awt
.event
.WindowAdapter
;
35 import java
.awt
.event
.WindowEvent
;
36 import java
.awt
.event
.KeyEvent
;
37 import java
.awt
.event
.KeyAdapter
;
38 import java
.awt
.event
.KeyListener
;
40 import javax
.vecmath
.Point3f
;
41 import javax
.vecmath
.Color3f
;
42 import javax
.vecmath
.Vector3f
;
43 import javax
.media
.j3d
.View
;
44 import javax
.media
.j3d
.Transform3D
;
45 import javax
.media
.j3d
.PolygonAttributes
;
47 import ij3d
.ImageWindow3D
;
48 import ij3d
.Image3DUniverse
;
50 import ij3d
.Image3DMenubar
;
51 import customnode
.CustomMeshNode
;
52 import customnode
.CustomMesh
;
53 import customnode
.CustomTriangleMesh
;
55 import java
.lang
.reflect
.Field
;
56 import java
.util
.concurrent
.ExecutorService
;
57 import java
.util
.concurrent
.Executors
;
58 import java
.util
.concurrent
.Future
;
59 import java
.util
.concurrent
.TimeUnit
;
60 import java
.util
.concurrent
.FutureTask
;
61 import java
.util
.concurrent
.Callable
;
64 /** One Display3D instance for each LayerSet (maximum). */
65 public final class Display3D
{
67 /** Table of LayerSet and Display3D - since there is a one to one relationship. */
68 static private Hashtable ht_layer_sets
= new Hashtable();
69 /**Control calls to new Display3D. */
70 static private Lock htlock
= new Lock();
72 /** 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. */
73 static public Hashtable
getMasterTable() {
74 return (Hashtable
)ht_layer_sets
.clone();
77 /** Table of ProjectThing keys versus meshes, the latter represented by List of triangles in the form of thre econsecutive Point3f in the List.*/
78 private Hashtable ht_pt_meshes
= new Hashtable();
80 private Image3DUniverse universe
;
82 private Lock u_lock
= new Lock();
84 private LayerSet layer_set
;
85 private double width
, height
;
86 private int resample
= -1; // unset
87 static private final int DEFAULT_RESAMPLE
= 4;
88 /** If the LayerSet dimensions are too large, then limit to max 2048 for width or height and setup a scale.*/
89 private double scale
= 1.0;
90 static private final int MAX_DIMENSION
= 1024;
92 private String selected
= null;
94 // To fork away from the EventDispatchThread
95 static private ExecutorService launchers
= Executors
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
98 private ExecutorService executors
= Executors
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
101 static private KeyAdapter ka = new KeyAdapter() {
102 public void keyPressed(KeyEvent ke) {
103 // F1 .. F12 keys to set tools
104 ProjectToolbar.keyPressed(ke);
109 /** Defaults to parallel projection. */
110 private Display3D(final LayerSet ls
) {
112 this.universe
= new Image3DUniverse(512, 512); // size of the initial canvas, not the universe itself
113 this.universe
.getViewer().getView().setProjectionPolicy(View
.PERSPECTIVE_PROJECTION
); // (View.PERSPECTIVE_PROJECTION);
115 this.universe
.show();
116 this.universe
.getWindow().addWindowListener(new IW3DListener(this, ls
));
117 // it ignores the listeners:
118 //preaddKeyListener(this.universe.getWindow(), ka);
119 //preaddKeyListener(this.universe.getWindow().getCanvas(), ka);
122 Display3D
.ht_layer_sets
.put(ls
, this);
126 private void preaddKeyListener(Component c, KeyListener kl) {
127 KeyListener[] all = c.getKeyListeners();
129 for (KeyListener k : all) c.removeKeyListener(k);
131 c.addKeyListener(kl);
133 for (KeyListener k : all) c.addKeyListener(k);
138 public Image3DUniverse
getUniverse() {
142 /* Take a snapshot know-it-all mode. Each Transform3D given as argument gets assigned to the (nearly) homonimous TransformGroup, which have the following relationships:
144 * scaleTG contains rotationsTG
145 * rotationsTG contains translateTG
146 * translateTG contains centerTG
147 * centerTG contains the whole scene, with all meshes, etc.
149 * Any null arguments imply the current transform in the open Display3D.
151 * 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.
153 * The TransformGroup instances may be reached like this:
155 * LayerSet layer_set = Display.getFrontLayer().getParent();
156 * Display3D d3d = Display3D.getDisplay(layer_set);
157 * TransformGroup scaleTG = d3d.getUniverse().getGlobalScale();
158 * TransformGroup rotationsTG = d3d.getUniverse().getGlobalRotate();
159 * TransformGroup translateTG = d3d.getUniverse().getGlobalTranslate();
160 * TransformGroup centerTG = d3d.getUniverse().getCenterTG();
162 * ... and the Transform3D from each may be read out indirectly like this:
164 * Transform3D t_scale = new Transform3D();
165 * scaleTG.getTransform(t_scale);
168 * 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).
171 /*public ImagePlus makeSnapshot(final Transform3D scale, final Transform3D rotate, final Transform3D translate, final Transform3D center) {
172 return universe.makeSnapshot(scale, rotate, translate, center);
175 /** Uses current scaling, translation and centering transforms! */
176 /*public ImagePlus makeSnapshotXY() { // aka posterior
178 return universe.makeSnapshot(null, new Transform3D(), null, null);
180 /** Uses current scaling, translation and centering transforms! */
181 /*public ImagePlus makeSnapshotXZ() { // aka dorsal
182 Transform3D rot1 = new Transform3D();
183 rot1.rotZ(-Math.PI/2);
184 Transform3D rot2 = new Transform3D();
185 rot2.rotX(Math.PI/2);
187 return universe.makeSnapshot(null, rot1, null, null);
190 /** Uses current scaling, translation and centering transforms! */
192 public ImagePlus makeSnapshotYZ() { // aka lateral
193 Transform3D rot = new Transform3D();
195 return universe.makeSnapshot(null, rot, null, null);
199 public ImagePlus makeSnapshotZX() { // aka frontal
200 Transform3D rot = new Transform3D();
201 rot.rotX(-Math.PI/2);
202 return universe.makeSnapshot(null, rot, null, null);
206 /** Uses current scaling, translation and centering transforms! Opposite side of XZ. */
208 public ImagePlus makeSnapshotXZOpp() {
209 Transform3D rot1 = new Transform3D();
210 rot1.rotX(-Math.PI/2); // 90 degrees clockwise
211 Transform3D rot2 = new Transform3D();
212 rot2.rotY(Math.PI); // 180 degrees around Y, to the other side.
214 return universe.makeSnapshot(null, rot1, null, null);
217 private class IW3DListener
extends WindowAdapter
{
218 private Display3D d3d
;
220 IW3DListener(Display3D d3d
, LayerSet ls
) {
224 public void windowClosing(WindowEvent we
) {
225 //Utils.log2("Display3D.windowClosing");
226 d3d
.executors
.shutdownNow();
227 /*Object ob =*/ ht_layer_sets
.remove(ls
);
229 Utils.log2("Removed Display3D from table for LayerSet " + ls);
232 public void windowClosed(WindowEvent we
) {
233 //Utils.log2("Display3D.windowClosed");
234 ht_layer_sets
.remove(ls
);
238 /** Reads the #ID in the name, which is immutable. */
239 private ProjectThing
find(String name
) {
240 long id
= Long
.parseLong(name
.substring(name
.lastIndexOf('#')+1));
241 for (Iterator it
= ht_pt_meshes
.keySet().iterator(); it
.hasNext(); ) {
242 ProjectThing pt
= (ProjectThing
)it
.next();
243 Displayable d
= (Displayable
)pt
.getObject();
244 if (d
.getId() == id
) {
251 /** 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. */
252 private void computeScale(LayerSet ls
) {
253 this.width
= ls
.getLayerWidth();
254 this.height
= ls
.getLayerHeight();
255 if (width
> MAX_DIMENSION
) {
256 scale
= MAX_DIMENSION
/ width
;
258 width
= MAX_DIMENSION
;
260 if (height
> MAX_DIMENSION
) {
261 scale
= MAX_DIMENSION
/ height
;
263 height
= MAX_DIMENSION
;
265 //Utils.log2("scale, width, height: " + scale + ", " + width + ", " + height);
268 static private boolean check_j3d
= true;
269 static private boolean has_j3d_3dviewer
= false;
271 static private boolean hasLibs() {
275 Class p3f
= Class
.forName("javax.vecmath.Point3f");
276 has_j3d_3dviewer
= true;
277 } catch (ClassNotFoundException cnfe
) {
278 Utils
.log("Java 3D not installed.");
279 has_j3d_3dviewer
= false;
283 Class ij3d
= Class
.forName("ij3d.ImageWindow3D");
284 has_j3d_3dviewer
= true;
285 } catch (ClassNotFoundException cnfe
) {
286 Utils
.log("3D Viewer not installed.");
287 has_j3d_3dviewer
= false;
291 return has_j3d_3dviewer
;
294 /** Get an existing Display3D for the given LayerSet, or create a new one for it (and cache it). */
295 static private Display3D
get(final LayerSet ls
) {
296 synchronized (htlock
) {
300 if (!hasLibs()) return null;
302 Object ob
= ht_layer_sets
.get(ls
);
304 final boolean[] done
= new boolean[]{false};
305 javax
.swing
.SwingUtilities
.invokeAndWait(new Runnable() { public void run() {
306 Display3D ob
= new Display3D(ls
);
307 ht_layer_sets
.put(ls
, ob
);
310 // wait to avoid crashes in amd64
311 // try { Thread.sleep(500); } catch (Exception e) {}
313 try { Thread
.sleep(50); } catch (Exception e
) {}
315 ob
= ht_layer_sets
.get(ls
);
317 return (Display3D
)ob
;
318 } catch (Exception e
) {
321 // executed even when returning from within the try-catch block
328 /** Get the Display3D instance that exists for the given LayerSet, if any. */
329 static public Display3D
getDisplay(final LayerSet ls
) {
330 return (Display3D
)ht_layer_sets
.get(ls
);
333 static public void setWaitingCursor() {
334 for (Iterator it
= ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
335 ((Display3D
)it
.next()).universe
.getWindow().setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
339 static public void doneWaiting() {
340 for (Iterator it
= ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
341 ((Display3D
)it
.next()).universe
.getWindow().setCursor(Cursor
.getDefaultCursor());
345 static public Future
<List
<Content
>> show(ProjectThing pt
) {
346 return show(pt
, false, -1);
349 static public void showAndResetView(final ProjectThing pt
) {
350 new Thread() { public void run() {
351 setPriority(Thread
.NORM_PRIORITY
);
354 Display3D
.resetView(pt
.getProject().getRootLayerSet());
358 /** 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. */
359 static public Future
<List
<Content
>> show(final ProjectThing pt
, final boolean wait
, final int resample
) {
360 if (null == pt
) return null;
361 final Callable
<List
<Content
>> c
= new Callable
<List
<Content
>>() {
362 public List
<Content
> call() {
364 // scan the given ProjectThing for 3D-viewable items not present in the ht_meshes
365 // So: find arealist, pipe, ball, and profile_list types
366 final HashSet hs
= pt
.findBasicTypeChildren();
367 if (null == hs
|| 0 == hs
.size()) {
368 Utils
.log("Node " + pt
+ " contains no 3D-displayable children");
372 final List
<Content
> list
= new ArrayList
<Content
>();
374 for (final Iterator it
= hs
.iterator(); it
.hasNext(); ) {
375 // obtain the Displayable object under the node
376 final ProjectThing child
= (ProjectThing
)it
.next();
377 Object obc
= child
.getObject();
378 Displayable displ
= obc
.getClass().equals(String
.class) ?
null : (Displayable
)obc
;
380 if (displ
.getClass().equals(Profile
.class)) {
381 //Utils.log("Display3D can't handle Bezier profiles at the moment.");
382 // handled by profile_list Thing
385 if (!displ
.isVisible()) {
386 Utils
.log("Skipping non-visible node " + displ
);
390 //StopWatch sw = new StopWatch();
391 // obtain the containing LayerSet
392 Display3D d3d
= null;
393 if (null != displ
) d3d
= Display3D
.get(displ
.getLayerSet());
394 else if (child
.getType().equals("profile_list")) {
395 ArrayList al_children
= child
.getChildren();
396 if (null == al_children
|| 0 == al_children
.size()) continue;
397 // else, get the first Profile and get its LayerSet
398 d3d
= Display3D
.get(((Displayable
)((ProjectThing
)al_children
.get(0)).getObject()).getLayerSet());
400 Utils
.log("Don't know what to do with node " + child
);
403 Utils
.log("Could not get a proper 3D display for node " + displ
);
404 return null; // java3D not installed most likely
406 if (d3d
.ht_pt_meshes
.contains(child
)) {
407 Utils
.log2("Already here: " + child
);
408 continue; // already here
410 setWaitingCursor(); // the above may be creating a display
411 //sw.elapsed("after creating and/or retrieving Display3D");
412 Future
<Content
> fu
= d3d
.addMesh(child
, displ
, resample
);
413 if (wait
&& -1 != d3d
.resample
) {
417 //sw.elapsed("after creating mesh");
422 } catch (Exception e
) {
430 return launchers
.submit(c
);
433 static public void resetView(final LayerSet ls
) {
434 Display3D d3d
= (Display3D
) ht_layer_sets
.get(ls
);
435 if (null != d3d
) d3d
.universe
.resetView();
438 static public void showOrthoslices(Patch p
) {
439 Display3D d3d
= get(p
.getLayerSet());
440 d3d
.adjustResampling();
441 //d3d.universe.resetView();
442 String title
= makeTitle(p
) + " orthoslices";
444 d3d
.universe
.removeContent(title
);
445 PatchStack ps
= p
.makePatchStack();
446 ImagePlus imp
= get8BitStack(ps
);
447 d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
448 Content ct
= d3d
.universe
.getContent(title
);
449 setTransform(ct
, ps
.getPatch(0));
450 ct
.toggleLock(); // locks the added content
453 static public void showVolume(Patch p
) {
454 Display3D d3d
= get(p
.getLayerSet());
455 d3d
.adjustResampling();
456 //d3d.universe.resetView();
457 String title
= makeTitle(p
) + " volume";
459 d3d
.universe
.removeContent(title
);
460 PatchStack ps
= p
.makePatchStack();
461 ImagePlus imp
= get8BitStack(ps
);
462 d3d
.universe
.addVoltex(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
463 Content ct
= d3d
.universe
.getContent(title
);
464 setTransform(ct
, ps
.getPatch(0));
465 ct
.toggleLock(); // locks the added content
468 static private void setTransform(Content ct
, Patch p
) {
469 final double[] a
= new double[6];
470 p
.getAffineTransform().getMatrix(a
);
471 Calibration cal
= p
.getLayerSet().getCalibration();
472 // a is: m00 m10 m01 m11 m02 m12
473 // d expects: m01 m02 m03 m04, m11 m12 ...
474 ct
.applyTransform(new Transform3D(new double[]{a
[0], a
[2], 0, a
[4] * cal
.pixelWidth
,
475 a
[1], a
[3], 0, a
[5] * cal
.pixelWidth
,
476 0, 0, 1, p
.getLayer().getZ() * cal
.pixelWidth
,
480 /** Returns a stack suitable for the ImageJ 3D Viewer, either 8-bit gray or 8-bit color.
481 * If the PatchStach is already of the right type, it is returned,
482 * otherwise a copy is made in the proper type.
484 static private ImagePlus
get8BitStack(final PatchStack ps
) {
485 switch (ps
.getType()) {
486 case ImagePlus
.COLOR_RGB
:
487 // convert stack to 8-bit color
488 return ps
.createColor256Copy();
489 case ImagePlus
.GRAY16
:
490 case ImagePlus
.GRAY32
:
491 // convert stack to 8-bit
492 return ps
.createGray8Copy();
498 /** A Material, but avoiding name colisions. */
499 static private int mat_index
= 1;
500 static private class Mtl
{
506 Mtl(float alpha
, float R
, float G
, float B
) {
511 name
= "mat_" + mat_index
;
514 public boolean equals(Object ob
) {
515 if (ob
instanceof Display3D
.Mtl
) {
517 if (mat
.alpha
== alpha
526 void fill(StringBuffer sb
) {
527 sb
.append("\nnewmtl ").append(name
).append('\n')
528 .append("Ns 96.078431\n")
529 .append("Ka 0.0 0.0 0.0\n")
530 .append("Kd ").append(R
).append(' ').append(G
).append(' ').append(B
).append('\n') // this is INCORRECT but I'll figure out the conversion later
531 .append("Ks 0.5 0.5 0.5\n")
533 .append("d ").append(alpha
).append('\n')
534 .append("illum 2\n\n");
537 return (int)((R
+ G
+ B
) / 3 * 255); // something silly
541 /** Generates DXF file from a table of ProjectThing and their associated triangles. */
542 private String
createDXF(Hashtable ht_content
) {
543 StringBuffer sb_data
= new StringBuffer("0\nSECTION\n2\nENTITIES\n"); //header of file
544 for (Iterator it
= ht_content
.entrySet().iterator(); it
.hasNext(); ) {
545 Map
.Entry entry
= (Map
.Entry
)it
.next();
546 ProjectThing pt
= (ProjectThing
)entry
.getKey();
547 Displayable displ
= (Displayable
)pt
.getObject();
548 List triangles
= (List
)entry
.getValue();
549 float[] color
= displ
.getColor().getColorComponents(null);
550 Mtl mtl
= new Mtl(displ
.getAlpha(), color
[0], color
[1], color
[2]);
551 writeTrianglesDXF(sb_data
, triangles
, mtl
.name
, Integer
.toString(mtl
.getAsSingle()));
553 sb_data
.append("0\nENDSEC\n0\nEOF\n"); //TRAILER of the file
554 return sb_data
.toString();
557 /** @param format works as extension as well. */
558 private void export(final ProjectThing pt
, final String format
) {
559 if (0 == ht_pt_meshes
.size()) return;
561 File file
= Utils
.chooseFile("untitled", format
);
562 if (null == file
) return;
563 final String name
= file
.getName();
565 if (!name2
.endsWith("." + format
)) {
566 name2
+= "." + format
;
568 File f2
= new File(file
.getParent() + "/" + name2
);
570 while (f2
.exists()) {
571 name2
= name
+ "_" + i
+ "." + format
;
572 f2
= new File(name2
);
574 Hashtable ht_content
= ht_pt_meshes
;
576 ht_content
= new Hashtable();
577 ht_content
.put(pt
, ht_pt_meshes
.get(pt
));
579 if (format
.equals("obj")) {
580 String
[] data
= createObjAndMtl(name2
, ht_content
);
581 Utils
.saveToFile(f2
, data
[0]);
582 Utils
.saveToFile(new File(f2
.getParent() + "/" + name2
+ ".mtl"), data
[1]);
583 } else if (format
.equals("dxf")) {
584 Utils
.saveToFile(f2
, createDXF(ht_content
));
588 /** Wavefront format. Returns the contents of two files: one for materials, another for meshes*/
589 private String
[] createObjAndMtl(final String file_name
, final Hashtable ht_content
) {
590 StringBuffer sb_obj
= new StringBuffer("# TrakEM2 OBJ File\n");
591 sb_obj
.append("mtllib ").append(file_name
).append(".mtl").append('\n');
593 Hashtable ht_mat
= new Hashtable();
595 int j
= 1; // Vert indices in .obj files are global, not reset for every object.
596 // starting at '1' because vert indices start at one.
598 for (Iterator it
= ht_content
.entrySet().iterator(); it
.hasNext(); ) {
599 Map
.Entry entry
= (Map
.Entry
)it
.next(); // I hate java's gratuituous verbosity
600 ProjectThing pt
= (ProjectThing
)entry
.getKey();
601 Displayable displ
= (Displayable
)pt
.getObject();
602 List triangles
= (List
)entry
.getValue();
603 // make material, and see whether it exists already
604 float[] color
= displ
.getColor().getColorComponents(null);
605 Mtl mat
= new Mtl(displ
.getAlpha(), color
[0], color
[1], color
[2]);
606 Object mat2
= ht_mat
.get(mat
);
607 if (null != mat2
) mat
= (Mtl
)mat2
; // recycling
608 else ht_mat
.put(mat
, mat
); // !@#$% Can't get the object in a HashSet easily
609 // make list of vertices
610 String title
= displ
.getProject().getMeaningfulTitle(displ
).replaceAll(" ", "_").replaceAll("#", "--");
611 Hashtable ht_points
= new Hashtable(); // because we like inefficiency
612 sb_obj
.append("o ").append(title
).append('\n');
613 final int len
= triangles
.size();
614 int[] index
= new int[len
];
615 int k
= 0; // iterate over index array, to make faces later
616 // j is tag for each new vert, which start at 1 (for some ridiculous reason)
617 for (Iterator tt
= triangles
.iterator(); tt
.hasNext(); ) {
618 Point3f p
= (Point3f
)tt
.next();
619 //no need if coords are not displaced//p = (Point3f)p.clone();
620 // check if point already exists
621 Object ob
= ht_points
.get(p
);
623 index
[k
] = ((Integer
)ob
).intValue();
628 ht_points
.put(p
, new Integer(j
));
630 sb_obj
.append('v').append(' ').append(p
.x
)
631 .append(' ').append(p
.y
)
632 .append(' ').append(p
.z
).append('\n');
637 sb_obj
.append("usemtl ").append(mat
.name
).append('\n');
638 sb_obj
.append("s 1\n");
639 if (0 != len
% 3) Utils
.log2("WARNING: list of triangles not multiple of 3");
641 int len_p
= ht_points
.size();
642 for (int i
=0; i
<len
; i
+=3) {
643 sb_obj
.append('f').append(' ').append(index
[i
])
644 .append(' ').append(index
[i
+1])
645 .append(' ').append(index
[i
+2]).append('\n');
646 //if (index[i] > len_p) Utils.log2("WARNING: face vert index beyond range"); // range is from 1 to len_p inclusive
647 //if (index[i+1] > len_p) Utils.log2("WARNING: face vert index beyond range");
648 //if (index[i+2] > len_p) Utils.log2("WARNING: face vert index beyond range");
649 //Utils.log2("j: " + index[i]);
655 StringBuffer sb_mtl
= new StringBuffer("# TrakEM2 MTL File\n");
656 for (Iterator it
= ht_mat
.keySet().iterator(); it
.hasNext(); ) {
657 Mtl mat
= (Mtl
)it
.next();
661 return new String
[]{sb_obj
.toString(), sb_mtl
.toString()};
664 /** Considers there is only one Display3D for each LayerSet. */
665 static public void remove(ProjectThing pt
) {
666 if (null == pt
) return;
667 if (null == pt
.getObject()) return;
668 Object ob
= pt
.getObject();
669 if (!(ob
instanceof Displayable
)) return;
670 Displayable displ
= (Displayable
)ob
;
671 Object d3ob
= ht_layer_sets
.get(displ
.getLayerSet()); // TODO profile_list is going to fail here
673 // there is no Display3D showing the pt to remove
674 Utils
.log2("No Display3D contains ProjectThing: " + pt
);
677 Display3D d3d
= (Display3D
)d3ob
;
678 Object ob_mesh
= d3d
.ht_pt_meshes
.remove(pt
);
679 if (null == ob_mesh
) {
680 Utils
.log2("No mesh contained within " + d3d
+ " for ProjectThing " + pt
);
681 return; // not contained here
683 String title
= makeTitle(displ
);
684 //Utils.log(d3d.universe.contains(title) + ": Universe contains " + displ);
685 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.
688 static private void writeTrianglesDXF(final StringBuffer sb
, final List triangles
, final String the_group
, final String the_color
) {
691 final String s10
= "10\n"; final String s11
= "11\n"; final String s12
= "12\n"; final String s13
= "13\n";
692 final String s20
= "20\n"; final String s21
= "21\n"; final String s22
= "22\n"; final String s23
= "23\n";
693 final String s30
= "30\n"; final String s31
= "31\n"; final String s32
= "32\n"; final String s33
= "33\n";
694 final String triangle_header
= "0\n3DFACE\n8\n" + the_group
+ "\n6\nCONTINUOUS\n62\n" + the_color
+ L
;
696 final int len
= triangles
.size();
697 final Point3f
[] vert
= new Point3f
[len
];
698 triangles
.toArray(vert
);
699 for (int i
=0; i
<len
; i
+=3) {
701 sb
.append(triangle_header
)
703 .append(s10
).append(vert
[i
].x
).append(L
)
704 .append(s20
).append(vert
[i
].y
).append(L
)
705 .append(s30
).append(vert
[i
].z
).append(L
)
707 .append(s11
).append(vert
[i
+1].x
).append(L
)
708 .append(s21
).append(vert
[i
+1].y
).append(L
)
709 .append(s31
).append(vert
[i
+1].z
).append(L
)
711 .append(s12
).append(vert
[i
+2].x
).append(L
)
712 .append(s22
).append(vert
[i
+2].y
).append(L
)
713 .append(s32
).append(vert
[i
+2].z
).append(L
)
715 .append(s13
).append(vert
[i
+2].x
).append(L
) // repeated point
716 .append(s23
).append(vert
[i
+2].y
).append(L
)
717 .append(s33
).append(vert
[i
+2].z
).append(L
);
721 /** Creates a mesh for the given Displayable in a separate Thread. */
722 private Future
<Content
> addMesh(final ProjectThing pt
, final Displayable displ
, final int resample
) {
723 final double scale
= this.scale
;
724 FutureTask
<Content
> fu
= new FutureTask
<Content
>(new Callable
<Content
>() {
725 public Content
call() {
726 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
729 // 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.)
730 List triangles
= null;
731 boolean no_culling
= false; // don't show back faces when false
732 if (displ
instanceof AreaList
) {
734 if (-1 == resample
) rs
= Display3D
.this.resample
= adjustResampling(); // will adjust this.resample, and return it (even if it's a default value)
735 else rs
= Display3D
.this.resample
;
736 triangles
= ((AreaList
)displ
).generateTriangles(scale
, rs
);
737 //triangles = removeNonManifold(triangles);
738 } else if (displ
instanceof Ball
) {
739 double[][][] globe
= Ball
.generateGlobe(12, 12);
740 triangles
= ((Ball
)displ
).generateTriangles(scale
, globe
);
741 } else if (displ
instanceof Line3D
) {
743 // adjustResampling(); // fails horribly, needs first to correct mesh-generation code
744 triangles
= ((Line3D
)displ
).generateTriangles(scale
, 12, 1 /*Display3D.this.resample*/);
745 } else if (null == displ
&& pt
.getType().equals("profile_list")) {
746 triangles
= Profile
.generateTriangles(pt
, scale
);
750 if (null == triangles
) {
751 Utils
.log("Some error ocurred: can't create triangles for " + displ
);
754 if (0 == triangles
.size()) {
755 Utils
.log2("Skipping empty mesh for " + displ
.getTitle());
758 if (0 != triangles
.size() % 3) {
759 Utils
.log2("Skipping non-multiple-of-3 vertices list generated for " + displ
.getTitle());
765 color
= displ
.getColor();
766 alpha
= displ
.getAlpha();
768 // for profile_list: get from the first (what a kludge)
769 Object obp
= ((ProjectThing
)pt
.getChildren().get(0)).getObject();
770 if (null == obp
) return null;
771 Displayable di
= (Displayable
)obp
;
772 color
= di
.getColor();
773 alpha
= di
.getAlpha();
778 no_culling
= true; // for ALL
780 // add to 3D view (synchronized)
781 synchronized (u_lock
) {
784 // craft a unique title (id is always unique)
785 String title
= null == displ ? pt
.toString() + " #" + pt
.getId() : makeTitle(displ
);
786 if (ht_pt_meshes
.contains(pt
) || universe
.contains(title
)) {
787 // remove content from universe
788 universe
.removeContent(title
);
789 // no need to remove entry from table, it's overwritten below
792 ht_pt_meshes
.put(pt
, triangles
);
793 // ensure proper default transform
794 //universe.resetView();
796 Color3f c3
= new Color3f(color
);
799 // create a mesh with the same color and zero transparency (that is, full opacity)
800 CustomTriangleMesh mesh
= new CustomTriangleMesh(triangles
, c3
, 0);
801 // Set mesh properties for double-sided triangles
802 PolygonAttributes pa
= mesh
.getAppearance().getPolygonAttributes();
803 pa
.setCullFace(PolygonAttributes
.CULL_NONE
);
804 pa
.setBackFaceNormalFlip(true);
806 // After setting properties, add to the viewer
807 ct
= universe
.addCustomMesh(mesh
, title
);
809 ct
= universe
.addTriangleMesh(triangles
, c3
, title
);
812 if (null == ct
) return null;
814 // Set general content properties
815 ct
.setTransparency(1f
- alpha
);
816 // Default is unlocked (editable) transformation; set it to locked:
819 } catch (Exception e
) {
825 Utils
.log2(pt
.toString() + " n points: " + triangles
.size());
829 } catch (Exception e
) {
835 executors
.submit(fu
);
839 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
840 static public Future
<Content
> addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
) {
841 return addMesh(ref_ls
, vs
, title
, color
, null, 1.0f
);
844 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
845 static public Future
<Content
> addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
, final double[] widths
, final float alpha
) {
846 final FutureTask
<Content
> fu
= new FutureTask
<Content
>(new Callable
<Content
>() {
847 public Content
call() {
848 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
851 final Display3D d3d
= Display3D
.get(ref_ls
);
852 final double scale
= d3d
.scale
;
853 final double width
= d3d
.width
;
854 float transp
= 1 - alpha
;
855 if (transp
< 0) transp
= 0;
856 if (transp
> 1) transp
= 1;
858 Utils
.log("WARNING: adding a 3D object fully transparent.");
861 double[] wi
= widths
;
862 if (null == widths
) {
863 wi
= new double[vs
.getPoints(0).length
];
864 //Utils.log2("len: " + wi.length + vs.getPoints(0).length + vs.getPoints(1).length);
865 Arrays
.fill(wi
, 2.0);
866 } else if (widths
.length
!= vs
.length()) {
867 Utils
.log("ERROR: widths.length != VectorString3D.length()");
871 List triangles
= Pipe
.generateTriangles(Pipe
.makeTube(vs
.getPoints(0), vs
.getPoints(1), vs
.getPoints(2), wi
, 1, 12, null), scale
);
875 // add to 3D view (synchronized)
876 synchronized (d3d
.u_lock
) {
879 // ensure proper default transform
880 //d3d.universe.resetView();
882 //Utils.log2(title + " : vertex count % 3 = " + triangles.size() % 3 + " for " + triangles.size() + " vertices");
883 //d3d.universe.ensureScale((float)(width*scale));
884 ct
= d3d
.universe
.addMesh(triangles
, new Color3f(color
), title
, /*(float)(width*scale),*/ 1);
885 ct
.setTransparency(transp
);
887 } catch (Exception e
) {
896 } catch (Exception e
) {
904 launchers
.submit(new Runnable() { public void run() {
905 final Display3D d3d
= Display3D
.get(ref_ls
);
906 d3d
.executors
.submit(fu
);
912 // This method has the exclusivity in adjusting the resampling value.
913 synchronized private final int adjustResampling() {
914 if (resample
> 0) return resample
;
915 final GenericDialog gd
= new GenericDialog("Resample");
916 gd
.addSlider("Resample: ", 1, 20, -1 != resample ? resample
: DEFAULT_RESAMPLE
);
918 if (gd
.wasCanceled()) {
919 resample
= -1 != resample ? resample
: DEFAULT_RESAMPLE
; // current or default value
922 resample
= ((java
.awt
.Scrollbar
)gd
.getSliders().get(0)).getValue();
926 /** Checks if there is any Display3D instance currently showing the given Displayable. */
927 static public boolean isDisplayed(final Displayable d
) {
928 if (null == d
) return false;
929 final String title
= makeTitle(d
);
930 for (Iterator it
= Display3D
.ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
931 Display3D d3d
= (Display3D
)it
.next();
932 if (null != d3d
.universe
.getContent(title
)) return true;
934 if (d
.getClass() == Profile
.class) {
935 Content content
= getProfileContent(d
);
940 /** Checks if the given Displayable is a Profile, and tries to find a possible Content object in the Image3DUniverse of its LayerSet according to the title as created from its profile_list ProjectThing. */
941 static public Content
getProfileContent(final Displayable d
) {
942 if (null == d
) return null;
943 if (d
.getClass() != Profile
.class) return null;
944 Display3D d3d
= get(d
.getLayer().getParent());
945 if (null == d3d
) return null;
946 ProjectThing pt
= d
.getProject().findProjectThing(d
);
947 if (null == pt
) return null;
948 pt
= (ProjectThing
) pt
.getParent();
949 return d3d
.universe
.getContent(new StringBuffer(pt
.toString()).append(" #").append(pt
.getId()).toString());
952 static public void setColor(final Displayable d
, final Color color
) {
953 launchers
.submit(new Runnable() { public void run() {
954 final Display3D d3d
= getDisplay(d
.getLayer().getParent());
955 if (null == d3d
) return; // no 3D displays open
956 d3d
.executors
.submit(new Runnable() { public void run() {
957 Content content
= d3d
.universe
.getContent(makeTitle(d
));
958 if (null == content
) content
= getProfileContent(d
);
959 if (null != content
) content
.setColor(new Color3f(color
));
964 static public void setTransparency(final Displayable d
, final float alpha
) {
965 if (null == d
) return;
966 Layer layer
= d
.getLayer();
967 if (null == layer
) return; // some objects have no layer, such as the parent LayerSet.
968 Object ob
= ht_layer_sets
.get(layer
.getParent());
969 if (null == ob
) return;
970 Display3D d3d
= (Display3D
)ob
;
971 String title
= makeTitle(d
);
972 Content content
= d3d
.universe
.getContent(title
);
973 if (null == content
) content
= getProfileContent(d
);
974 if (null != content
) content
.setTransparency(1 - alpha
);
975 else if (null == content
&& d
.getClass().equals(Patch
.class)) {
978 title
= pa
.getProject().getLoader().getFileName(pa
);
979 for (Iterator it
= Display3D
.ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
980 d3d
= (Display3D
)it
.next();
981 for (Iterator cit
= d3d
.universe
.getContents().iterator(); cit
.hasNext(); ) {
982 Content c
= (Content
)cit
.next();
983 if (c
.getName().startsWith(title
)) {
984 c
.setTransparency(1 - alpha
);
985 // no break, since there could be a volume and an orthoslice
993 static public String
makeTitle(final Displayable d
) {
994 return d
.getProject().getMeaningfulTitle(d
) + " #" + d
.getId();
996 static public String
makeTitle(final Patch p
) {
997 return new File(p
.getProject().getLoader().getAbsolutePath(p
)).getName()
998 + " #" + p
.getProject().getLoader().getNextId();
1001 /** Remake the mesh for the Displayable in a separate Thread, if it's included in a Display3D
1002 * (otherwise returns null). */
1003 static public Future
<Content
> update(final Displayable d
) {
1004 Layer layer
= d
.getLayer();
1005 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
1006 Object ob
= ht_layer_sets
.get(layer
.getParent());
1007 if (null == ob
) return null;
1008 Display3D d3d
= (Display3D
)ob
;
1009 return d3d
.addMesh(d
.getProject().findProjectThing(d
), d
, d3d
.resample
);
1013 static public final double computeTriangleArea() {
1014 return 0.5 * Math.sqrt(Math.pow(xA*yB + xB*yC + xC*yA, 2) +
1015 Math.pow(yA*zB + yB*zC + yC*zA, 2) +
1016 Math.pow(zA*xB + zB*xC + zC*xA, 2));
1020 static public final boolean contains(final LayerSet ls
, final String title
) {
1021 final Display3D d3d
= getDisplay(ls
);
1022 if (null == d3d
) return false;
1023 return null != d3d
.universe
.getContent(title
);
1026 static public void destroy() {
1027 launchers
.shutdownNow();
1030 static public void init() {
1031 if (launchers
.isShutdown()) {
1032 launchers
= Executors
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());