1 package ini
.trakem2
.display
;
4 import ij
.gui
.GenericDialog
;
5 import ij
.measure
.Calibration
;
7 import ij3d
.Image3DUniverse
;
8 import ij3d
.behaviors
.InteractiveBehavior
;
9 import ij3d
.behaviors
.Picker
;
11 import ini
.trakem2
.imaging
.PatchStack
;
12 import ini
.trakem2
.tree
.ProjectThing
;
13 import ini
.trakem2
.utils
.IJError
;
14 import ini
.trakem2
.utils
.Lock
;
15 import ini
.trakem2
.utils
.Utils
;
16 import ini
.trakem2
.vector
.VectorString3D
;
18 import java
.awt
.Color
;
19 import java
.awt
.Cursor
;
20 import java
.awt
.event
.MouseEvent
;
21 import java
.awt
.event
.WindowAdapter
;
22 import java
.awt
.event
.WindowEvent
;
24 import java
.util
.ArrayList
;
25 import java
.util
.Arrays
;
26 import java
.util
.Collection
;
27 import java
.util
.HashMap
;
28 import java
.util
.HashSet
;
29 import java
.util
.Hashtable
;
30 import java
.util
.Iterator
;
31 import java
.util
.List
;
33 import java
.util
.Vector
;
34 import java
.util
.concurrent
.Callable
;
35 import java
.util
.concurrent
.ExecutorService
;
36 import java
.util
.concurrent
.Executors
;
37 import java
.util
.concurrent
.Future
;
38 import java
.util
.concurrent
.FutureTask
;
39 import java
.util
.concurrent
.ScheduledExecutorService
;
40 import java
.util
.concurrent
.TimeUnit
;
41 import java
.util
.concurrent
.atomic
.AtomicInteger
;
43 import javax
.media
.j3d
.PolygonAttributes
;
44 import javax
.media
.j3d
.Transform3D
;
45 import javax
.media
.j3d
.View
;
46 import javax
.vecmath
.Color3f
;
47 import javax
.vecmath
.Point3f
;
48 import javax
.vecmath
.Point3d
;
50 import customnode
.CustomLineMesh
;
51 import customnode
.CustomMesh
;
52 import customnode
.CustomMultiMesh
;
53 import customnode
.CustomTriangleMesh
;
56 /** One Display3D instance for each LayerSet (maximum). */
57 public final class Display3D
{
59 /** A class to provide the behavior on control-clicking on
60 content in the 3D viewer. This will attempt to center
61 the front TrakEM2 Display on the clicked point */
62 protected static class ControlClickBehavior
extends InteractiveBehavior
{
64 protected Image3DUniverse universe
;
65 ControlClickBehavior(Image3DUniverse univ
) {
70 public void doProcess(MouseEvent e
) {
71 if(!e
.isControlDown() ||
72 e
.getID() != MouseEvent
.MOUSE_PRESSED
) {
76 Picker picker
= universe
.getPicker();
77 Content content
= picker
.getPickedContent(e
.getX(),e
.getY());
80 Point3d p
= picker
.getPickPointGeometry(content
,e
);
82 Utils
.log("No point was found on content "+content
);
85 Display display
= Display
.getFront();
87 // If there's no Display, just return...
90 LayerSet ls
= display
.getLayerSet();
92 Utils
.log("No LayerSet was found for the Display");
95 Calibration cal
= ls
.getCalibration();
97 Utils
.log("No calibration information was found for the LayerSet");
100 double scaledZ
= p
.z
/cal
.pixelWidth
;
101 Layer l
= ls
.getNearestLayer(scaledZ
);
103 Utils
.log("No layer was found nearest to "+scaledZ
);
106 Coordinate
<?
> coordinate
= new Coordinate
<Object
>(p
.x
/cal
.pixelWidth
,p
.y
/cal
.pixelHeight
,l
,null);
107 display
.center(coordinate
);
111 /** Table of LayerSet and Display3D - since there is a one to one relationship. */
112 static private Hashtable
<LayerSet
,Display3D
> ht_layer_sets
= new Hashtable
<LayerSet
,Display3D
>();
113 /**Control calls to new Display3D. */
114 static private Lock htlock
= new Lock();
116 /** 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. */
117 static public Hashtable
<LayerSet
,Display3D
> getMasterTable() {
118 return new Hashtable
<LayerSet
,Display3D
>(ht_layer_sets
);
121 /** Table of ProjectThing keys versus meshes, the latter represented by List of triangles in the form of thre econsecutive Point3f in the List.*/
122 private Hashtable
<ProjectThing
,Content
> ht_pt_meshes
= new Hashtable
<ProjectThing
,Content
>();
124 private Image3DUniverse universe
;
126 private Lock u_lock
= new Lock();
128 private LayerSet layer_set
;
129 /** The dimensions of the LayerSet in 2D. */
130 private double width
, height
;
131 private int resample
= -1; // unset
132 static private final int DEFAULT_RESAMPLE
= 4;
133 /** If the LayerSet dimensions are too large, then limit to max 2048 for width or height and setup a scale.*/
134 private final double scale
= 1.0; // OBSOLETE: meshes are now generated with imglib ShapeList images.
137 // To fork away from the EventDispatchThread
138 static private ExecutorService launchers
= Utils
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors(), "Display3D-launchers");
140 // To build meshes, or edit them
141 private ExecutorService executors
= Utils
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors(), "Display3D-executors");
144 static private KeyAdapter ka = new KeyAdapter() {
145 public void keyPressed(KeyEvent ke) {
146 // F1 .. F12 keys to set tools
147 ProjectToolbar.keyPressed(ke);
152 /** Defaults to parallel projection. */
153 private Display3D(final LayerSet ls
) {
155 this.width
= ls
.getLayerWidth();
156 this.height
= ls
.getLayerHeight();
157 this.universe
= new Image3DUniverse(512, 512); // size of the initial canvas, not the universe itself
158 this.universe
.getViewer().getView().setProjectionPolicy(View
.PERSPECTIVE_PROJECTION
); // (View.PERSPECTIVE_PROJECTION);
159 this.universe
.show();
160 this.universe
.getWindow().addWindowListener(new IW3DListener(this, ls
));
161 this.universe
.getWindow().setTitle(ls
.getProject().toString() + " -- 3D Viewer");
162 // it ignores the listeners:
163 //preaddKeyListener(this.universe.getWindow(), ka);
164 //preaddKeyListener(this.universe.getWindow().getCanvas(), ka);
167 Display3D
.ht_layer_sets
.put(ls
, this);
169 // Add a behavior to catch control + mouse-click on
170 // objects in the 3D viewer and centre the front Display
172 this.universe
.addInteractiveBehavior(new ControlClickBehavior(universe
));
176 private void preaddKeyListener(Component c, KeyListener kl) {
177 KeyListener[] all = c.getKeyListeners();
179 for (KeyListener k : all) c.removeKeyListener(k);
181 c.addKeyListener(kl);
183 for (KeyListener k : all) c.addKeyListener(k);
188 public Image3DUniverse
getUniverse() {
192 /* Take a snapshot know-it-all mode. Each Transform3D given as argument gets assigned to the (nearly) homonimous TransformGroup, which have the following relationships:
194 * scaleTG contains rotationsTG
195 * rotationsTG contains translateTG
196 * translateTG contains centerTG
197 * centerTG contains the whole scene, with all meshes, etc.
199 * Any null arguments imply the current transform in the open Display3D.
201 * 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.
203 * The TransformGroup instances may be reached like this:
205 * LayerSet layer_set = Display.getFrontLayer().getParent();
206 * Display3D d3d = Display3D.getDisplay(layer_set);
207 * TransformGroup scaleTG = d3d.getUniverse().getGlobalScale();
208 * TransformGroup rotationsTG = d3d.getUniverse().getGlobalRotate();
209 * TransformGroup translateTG = d3d.getUniverse().getGlobalTranslate();
210 * TransformGroup centerTG = d3d.getUniverse().getCenterTG();
212 * ... and the Transform3D from each may be read out indirectly like this:
214 * Transform3D t_scale = new Transform3D();
215 * scaleTG.getTransform(t_scale);
218 * 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).
221 /*public ImagePlus makeSnapshot(final Transform3D scale, final Transform3D rotate, final Transform3D translate, final Transform3D center) {
222 return universe.makeSnapshot(scale, rotate, translate, center);
225 /** Uses current scaling, translation and centering transforms! */
226 /*public ImagePlus makeSnapshotXY() { // aka posterior
228 return universe.makeSnapshot(null, new Transform3D(), null, null);
230 /** Uses current scaling, translation and centering transforms! */
231 /*public ImagePlus makeSnapshotXZ() { // aka dorsal
232 Transform3D rot1 = new Transform3D();
233 rot1.rotZ(-Math.PI/2);
234 Transform3D rot2 = new Transform3D();
235 rot2.rotX(Math.PI/2);
237 return universe.makeSnapshot(null, rot1, null, null);
240 /** Uses current scaling, translation and centering transforms! */
242 public ImagePlus makeSnapshotYZ() { // aka lateral
243 Transform3D rot = new Transform3D();
245 return universe.makeSnapshot(null, rot, null, null);
249 public ImagePlus makeSnapshotZX() { // aka frontal
250 Transform3D rot = new Transform3D();
251 rot.rotX(-Math.PI/2);
252 return universe.makeSnapshot(null, rot, null, null);
256 /** Uses current scaling, translation and centering transforms! Opposite side of XZ. */
258 public ImagePlus makeSnapshotXZOpp() {
259 Transform3D rot1 = new Transform3D();
260 rot1.rotX(-Math.PI/2); // 90 degrees clockwise
261 Transform3D rot2 = new Transform3D();
262 rot2.rotY(Math.PI); // 180 degrees around Y, to the other side.
264 return universe.makeSnapshot(null, rot1, null, null);
267 private class IW3DListener
extends WindowAdapter
{
268 private Display3D d3d
;
270 IW3DListener(Display3D d3d
, LayerSet ls
) {
274 public void windowClosing(WindowEvent we
) {
275 //Utils.log2("Display3D.windowClosing");
276 d3d
.executors
.shutdownNow();
277 /*Object ob =*/ ht_layer_sets
.remove(ls
);
279 Utils.log2("Removed Display3D from table for LayerSet " + ls);
282 public void windowClosed(WindowEvent we
) {
283 //Utils.log2("Display3D.windowClosed");
284 ht_layer_sets
.remove(ls
);
288 static private boolean check_j3d
= true;
289 static private boolean has_j3d_3dviewer
= false;
291 static private boolean hasLibs() {
295 Class
.forName("javax.vecmath.Point3f");
296 has_j3d_3dviewer
= true;
297 } catch (ClassNotFoundException cnfe
) {
298 Utils
.log("Java 3D not installed.");
299 has_j3d_3dviewer
= false;
303 Class
.forName("ij3d.ImageWindow3D");
304 has_j3d_3dviewer
= true;
305 } catch (ClassNotFoundException cnfe
) {
306 Utils
.log("3D Viewer not installed.");
307 has_j3d_3dviewer
= false;
311 return has_j3d_3dviewer
;
314 /** Get an existing Display3D for the given LayerSet, or create a new one for it (and cache it). */
315 static public Display3D
get(final LayerSet ls
) {
316 synchronized (htlock
) {
320 if (!hasLibs()) return null;
322 Display3D d3d
= ht_layer_sets
.get(ls
);
323 if (null != d3d
) return d3d
;
325 final boolean[] done
= new boolean[]{false};
326 javax
.swing
.SwingUtilities
.invokeAndWait(new Runnable() { public void run() {
327 ht_layer_sets
.put(ls
, new Display3D(ls
));
330 // wait to avoid crashes in amd64
331 // try { Thread.sleep(500); } catch (Exception e) {}
333 try { Thread
.sleep(10); } catch (Exception e
) {}
335 return ht_layer_sets
.get(ls
);
336 } catch (Exception e
) {
339 // executed even when returning from within the try-catch block
346 /** Get the Display3D instance that exists for the given LayerSet, if any. */
347 static public Display3D
getDisplay(final LayerSet ls
) {
348 return ht_layer_sets
.get(ls
);
351 static public void setWaitingCursor() {
352 for (Display3D d3d
: ht_layer_sets
.values()) {
353 d3d
.universe
.getWindow().setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
357 static public void doneWaiting() {
358 for (Display3D d3d
: ht_layer_sets
.values()) {
359 d3d
.universe
.getWindow().setCursor(Cursor
.getDefaultCursor());
363 static public Future
<Vector
<Future
<Content
>>> show(ProjectThing pt
) {
364 return show(pt
, false, -1);
367 static public void showAndResetView(final ProjectThing pt
) {
368 launchers
.submit(new Runnable() {
371 Future
<Vector
<Future
<Content
>>> fu
= show(pt
, true, -1);
372 Vector
<Future
<Content
>> vc
;
374 vc
= fu
.get(); // wait until done
375 } catch (Exception e
) {
379 for (Future
<Content
> fc
: vc
) {
381 Content c
= fc
.get();
382 if (null == c
) continue;
383 ArrayList
<Display3D
> d3ds
= new ArrayList
<Display3D
>();
384 synchronized (ht_layer_sets
) {
385 d3ds
.addAll(ht_layer_sets
.values());
387 /* // Disabled, it's annoying
388 for (Display3D d3d : d3ds) {
390 if (d3d.universe.getContents().contains(c)) {
391 d3d.universe.resetView(); // reset the absolute center
392 d3d.universe.adjustView(); // zoom out to bring all elements in universe within view
396 } catch (Exception e
) {
400 Utils
.logAll("Reset 3D view if not within field of view!");
405 /** 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. */
406 static public Future
<Vector
<Future
<Content
>>> show(final ProjectThing pt
, final boolean wait
, final int resample
) {
407 if (null == pt
) return null;
409 Future
<Vector
<Future
<Content
>>> fu
= launchers
.submit(new Callable
<Vector
<Future
<Content
>>>() {
410 public Vector
<Future
<Content
>> call() {
412 // Scan the given ProjectThing for 3D-viewable items
413 // So: find arealist, pipe, ball, and profile_list types
414 final HashSet
<ProjectThing
> hs
= pt
.findBasicTypeChildren();
415 if (null == hs
|| 0 == hs
.size()) {
416 Utils
.logAll("Node " + pt
+ " does not contain any 3D-displayable children");
420 // Remove profile if it lives under a profile_list
421 for (Iterator
<ProjectThing
> it
= hs
.iterator(); it
.hasNext(); ) {
422 ProjectThing pt
= it
.next();
423 if (null != pt
.getObject() && pt
.getObject().getClass() == Profile
.class && pt
.getParent().getType().equals("profile_list")) {
430 // Start new scheduler to publish/add meshes to the 3D Viewer every 5 seconds and when done.
431 final Hashtable
<Display3D
,Vector
<Content
>> contents
= new Hashtable
<Display3D
,Vector
<Content
>>();
432 final ScheduledExecutorService updater
= Executors
.newScheduledThreadPool(1);
433 final AtomicInteger counter
= new AtomicInteger();
434 updater
.scheduleWithFixedDelay(new Runnable() {
436 // Obtain a copy of the contents queue
437 HashMap
<Display3D
,Vector
<Content
>> m
= new HashMap
<Display3D
,Vector
<Content
>>();
438 synchronized (contents
) {
442 if (m
.isEmpty()) return;
443 // Add all to the corresponding Display3D
444 for (Map
.Entry
<Display3D
,Vector
<Content
>> e
: m
.entrySet()) {
445 e
.getKey().universe
.addContentLater(e
.getValue());
446 counter
.getAndAdd(e
.getValue().size());
448 Utils
.showStatus(new StringBuilder("Rendered ").append(counter
.get()).append('/').append(hs
.size()).toString());
450 }, 100, 4000, TimeUnit
.MILLISECONDS
);
452 // A list of all generated Content objects
453 final Vector
<Future
<Content
>> list
= new Vector
<Future
<Content
>>();
455 for (final Iterator
<ProjectThing
> it
= hs
.iterator(); it
.hasNext(); ) {
456 // obtain the Displayable object under the node
457 final ProjectThing child
= it
.next();
459 Object obc
= child
.getObject();
460 final Displayable displ
= obc
.getClass().equals(String
.class) ?
null : (Displayable
)obc
;
462 if (displ
.getClass().equals(Profile
.class)) {
463 //Utils.log("Display3D can't handle Bezier profiles at the moment.");
464 // handled by profile_list Thing
467 if (!displ
.isVisible()) {
468 Utils
.log("Skipping non-visible node " + displ
);
472 // obtain the containing LayerSet
474 if (null != displ
) d3d
= Display3D
.get(displ
.getLayerSet());
475 else if (child
.getType().equals("profile_list")) {
476 ArrayList
<ProjectThing
> al_children
= child
.getChildren();
477 if (null == al_children
|| 0 == al_children
.size()) continue;
478 // else, get the first Profile and get its LayerSet
479 d3d
= Display3D
.get(((Displayable
)((ProjectThing
)al_children
.get(0)).getObject()).getLayerSet());
481 Utils
.log("Don't know what to do with node " + child
);
485 Utils
.log("Could not get a proper 3D display for node " + displ
);
486 return null; // java3D not installed most likely
488 if (d3d
.ht_pt_meshes
.contains(child
)) {
489 Utils
.log2("Already here: " + child
);
490 continue; // already here
493 list
.add(d3d
.executors
.submit(new Callable
<Content
>() {
494 public Content
call() {
497 c
= d3d
.createMesh(child
, displ
, resample
).call();
499 synchronized (contents
) {
500 vc
= contents
.get(d3d
);
501 if (null == vc
) vc
= new Vector
<Content
>();
502 contents
.put(d3d
, vc
);
505 } catch (Exception e
) {
512 // If it's the last one:
514 // Add the concluding task, that waits on all and shuts down the scheduler
515 d3d
.executors
.submit(new Runnable() {
517 // Wait until all are done
518 for (Future
<Content
> c
: list
) {
521 } catch (Throwable t
) {
526 // Shutdown scheduler and execute remaining tasks
527 for (Runnable r
: updater
.shutdownNow()) {
530 } catch (Throwable e
) {
535 Utils
.showStatus(new StringBuilder("Done rendering ").append(counter
.get()).append('/').append(hs
.size()).toString());
545 if (wait
&& -1 != resample
) {
548 } catch (Throwable t
) {
556 static public void resetView(final LayerSet ls
) {
557 Display3D d3d
= ht_layer_sets
.get(ls
);
558 if (null != d3d
) d3d
.universe
.resetView();
561 static public void showOrthoslices(Patch p
) {
562 Display3D d3d
= get(p
.getLayerSet());
563 d3d
.adjustResampling();
564 //d3d.universe.resetView();
565 String title
= makeTitle(p
) + " orthoslices";
567 d3d
.universe
.removeContent(title
);
568 PatchStack ps
= p
.makePatchStack();
569 ImagePlus imp
= get8BitStack(ps
);
570 Content ct
= d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
571 setTransform(ct
, ps
.getPatch(0));
572 ct
.setLocked(true); // locks the added content
575 static public void showVolume(Patch p
) {
576 Display3D d3d
= get(p
.getLayerSet());
577 d3d
.adjustResampling();
578 //d3d.universe.resetView();
579 String title
= makeTitle(p
) + " volume";
581 d3d
.universe
.removeContent(title
);
582 PatchStack ps
= p
.makePatchStack();
583 ImagePlus imp
= get8BitStack(ps
);
584 Content ct
= d3d
.universe
.addVoltex(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
585 setTransform(ct
, ps
.getPatch(0));
586 ct
.setLocked(true); // locks the added content
589 static private void setTransform(Content ct
, Patch p
) {
590 final double[] a
= new double[6];
591 p
.getAffineTransform().getMatrix(a
);
592 Calibration cal
= p
.getLayerSet().getCalibration();
593 // a is: m00 m10 m01 m11 m02 m12
594 // d expects: m01 m02 m03 m04, m11 m12 ...
595 ct
.applyTransform(new Transform3D(new double[]{a
[0], a
[2], 0, a
[4] * cal
.pixelWidth
,
596 a
[1], a
[3], 0, a
[5] * cal
.pixelWidth
,
597 0, 0, 1, p
.getLayer().getZ() * cal
.pixelWidth
,
601 static public void showOrthoslices(final ImagePlus imp
, final String title
, final int wx
, final int wy
, final float scale2D
, final Layer first
) {
602 Display3D d3d
= get(first
.getParent());
603 d3d
.universe
.removeContent(title
);
604 Content ct
= d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, 1);
605 Calibration cal
= imp
.getCalibration();
606 Transform3D t
= new Transform3D(new double[]{1, 0, 0, wx
* cal
.pixelWidth
* scale2D
,
607 0, 1, 0, wy
* cal
.pixelHeight
* scale2D
,
608 0, 0, scale2D
, first
.getZ() * cal
.pixelWidth
* scale2D
, // not pixelDepth!
610 // why scale2D has to be there at all reflects a horrible underlying setting of the calibration, plus of the scaling in the Display3D.
612 ct
.applyTransform(t
);
616 /** Returns a stack suitable for the ImageJ 3D Viewer, either 8-bit gray or 8-bit color.
617 * If the PatchStack is already of the right type, it is returned,
618 * otherwise a copy is made in the proper type.
620 static private ImagePlus
get8BitStack(final PatchStack ps
) {
621 switch (ps
.getType()) {
622 case ImagePlus
.COLOR_RGB
:
623 // convert stack to 8-bit color
624 return ps
.createColor256Copy();
625 case ImagePlus
.GRAY16
:
626 case ImagePlus
.GRAY32
:
627 // convert stack to 8-bit
628 return ps
.createGray8Copy();
629 case ImagePlus
.GRAY8
:
630 case ImagePlus
.COLOR_256
:
633 Utils
.logAll("Cannot handle stacks of type: " + ps
.getType());
638 /** Considers there is only one Display3D for each LayerSet. */
639 static public void remove(ProjectThing pt
) {
640 if (null == pt
) return;
641 if (null == pt
.getObject()) return;
642 Object ob
= pt
.getObject();
643 if (!(ob
instanceof Displayable
)) return;
644 Displayable displ
= (Displayable
)ob
;
645 Display3D d3d
= ht_layer_sets
.get(displ
.getLayerSet()); // TODO profile_list is going to fail here
647 // there is no Display3D showing the pt to remove
648 //Utils.log2("No Display3D contains ProjectThing: " + pt);
651 if (null == d3d
.ht_pt_meshes
.remove(pt
)) {
652 Utils
.log2("No mesh contained within " + d3d
+ " for ProjectThing " + pt
);
653 return; // not contained here
656 String title = makeTitle(displ);
657 //Utils.log(d3d.universe.contains(title) + ": Universe contains " + displ);
658 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.
660 Utils
.log2(Utils
.toString(d3d
.ht_pt_meshes
));
661 Content ct
= d3d
.ht_pt_meshes
.get(pt
);
662 if (null != ct
) d3d
.universe
.removeContent(ct
.getName());
666 /** Creates a mesh for the given Displayable in a separate Thread, and adds it to the universe. */
667 private Future
<Content
> addMesh(final ProjectThing pt
, final Displayable displ
, final int resample
) {
668 return executors
.submit(new Callable
<Content
>() {
669 public Content
call() {
671 // 1 - Create content
672 Callable
<Content
> c
= createMesh(pt
, displ
, resample
);
673 if (null == c
) return null;
674 Content content
= c
.call();
675 if (null == content
) return null;
676 String title
= content
.getName();
677 // 2 - Remove from universe any content of the same title
678 if (universe
.contains(title
)) {
679 universe
.removeContent(title
);
681 // 3 - Add to universe, and wait
682 universe
.addContentLater(content
).get();
686 } catch (Exception e
) {
696 /** Returns a function that returns a Content object.
697 * Does NOT add the Content to the universe; it merely creates it. */
698 public Callable
<Content
> createMesh(final ProjectThing pt
, final Displayable displ
, final int resample
) {
699 final double scale
= 1.0; // OBSOLETE
700 return new Callable
<Content
>() {
701 public Content
call() {
702 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
705 // 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.)
706 final List
<Point3f
> triangles
;
707 //boolean no_culling_ = false;
710 final boolean line_mesh
;
711 final int line_mesh_mode
;
715 line_mesh_mode
= Integer
.MAX_VALUE
;
717 c
= displ
.getClass();
718 line_mesh
= Tree
.class.isAssignableFrom(c
) || Polyline
.class == c
;
719 if (Tree
.class.isAssignableFrom(c
)) line_mesh_mode
= CustomLineMesh
.PAIRWISE
;
720 else if (Polyline
.class == c
) line_mesh_mode
= CustomLineMesh
.CONTINUOUS
;
721 else line_mesh_mode
= Integer
.MAX_VALUE
; // disabled
724 List
<Point3f
> extra_triangles
= null;
725 List
<Color3f
> triangle_colors
= null,
726 extra_triangle_colors
= null;
729 if (displ
instanceof AreaContainer
) {
730 if (-1 == resample
) rs
= Display3D
.this.resample
= adjustResampling(); // will adjust this.resample, and return it (even if it's a default value)
731 else rs
= Display3D
.this.resample
;
733 if (AreaList
.class == c
) {
734 triangles
= ((AreaList
)displ
).generateTriangles(scale
, rs
);
735 //triangles = removeNonManifold(triangles);
736 } else if (Ball
.class == c
) {
737 double[][][] globe
= Ball
.generateGlobe(12, 12);
738 triangles
= ((Ball
)displ
).generateTriangles(scale
, globe
);
739 } else if (displ
instanceof Line3D
) {
741 // adjustResampling(); // fails horribly, needs first to correct mesh-generation code
742 triangles
= ((Line3D
)displ
).generateTriangles(scale
, 12, 1 /*Display3D.this.resample*/);
743 } else if (displ
instanceof Tree
<?
>) {
744 // A 3D wire skeleton, using CustomLineMesh
745 final Tree
.MeshData skeleton
= ((Tree
<?
>)displ
).generateSkeleton(scale
, 12, 1);
746 triangles
= skeleton
.verts
;
747 triangle_colors
= skeleton
.colors
;
748 if (displ
instanceof Treeline
) {
749 final Tree
.MeshData tube
= ((Treeline
)displ
).generateMesh(scale
, 12);
750 extra_triangles
= tube
.verts
;
751 extra_triangle_colors
= tube
.colors
;
752 } else if (displ
instanceof AreaTree
) {
753 final Tree
.MeshData mesh
= ((AreaTree
)displ
).generateMesh(scale
, rs
);
754 extra_triangles
= mesh
.verts
;
755 extra_triangle_colors
= mesh
.colors
;
757 if (null != extra_triangles
&& extra_triangles
.isEmpty()) extra_triangles
= null; // avoid issues with MultiMesh
758 } else if (Connector
.class == c
) {
759 final Tree
.MeshData octopus
= ((Connector
)displ
).generateMesh(scale
, 12);
760 triangles
= octopus
.verts
;
761 triangle_colors
= octopus
.colors
;
762 } else if (null == displ
&& pt
.getType().equals("profile_list")) {
763 triangles
= Profile
.generateTriangles(pt
, scale
);
764 //no_culling_ = true;
766 Utils
.log("Unrecognized type for 3D mesh generation: " + (null != displ ? displ
.getClass() : null) + " : " + displ
);
770 if (null == triangles
) {
771 Utils
.log("Some error ocurred: can't create triangles for " + displ
);
774 if (0 == triangles
.size()) {
775 Utils
.log2("Skipping empty mesh for " + displ
.getTitle());
778 if (!line_mesh
&& 0 != triangles
.size() % 3) {
779 Utils
.log2("Skipping non-multiple-of-3 vertices list generated for " + displ
.getTitle());
787 color
= displ
.getColor();
788 alpha
= displ
.getAlpha();
789 title
= makeTitle(displ
);
790 } else if (pt
.getType().equals("profile_list")) {
791 // for profile_list: get from the first (what a kludge; there should be a ZDisplayable ProfileList object)
792 Object obp
= ((ProjectThing
)pt
.getChildren().get(0)).getObject();
793 if (null == obp
) return null;
794 Displayable di
= (Displayable
)obp
;
795 color
= di
.getColor();
796 alpha
= di
.getAlpha();
797 Object ob
= pt
.getParent().getTitle();
798 if (null == ob
|| ob
.equals(pt
.getParent().getType())) title
= pt
.toString() + " #" + pt
.getId(); // Project.getMeaningfulTitle can't handle profile_list properly
799 else title
= ob
.toString() + " /[" + pt
.getParent().getType() + "]/[profile_list] #" + pt
.getId();
801 title
= pt
.toString() + " #" + pt
.getId();
806 // Why for all? Above no_culling_ is set to true or false, depending upon type. --> Because with transparencies it looks proper and better when no_culling is true.
807 final boolean no_culling
= true; // for ALL
811 synchronized (u_lock
) {
814 Color3f c3
= new Color3f(color
);
816 // If it exists, remove and add as new:
817 universe
.removeContent(title
);
822 //ct = universe.createContent(new CustomLineMesh(triangles, line_mesh_mode, c3, 0), title);
823 cm
= new CustomLineMesh(triangles
, line_mesh_mode
, c3
, 0);
824 } else if (no_culling
) {
825 // create a mesh with the same color and zero transparency (that is, full opacity)
826 CustomTriangleMesh mesh
= new CustomTriangleMesh(triangles
, c3
, 0);
827 // Set mesh properties for double-sided triangles
828 PolygonAttributes pa
= mesh
.getAppearance().getPolygonAttributes();
829 pa
.setCullFace(PolygonAttributes
.CULL_NONE
);
830 pa
.setBackFaceNormalFlip(true);
832 // After setting properties, add to the viewer
833 //ct = universe.createContent(mesh, title);
836 //ct = universe.createContent(new CustomTriangleMesh(triangles, c3, 0), title);
837 cm
= new CustomTriangleMesh(triangles
, c3
, 0);
840 if (null != triangle_colors
) cm
.setColor(triangle_colors
);
842 //if (null == cm) return null;
844 if (null == extra_triangles
|| 0 == extra_triangles
.size()) {
845 ct
= universe
.createContent(cm
, title
);
847 final CustomTriangleMesh extra
= new CustomTriangleMesh(extra_triangles
, c3
, 0);
848 if (null != extra_triangle_colors
) {
849 // Set mesh properties for double-sided triangles
850 PolygonAttributes pa
= extra
.getAppearance().getPolygonAttributes();
851 pa
.setCullFace(PolygonAttributes
.CULL_NONE
);
852 pa
.setBackFaceNormalFlip(true);
853 extra
.setColor(extra_triangle_colors
);
855 ct
= universe
.createContent(new CustomMultiMesh(Arrays
.asList(new CustomMesh
[]{cm
, extra
})), title
);
858 // Set general content properties
859 ct
.setTransparency(1f
- alpha
);
860 // Default is unlocked (editable) transformation; set it to locked:
863 // register mesh -- TODO is this necessary any longer?
864 ht_pt_meshes
.put(pt
, ct
);
866 } catch (Throwable e
) {
867 Utils
.logAll("Mesh generation failed for \"" + title
+ "\" from " + pt
);
875 Utils
.log2(pt
.toString() + " n points: " + triangles
.size());
879 } catch (Exception e
) {
887 static public class VectorStringContent
{
893 public VectorStringContent(VectorString3D vs
, String title
, Color color
, double[] widths
, float alpha
){
897 this.widths
= widths
;
900 public Content
asContent(Display3D d3d
) {
901 double[] wi
= widths
;
902 if (null == widths
) {
903 wi
= new double[vs
.getPoints(0).length
];
904 Arrays
.fill(wi
, 2.0);
905 } else if (widths
.length
!= vs
.length()) {
906 Utils
.log("ERROR: widths.length != VectorString3D.length()");
909 float transp
= 1 - alpha
;
910 if (transp
< 0) transp
= 0;
911 if (transp
> 1) transp
= 1;
913 Utils
.log("WARNING: adding a 3D object fully transparent.");
915 List
<Point3f
> triangles
= Pipe
.generateTriangles(Pipe
.makeTube(vs
.getPoints(0), vs
.getPoints(1), vs
.getPoints(2), wi
, 1, 12, null), d3d
.scale
);
916 Content ct
= d3d
.universe
.createContent(new CustomTriangleMesh(triangles
, new Color3f(color
), 0), title
);
917 ct
.setTransparency(transp
);
923 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
924 static public Future
<Collection
<Future
<Content
>>> addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
) {
925 return addMesh(ref_ls
, vs
, title
, color
, null, 1.0f
);
928 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
929 static public Future
<Collection
<Future
<Content
>>> addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
, final double[] widths
, final float alpha
) {
930 List
<Content
> col
= new ArrayList
<Content
>();
931 Display3D d3d
= Display3D
.get(ref_ls
);
932 col
.add(new VectorStringContent(vs
, title
, color
, widths
, alpha
).asContent(d3d
));
933 return d3d
.addContent(col
);
936 static public Future
<Collection
<Future
<Content
>>> show(final LayerSet ref_ls
, final Collection
<Content
> col
) {
937 Display3D d3d
= get(ref_ls
);
938 return d3d
.addContent(col
);
941 public Future
<Collection
<Future
<Content
>>> addContent(final Collection
<Content
> col
) {
943 final FutureTask
<Collection
<Future
<Content
>>> fu
= new FutureTask
<Collection
<Future
<Content
>>>(new Callable
<Collection
<Future
<Content
>>>() {
944 public Collection
<Future
<Content
>> call() {
945 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
947 return universe
.addContentLater(col
);
948 } catch (Throwable e
) {
954 launchers
.submit(new Runnable() { public void run() {
955 executors
.submit(fu
);
961 public Future
<Content
> addContent(final Content c
) {
962 final FutureTask
<Content
> fu
= new FutureTask
<Content
>(new Callable
<Content
>() {
963 public Content
call() {
964 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
966 return universe
.addContentLater(c
).get();
967 } catch (Throwable e
) {
974 launchers
.submit(new Runnable() { public void run() {
975 executors
.submit(fu
);
981 static public final int estimateResamplingFactor(final LayerSet ls
, final double width
, final double height
) {
982 final int max_dimension
= ls
.getPixelsMaxDimension();
983 return (int)(DEFAULT_RESAMPLE
/ (Math
.max(width
, height
) > max_dimension ?
984 max_dimension
/ Math
.max(width
, height
)
988 /** Estimate a scaling factor, to be used as a multiplier of the suggested default resampling. */
989 private final int estimateResamplingFactor() {
990 return estimateResamplingFactor(layer_set
, width
, height
);
993 // This method has the exclusivity in adjusting the resampling value, and it also returns it.
994 synchronized private final int adjustResampling() {
995 if (resample
> 0) return resample
;
996 final GenericDialog gd
= new GenericDialog("Resample");
997 final int default_resample
= estimateResamplingFactor();
998 gd
.addSlider("Resample: ", 1, Math
.max(default_resample
, 100), -1 != resample ? resample
: default_resample
);
1000 if (gd
.wasCanceled()) {
1001 resample
= -1 != resample ? resample
: default_resample
; // current or default value
1004 resample
= ((java
.awt
.Scrollbar
)gd
.getSliders().get(0)).getValue();
1008 /** Checks if there is any Display3D instance currently showing the given Displayable. */
1009 static public boolean isDisplayed(final Displayable d
) {
1010 if (null == d
) return false;
1011 final String title
= makeTitle(d
);
1012 for (Display3D d3d
: ht_layer_sets
.values()) {
1013 if (null != d3d
.universe
.getContent(title
)) return true;
1015 if (d
.getClass() == Profile
.class) {
1016 if (null != getProfileContent(d
)) return true;
1021 /** 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. */
1022 static public Content
getProfileContent(final Displayable d
) {
1023 if (null == d
) return null;
1024 if (d
.getClass() != Profile
.class) return null;
1025 Display3D d3d
= get(d
.getLayer().getParent());
1026 if (null == d3d
) return null;
1027 ProjectThing pt
= d
.getProject().findProjectThing(d
);
1028 if (null == pt
) return null;
1029 pt
= (ProjectThing
) pt
.getParent();
1030 return d3d
.universe
.getContent(new StringBuilder(pt
.toString()).append(" #").append(pt
.getId()).toString());
1033 static public Future
<Boolean
> setColor(final Displayable d
, final Color color
) {
1034 final Display3D d3d
= getDisplay(d
.getLayer().getParent());
1035 if (null == d3d
) return null; // no 3D displays open
1036 return d3d
.executors
.submit(new Callable
<Boolean
>() { public Boolean
call() {
1037 Content content
= d3d
.universe
.getContent(makeTitle(d
));
1038 if (null == content
) content
= getProfileContent(d
);
1039 if (null != content
) {
1040 content
.setColor(new Color3f(color
));
1047 static public Future
<Boolean
> setTransparency(final Displayable d
, final float alpha
) {
1048 if (null == d
) return null;
1049 Layer layer
= d
.getLayer();
1050 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
1051 final Display3D d3d
= ht_layer_sets
.get(layer
.getParent());
1052 if (null == d3d
) return null;
1053 return d3d
.executors
.submit(new Callable
<Boolean
>() { public Boolean
call() {
1054 String title
= makeTitle(d
);
1055 Content content
= d3d
.universe
.getContent(title
);
1056 if (null == content
) content
= getProfileContent(d
);
1057 if (null != content
) content
.setTransparency(1 - alpha
);
1058 else if (null == content
&& d
.getClass().equals(Patch
.class)) {
1059 Patch pa
= (Patch
)d
;
1061 title
= pa
.getProject().getLoader().getFileName(pa
);
1062 for (Display3D dd
: ht_layer_sets
.values()) {
1063 for (Iterator
<?
> cit
= dd
.universe
.getContents().iterator(); cit
.hasNext(); ) {
1064 Content c
= (Content
)cit
.next();
1065 if (c
.getName().startsWith(title
)) {
1066 c
.setTransparency(1 - alpha
);
1067 // no break, since there could be a volume and an orthoslice
1077 static public String
makeTitle(final Displayable d
) {
1078 return d
.getProject().getMeaningfulTitle(d
) + " #" + d
.getId();
1080 static public String
makeTitle(final Patch p
) {
1081 return new File(p
.getProject().getLoader().getAbsolutePath(p
)).getName()
1082 + " #" + p
.getProject().getLoader().getNextId();
1085 /** Remake the mesh for the Displayable in a separate Thread, if it's included in a Display3D
1086 * (otherwise returns null). */
1087 static public Future
<Content
> update(final Displayable d
) {
1088 Layer layer
= d
.getLayer();
1089 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
1090 Display3D d3d
= ht_layer_sets
.get(layer
.getParent());
1091 if (null == d3d
) return null;
1092 return d3d
.addMesh(d
.getProject().findProjectThing(d
), d
, d3d
.resample
);
1096 static public final double computeTriangleArea() {
1097 return 0.5 * Math.sqrt(Math.pow(xA*yB + xB*yC + xC*yA, 2) +
1098 Math.pow(yA*zB + yB*zC + yC*zA, 2) +
1099 Math.pow(zA*xB + zB*xC + zC*xA, 2));
1103 static public final boolean contains(final LayerSet ls
, final String title
) {
1104 final Display3D d3d
= getDisplay(ls
);
1105 if (null == d3d
) return false;
1106 return null != d3d
.universe
.getContent(title
);
1109 static public void destroy() {
1110 launchers
.shutdownNow();
1113 static public void init() {
1114 if (launchers
.isShutdown()) {
1115 launchers
= Utils
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors(), "Display3D-launchers");
1119 /** Creates a calibrated sphere to represent a point at LayerSet pixel coordinates wx, wy, wz, with radius wr.*/
1120 public List
<Point3f
> createFatPoint(final double wx
, final double wy
, final double wz
, final double wr
, final Calibration cal
) {
1121 final double[][][] globe
= Ball
.generateGlobe(12, 12);
1122 final int sign
= cal
.pixelDepth
< 0 ?
-1 : 1;
1123 for (int z
=0; z
<globe
.length
; z
++) {
1124 for (int k
=0; k
<globe
[0].length
; k
++) {
1125 globe
[z
][k
][0] = (globe
[z
][k
][0] * wr
+ wx
) * scale
* cal
.pixelWidth
;
1126 globe
[z
][k
][1] = (globe
[z
][k
][1] * wr
+ wy
) * scale
* cal
.pixelHeight
;
1127 globe
[z
][k
][2] = (globe
[z
][k
][2] * wr
+ wz
) * scale
* cal
.pixelWidth
* sign
; // not pixelDepth, see day notes 20080227. Because pixelDepth is in microns/px, not in px/microns, and the z coord here is taken from the z of the layer, which is in pixels.
1130 final ArrayList
<Point3f
> list
= new ArrayList
<Point3f
>();
1131 // create triangular faces and add them to the list
1132 for (int z
=0; z
<globe
.length
-1; z
++) { // the parallels
1133 for (int k
=0; k
<globe
[0].length
-1; k
++) { // meridian points
1134 // half quadrant (a triangle)
1135 list
.add(new Point3f((float)globe
[z
][k
][0], (float)globe
[z
][k
][1], (float)globe
[z
][k
][2]));
1136 list
.add(new Point3f((float)globe
[z
+1][k
+1][0], (float)globe
[z
+1][k
+1][1], (float)globe
[z
+1][k
+1][2]));
1137 list
.add(new Point3f((float)globe
[z
+1][k
][0], (float)globe
[z
+1][k
][1], (float)globe
[z
+1][k
][2]));
1138 // the other half quadrant
1139 list
.add(new Point3f((float)globe
[z
][k
][0], (float)globe
[z
][k
][1], (float)globe
[z
][k
][2]));
1140 list
.add(new Point3f((float)globe
[z
][k
+1][0], (float)globe
[z
][k
+1][1], (float)globe
[z
][k
+1][2]));
1141 list
.add(new Point3f((float)globe
[z
+1][k
+1][0], (float)globe
[z
+1][k
+1][1], (float)globe
[z
+1][k
+1][2]));
1147 /** Expects uncalibrated wx,wy,wz, (i.e. pixel values), to be calibrated by @param ls calibration. */
1148 static public final Future
<Content
> addFatPoint(final String title
, final LayerSet ls
, final double wx
, final double wy
, final double wz
, final double wr
, final Color color
) {
1149 Display3D d3d
= Display3D
.get(ls
);
1150 d3d
.universe
.removeContent(title
);
1151 Content ct
= d3d
.universe
.createContent(new CustomTriangleMesh(d3d
.createFatPoint(wx
, wy
, wz
, wr
, ls
.getCalibrationCopy()), new Color3f(color
), 0), title
);
1153 return d3d
.addContent(ct
);