1 package ini
.trakem2
.display
;
4 import java
.awt
.Cursor
;
5 import java
.awt
.event
.WindowAdapter
;
6 import java
.awt
.event
.WindowEvent
;
8 import java
.util
.ArrayList
;
9 import java
.util
.Arrays
;
10 import java
.util
.Collection
;
11 import java
.util
.Collections
;
12 import java
.util
.HashMap
;
13 import java
.util
.HashSet
;
14 import java
.util
.Hashtable
;
15 import java
.util
.Iterator
;
16 import java
.util
.List
;
18 import java
.util
.Vector
;
19 import java
.util
.concurrent
.Callable
;
20 import java
.util
.concurrent
.ExecutorService
;
21 import java
.util
.concurrent
.Executors
;
22 import java
.util
.concurrent
.Future
;
23 import java
.util
.concurrent
.FutureTask
;
24 import java
.util
.concurrent
.ScheduledExecutorService
;
25 import java
.util
.concurrent
.TimeUnit
;
26 import java
.util
.concurrent
.atomic
.AtomicInteger
;
28 import org
.scijava
.java3d
.PolygonAttributes
;
29 import org
.scijava
.java3d
.Transform3D
;
30 import org
.scijava
.java3d
.View
;
31 import org
.scijava
.vecmath
.Color3f
;
32 import org
.scijava
.vecmath
.Point3f
;
34 import customnode
.CustomLineMesh
;
35 import customnode
.CustomMesh
;
36 import customnode
.CustomMultiMesh
;
37 import customnode
.CustomTriangleMesh
;
39 import ij
.gui
.GenericDialog
;
40 import ij
.measure
.Calibration
;
42 import ij3d
.Image3DUniverse
;
43 import ij3d
.ImageWindow3D
;
44 import ij3d
.UniverseListener
;
45 import ini
.trakem2
.display
.d3d
.ControlClickBehavior
;
46 import ini
.trakem2
.display
.d3d
.Display3DGUI
;
47 import ini
.trakem2
.imaging
.PatchStack
;
48 import ini
.trakem2
.tree
.ProjectThing
;
49 import ini
.trakem2
.utils
.IJError
;
50 import ini
.trakem2
.utils
.Utils
;
51 import ini
.trakem2
.vector
.VectorString3D
;
54 /** One Display3D instance for each LayerSet (maximum). */
55 public final class Display3D
{
57 /** Table of LayerSet and Display3D - since there is a one to one relationship. */
58 static private Hashtable
<LayerSet
,Display3D
> ht_layer_sets
= new Hashtable
<LayerSet
,Display3D
>();
59 /**Control calls to new Display3D. */
60 static private Object htlock
= new Object();
62 /** 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. */
63 static public Hashtable
<LayerSet
,Display3D
> getMasterTable() {
64 return new Hashtable
<LayerSet
,Display3D
>(ht_layer_sets
);
67 /** Table of ProjectThing keys versus names of Content objects in the universe. */
68 private Map
<ProjectThing
,String
> ht_pt_meshes
= Collections
.synchronizedMap(new HashMap
<ProjectThing
,String
>());
70 private Image3DUniverse universe
;
72 private LayerSet layer_set
;
73 /** The dimensions of the LayerSet in 2D. */
74 private double width
, height
;
75 private int resample
= -1; // unset
76 static private final int DEFAULT_RESAMPLE
= 4;
77 /** If the LayerSet dimensions are too large, then limit to max 2048 for width or height and setup a scale.*/
78 private final double scale
= 1.0; // OBSOLETE: meshes are now generated with imglib ShapeList images.
81 // To fork away from the EventDispatchThread
82 static private ExecutorService launchers
= Utils
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors(), "Display3D-launchers");
84 // To build meshes, or edit them
85 private ExecutorService executors
= Utils
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors(), "Display3D-executors");
88 static private KeyAdapter ka = new KeyAdapter() {
89 public void keyPressed(KeyEvent ke) {
90 // F1 .. F12 keys to set tools
91 ProjectToolbar.keyPressed(ke);
96 /** Defaults to parallel projection. */
97 private Display3D(final LayerSet ls
) {
99 this.width
= ls
.getLayerWidth();
100 this.height
= ls
.getLayerHeight();
101 this.universe
= new Image3DUniverse(512, 512); // size of the initial canvas, not the universe itself
102 this.universe
.getViewer().getView().setProjectionPolicy(View
.PERSPECTIVE_PROJECTION
); // (View.PERSPECTIVE_PROJECTION);
104 //this.universe.show();
106 final Display3DGUI gui
= new Display3DGUI(this.universe
);
107 final ImageWindow3D win
= gui
.init();
108 this.universe
.init(win
);
110 win
.setVisible(true);
112 this.universe
.getWindow().addWindowListener(new IW3DListener(this, ls
));
113 this.universe
.getWindow().setTitle(ls
.getProject().toString() + " -- 3D Viewer");
114 // it ignores the listeners:
115 //preaddKeyListener(this.universe.getWindow(), ka);
116 //preaddKeyListener(this.universe.getWindow().getCanvas(), ka);
119 Display3D
.ht_layer_sets
.put(ls
, this);
121 // Add a behavior to catch control + mouse-click on
122 // objects in the 3D viewer and centre the front Display
124 this.universe
.addInteractiveBehavior(new ControlClickBehavior(universe
, ls
));
126 this.universe
.addUniverseListener(new UniverseListener() {
128 public void universeClosed() {
129 synchronized (ht_pt_meshes
) {
130 ht_pt_meshes
.clear();
134 public void transformationUpdated(final View arg0
) {
137 public void transformationStarted(final View arg0
) {
140 public void transformationFinished(final View arg0
) {
143 public void contentSelected(final Content arg0
) {
144 // TODO could select in TrakEM2's Display
147 public void contentRemoved(final Content arg0
) {
148 final String name
= arg0
.getName();
149 synchronized (ht_pt_meshes
) {
150 for (final Iterator
<Map
.Entry
<ProjectThing
,String
>> it
= ht_pt_meshes
.entrySet().iterator(); it
.hasNext(); ) {
151 if (name
.equals(it
.next().getValue())) {
159 public void contentChanged(final Content arg0
) {
162 public void contentAdded(final Content arg0
) {
165 public void canvasResized() {
171 private void preaddKeyListener(Component c, KeyListener kl) {
172 KeyListener[] all = c.getKeyListeners();
174 for (KeyListener k : all) c.removeKeyListener(k);
176 c.addKeyListener(kl);
178 for (KeyListener k : all) c.addKeyListener(k);
183 public Image3DUniverse
getUniverse() {
187 /* Take a snapshot know-it-all mode. Each Transform3D given as argument gets assigned to the (nearly) homonimous TransformGroup, which have the following relationships:
189 * scaleTG contains rotationsTG
190 * rotationsTG contains translateTG
191 * translateTG contains centerTG
192 * centerTG contains the whole scene, with all meshes, etc.
194 * Any null arguments imply the current transform in the open Display3D.
196 * 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.
198 * The TransformGroup instances may be reached like this:
200 * LayerSet layer_set = Display.getFrontLayer().getParent();
201 * Display3D d3d = Display3D.getDisplay(layer_set);
202 * TransformGroup scaleTG = d3d.getUniverse().getGlobalScale();
203 * TransformGroup rotationsTG = d3d.getUniverse().getGlobalRotate();
204 * TransformGroup translateTG = d3d.getUniverse().getGlobalTranslate();
205 * TransformGroup centerTG = d3d.getUniverse().getCenterTG();
207 * ... and the Transform3D from each may be read out indirectly like this:
209 * Transform3D t_scale = new Transform3D();
210 * scaleTG.getTransform(t_scale);
213 * 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).
216 /*public ImagePlus makeSnapshot(final Transform3D scale, final Transform3D rotate, final Transform3D translate, final Transform3D center) {
217 return universe.makeSnapshot(scale, rotate, translate, center);
220 /** Uses current scaling, translation and centering transforms! */
221 /*public ImagePlus makeSnapshotXY() { // aka posterior
223 return universe.makeSnapshot(null, new Transform3D(), null, null);
225 /** Uses current scaling, translation and centering transforms! */
226 /*public ImagePlus makeSnapshotXZ() { // aka dorsal
227 Transform3D rot1 = new Transform3D();
228 rot1.rotZ(-Math.PI/2);
229 Transform3D rot2 = new Transform3D();
230 rot2.rotX(Math.PI/2);
232 return universe.makeSnapshot(null, rot1, null, null);
235 /** Uses current scaling, translation and centering transforms! */
237 public ImagePlus makeSnapshotYZ() { // aka lateral
238 Transform3D rot = new Transform3D();
240 return universe.makeSnapshot(null, rot, null, null);
244 public ImagePlus makeSnapshotZX() { // aka frontal
245 Transform3D rot = new Transform3D();
246 rot.rotX(-Math.PI/2);
247 return universe.makeSnapshot(null, rot, null, null);
251 /** Uses current scaling, translation and centering transforms! Opposite side of XZ. */
253 public ImagePlus makeSnapshotXZOpp() {
254 Transform3D rot1 = new Transform3D();
255 rot1.rotX(-Math.PI/2); // 90 degrees clockwise
256 Transform3D rot2 = new Transform3D();
257 rot2.rotY(Math.PI); // 180 degrees around Y, to the other side.
259 return universe.makeSnapshot(null, rot1, null, null);
262 private class IW3DListener
extends WindowAdapter
{
263 private Display3D d3d
;
265 IW3DListener(final Display3D d3d
, final LayerSet ls
) {
270 public void windowClosing(final WindowEvent we
) {
271 //Utils.log2("Display3D.windowClosing");
272 d3d
.executors
.shutdownNow();
273 /*Object ob =*/ ht_layer_sets
.remove(ls
);
275 Utils.log2("Removed Display3D from table for LayerSet " + ls);
279 public void windowClosed(final WindowEvent we
) {
280 //Utils.log2("Display3D.windowClosed");
281 ht_layer_sets
.remove(ls
);
285 static private boolean check_j3d
= true;
286 static private boolean has_j3d_3dviewer
= false;
288 static private boolean hasLibs() {
292 Class
.forName("javax.vecmath.Point3f");
293 has_j3d_3dviewer
= true;
294 } catch (final ClassNotFoundException cnfe
) {
295 Utils
.log("Java 3D not installed.");
296 has_j3d_3dviewer
= false;
300 Class
.forName("ij3d.ImageWindow3D");
301 has_j3d_3dviewer
= true;
302 } catch (final ClassNotFoundException cnfe
) {
303 Utils
.log("3D Viewer not installed.");
304 has_j3d_3dviewer
= false;
308 return has_j3d_3dviewer
;
311 /** Get an existing Display3D for the given LayerSet, or create a new one for it (and cache it). */
312 static public Display3D
get(final LayerSet ls
) {
313 synchronized (htlock
) {
316 if (!hasLibs()) return null;
318 final Display3D d3d
= ht_layer_sets
.get(ls
);
319 if (null != d3d
) return d3d
;
321 final boolean[] done
= new boolean[]{false};
322 javax
.swing
.SwingUtilities
.invokeAndWait(new Runnable() { @Override
324 ht_layer_sets
.put(ls
, new Display3D(ls
));
327 // wait to avoid crashes in amd64
328 // try { Thread.sleep(500); } catch (Exception e) {}
330 try { Thread
.sleep(10); } catch (final Exception e
) {}
332 return ht_layer_sets
.get(ls
);
333 } catch (final Exception e
) {
340 /** Get the Display3D instance that exists for the given LayerSet, if any. */
341 static public Display3D
getDisplay(final LayerSet ls
) {
342 return ht_layer_sets
.get(ls
);
345 static public void setWaitingCursor() {
346 Utils
.invokeLater(new Runnable() { @Override
348 for (final Display3D d3d
: ht_layer_sets
.values()) {
349 d3d
.universe
.getWindow().setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
354 static public void doneWaiting() {
355 Utils
.invokeLater(new Runnable() { @Override
357 for (final Display3D d3d
: ht_layer_sets
.values()) {
358 d3d
.universe
.getWindow().setCursor(Cursor
.getDefaultCursor());
363 static public Future
<Vector
<Future
<Content
>>> show(final ProjectThing pt
) {
364 return show(pt
, false, -1);
367 static public void showAndResetView(final ProjectThing pt
) {
368 launchers
.submit(new Runnable() {
372 final Future
<Vector
<Future
<Content
>>> fu
= show(pt
, true, -1);
373 Vector
<Future
<Content
>> vc
;
375 vc
= fu
.get(); // wait until done
376 } catch (final Exception e
) {
380 for (final Future
<Content
> fc
: vc
) {
382 final Content c
= fc
.get();
383 if (null == c
) continue;
384 final ArrayList
<Display3D
> d3ds
= new ArrayList
<Display3D
>();
385 synchronized (ht_layer_sets
) {
386 d3ds
.addAll(ht_layer_sets
.values());
388 /* // Disabled, it's annoying
389 for (Display3D d3d : d3ds) {
391 if (d3d.universe.getContents().contains(c)) {
392 d3d.universe.resetView(); // reset the absolute center
393 d3d.universe.adjustView(); // zoom out to bring all elements in universe within view
397 } catch (final Exception e
) {
401 Utils
.logAll("Reset 3D view if not within field of view!");
406 /** 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. */
407 static public Future
<Vector
<Future
<Content
>>> show(final ProjectThing pt
, final boolean wait
, final int resample
) {
408 if (null == pt
) return null;
410 final Future
<Vector
<Future
<Content
>>> fu
= launchers
.submit(new Callable
<Vector
<Future
<Content
>>>() {
412 public Vector
<Future
<Content
>> call() {
414 // Scan the given ProjectThing for 3D-viewable items
415 // So: find arealist, pipe, ball, and profile_list types
416 final HashSet
<ProjectThing
> hs
= pt
.findBasicTypeChildren();
417 if (null == hs
|| 0 == hs
.size()) {
418 Utils
.logAll("Node " + pt
+ " does not contain any 3D-displayable children");
422 // Remove profile if it lives under a profile_list
423 for (final Iterator
<ProjectThing
> it
= hs
.iterator(); it
.hasNext(); ) {
424 final ProjectThing pt
= it
.next();
425 if (null != pt
.getObject() && pt
.getObject().getClass() == Profile
.class && pt
.getParent().getType().equals("profile_list")) {
432 // Start new scheduler to publish/add meshes to the 3D Viewer every 5 seconds and when done.
433 final Hashtable
<Display3D
,Vector
<Content
>> contents
= new Hashtable
<Display3D
,Vector
<Content
>>();
434 final ScheduledExecutorService updater
= Executors
.newScheduledThreadPool(1);
435 final AtomicInteger counter
= new AtomicInteger();
436 updater
.scheduleWithFixedDelay(new Runnable() {
439 // Obtain a copy of the contents queue
440 final HashMap
<Display3D
,Vector
<Content
>> m
= new HashMap
<Display3D
,Vector
<Content
>>();
441 synchronized (contents
) {
445 if (m
.isEmpty()) return;
446 // Add all to the corresponding Display3D
447 for (final Map
.Entry
<Display3D
,Vector
<Content
>> e
: m
.entrySet()) {
448 e
.getKey().universe
.addContentLater(e
.getValue());
449 counter
.getAndAdd(e
.getValue().size());
451 Utils
.showStatus(new StringBuilder("Rendered ").append(counter
.get()).append('/').append(hs
.size()).toString());
453 }, 100, 4000, TimeUnit
.MILLISECONDS
);
455 // A list of all generated Content objects
456 final Vector
<Future
<Content
>> list
= new Vector
<Future
<Content
>>();
458 for (final Iterator
<ProjectThing
> it
= hs
.iterator(); it
.hasNext(); ) {
459 // obtain the Displayable object under the node
460 final ProjectThing child
= it
.next();
462 final Object obc
= child
.getObject();
463 final Displayable displ
= obc
.getClass().equals(String
.class) ?
null : (Displayable
)obc
;
465 if (displ
.getClass().equals(Profile
.class)) {
466 //Utils.log("Display3D can't handle Bezier profiles at the moment.");
467 // handled by profile_list Thing
470 if (!displ
.isVisible()) {
471 Utils
.log("Skipping non-visible node " + displ
);
475 // obtain the containing LayerSet
477 if (null != displ
) d3d
= Display3D
.get(displ
.getLayerSet());
478 else if (child
.getType().equals("profile_list")) {
479 final ArrayList
<ProjectThing
> al_children
= child
.getChildren();
480 if (null == al_children
|| 0 == al_children
.size()) continue;
481 // else, get the first Profile and get its LayerSet
482 d3d
= Display3D
.get(((Displayable
)((ProjectThing
)al_children
.get(0)).getObject()).getLayerSet());
484 Utils
.log("Don't know what to do with node " + child
);
488 Utils
.log("Could not get a proper 3D display for node " + displ
);
489 return null; // java3D not installed most likely
493 synchronized (d3d
.ht_pt_meshes
) {
494 already
= d3d
.ht_pt_meshes
.containsKey(child
);
497 if (child
.getObject() instanceof ZDisplayable
) {
498 Utils
.log("Updating 3D view of " + child
.getObject());
500 Utils
.log("Updating 3D view of " + child
);
504 list
.add(d3d
.executors
.submit(new Callable
<Content
>() {
506 public Content
call() {
509 c
= d3d
.createMesh(child
, displ
, resample
).call();
511 synchronized (contents
) {
512 vc
= contents
.get(d3d
);
513 if (null == vc
) vc
= new Vector
<Content
>();
514 contents
.put(d3d
, vc
);
517 } catch (final Exception e
) {
524 // If it's the last one:
526 // Add the concluding task, that waits on all and shuts down the scheduler
527 d3d
.executors
.submit(new Runnable() {
530 // Wait until all are done
531 for (final Future
<Content
> c
: list
) {
534 } catch (final Throwable t
) {
539 // Shutdown scheduler and execute remaining tasks
540 for (final Runnable r
: updater
.shutdownNow()) {
543 } catch (final Throwable e
) {
548 Utils
.showStatus(new StringBuilder("Done rendering ").append(counter
.get()).append('/').append(hs
.size()).toString());
558 if (wait
&& -1 != resample
) {
561 } catch (final Throwable t
) {
569 static public void resetView(final LayerSet ls
) {
570 final Display3D d3d
= ht_layer_sets
.get(ls
);
571 if (null != d3d
) d3d
.universe
.resetView();
574 static public void showOrthoslices(final Patch p
) {
575 final Display3D d3d
= get(p
.getLayerSet());
576 d3d
.adjustResampling();
577 //d3d.universe.resetView();
578 final String title
= makeTitle(p
) + " orthoslices";
580 d3d
.universe
.removeContent(title
);
581 final PatchStack ps
= p
.makePatchStack();
582 final ImagePlus imp
= get8BitStack(ps
);
583 final Content ct
= d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
584 setTransform(ct
, ps
.getPatch(0));
585 ct
.setLocked(true); // locks the added content
588 static public void showVolume(final Patch p
) {
589 final Display3D d3d
= get(p
.getLayerSet());
590 d3d
.adjustResampling();
591 //d3d.universe.resetView();
592 final String title
= makeTitle(p
) + " volume";
594 d3d
.universe
.removeContent(title
);
595 final PatchStack ps
= p
.makePatchStack();
596 final ImagePlus imp
= get8BitStack(ps
);
597 final Content ct
= d3d
.universe
.addVoltex(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
598 setTransform(ct
, ps
.getPatch(0));
599 ct
.setLocked(true); // locks the added content
602 static private void setTransform(final Content ct
, final Patch p
) {
603 final double[] a
= new double[6];
604 p
.getAffineTransform().getMatrix(a
);
605 final Calibration cal
= p
.getLayerSet().getCalibration();
606 // a is: m00 m10 m01 m11 m02 m12
607 // d expects: m01 m02 m03 m04, m11 m12 ...
608 ct
.applyTransform(new Transform3D(new double[]{a
[0], a
[2], 0, a
[4] * cal
.pixelWidth
,
609 a
[1], a
[3], 0, a
[5] * cal
.pixelWidth
,
610 0, 0, 1, p
.getLayer().getZ() * cal
.pixelWidth
,
614 static public void showOrthoslices(final ImagePlus imp
, final String title
, final int wx
, final int wy
, final float scale2D
, final Layer first
) {
615 final Display3D d3d
= get(first
.getParent());
616 d3d
.universe
.removeContent(title
);
617 final Content ct
= d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, 1);
618 final Calibration cal
= imp
.getCalibration();
619 final Transform3D t
= new Transform3D(new double[]{1, 0, 0, wx
* cal
.pixelWidth
* scale2D
,
620 0, 1, 0, wy
* cal
.pixelHeight
* scale2D
,
621 0, 0, scale2D
, first
.getZ() * cal
.pixelWidth
* scale2D
, // not pixelDepth!
623 // why scale2D has to be there at all reflects a horrible underlying setting of the calibration, plus of the scaling in the Display3D.
625 ct
.applyTransform(t
);
629 /** Returns a stack suitable for the ImageJ 3D Viewer, either 8-bit gray or 8-bit color.
630 * If the PatchStack is already of the right type, it is returned,
631 * otherwise a copy is made in the proper type.
633 static private ImagePlus
get8BitStack(final PatchStack ps
) {
634 switch (ps
.getType()) {
635 case ImagePlus
.COLOR_RGB
:
636 // convert stack to 8-bit color
637 return ps
.createColor256Copy();
638 case ImagePlus
.GRAY16
:
639 case ImagePlus
.GRAY32
:
640 // convert stack to 8-bit
641 return ps
.createGray8Copy();
642 case ImagePlus
.GRAY8
:
643 case ImagePlus
.COLOR_256
:
646 Utils
.logAll("Cannot handle stacks of type: " + ps
.getType());
651 /** Considers there is only one Display3D for each LayerSet. */
652 static public void remove(final ProjectThing pt
) {
653 if (null == pt
) return;
654 if (null == pt
.getObject()) return;
655 final Object ob
= pt
.getObject();
656 if (!(ob
instanceof Displayable
)) return;
657 final Displayable displ
= (Displayable
)ob
;
658 final Display3D d3d
= ht_layer_sets
.get(displ
.getLayerSet()); // TODO profile_list is going to fail here
660 // there is no Display3D showing the pt to remove
661 //Utils.log2("No Display3D contains ProjectThing: " + pt);
665 synchronized (d3d
.ht_pt_meshes
) {
666 name
= d3d
.ht_pt_meshes
.remove(pt
);
669 Utils
.log2("No mesh contained within " + d3d
+ " for ProjectThing " + pt
);
672 d3d
.universe
.removeContent(name
);
676 /** Creates a mesh for the given Displayable in a separate Thread, and adds it to the universe. */
677 private Future
<Content
> addMesh(final ProjectThing pt
, final Displayable displ
, final int resample
) {
678 return executors
.submit(new Callable
<Content
>() {
680 public Content
call() {
682 // 1 - Create content
683 final Callable
<Content
> c
= createMesh(pt
, displ
, resample
);
684 if (null == c
) return null;
685 final Content content
= c
.call();
686 if (null == content
) return null;
687 final String title
= content
.getName();
688 // 2 - Remove from universe any content of the same title
689 if (universe
.contains(title
)) {
690 universe
.removeContent(title
);
692 // 3 - Add to universe, and wait
693 universe
.addContentLater(content
).get();
697 } catch (final Exception e
) {
705 static private final String
makeProfileListTitle(final ProjectThing pt
) {
707 final Object ob
= pt
.getParent().getTitle();
708 if (null == ob
|| ob
.equals(pt
.getParent().getType())) title
= pt
.toString() + " #" + pt
.getId(); // Project.getMeaningfulTitle can't handle profile_list properly
709 else title
= ob
.toString() + " /[" + pt
.getParent().getType() + "]/[profile_list] #" + pt
.getId();
713 /** Remove all basic type children contained in {@param pt} and its children, recursively.
717 static public void removeFrom3D(final ProjectThing pt
) {
718 final HashSet
<ProjectThing
> hs
= pt
.findBasicTypeChildren();
719 if (null == hs
|| 0 == hs
.size()) {
720 Utils
.logAll("Nothing to remove from 3D.");
723 // Ignore Profile instances ("profile_list" takes care of them)
724 for (final ProjectThing child
: hs
) {
725 if (child
.getByType().equals("profile")) continue;
727 LayerSet lset
= null;
728 if (child
.getType().equals("profile_list")) {
729 if (!child
.hasChildren()) continue;
730 for (final ProjectThing p
: child
.getChildren()) {
731 if (null != p
.getObject() && p
.getObject() instanceof Profile
) {
732 lset
= ((Displayable
)p
.getObject()).getLayerSet();
736 if (null == lset
) continue;
737 } else if (child
.getType().equals("profile")) {
738 // Taken care of by "profile list"
741 final Displayable d
= (Displayable
)child
.getObject();
743 Utils
.log("Null object for ProjectThing " + child
);
746 lset
= d
.getLayerSet();
749 Utils
.log("No LayerSet found for " + child
);
752 final Display3D d3d
= getDisplay(lset
);
754 Utils
.log("No Display 3D found for " + child
);
755 continue; // no Display3D window open
757 final String oldTitle
= d3d
.ht_pt_meshes
.remove(child
);
758 if (null == oldTitle
) {
759 Utils
.log("Could not find a title for " + child
);
762 Utils
.log("Removed from 3D view: " + oldTitle
);
763 d3d
.getUniverse().removeContent(oldTitle
);
768 /** Returns a function that returns a Content object.
769 * Does NOT add the Content to the universe; it merely creates it. */
770 public Callable
<Content
> createMesh(final ProjectThing pt
, final Displayable displ
, final int resample
) {
771 final double scale
= 1.0; // OBSOLETE
772 return new Callable
<Content
>() {
774 public Content
call() {
775 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
778 // 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.)
779 final List
<Point3f
> triangles
;
780 //boolean no_culling_ = false;
783 final boolean line_mesh
;
784 final int line_mesh_mode
;
788 line_mesh_mode
= Integer
.MAX_VALUE
;
790 c
= displ
.getClass();
791 line_mesh
= Tree
.class.isAssignableFrom(c
) || Polyline
.class == c
;
792 if (Tree
.class.isAssignableFrom(c
)) line_mesh_mode
= CustomLineMesh
.PAIRWISE
;
793 else if (Polyline
.class == c
) line_mesh_mode
= CustomLineMesh
.CONTINUOUS
;
794 else line_mesh_mode
= Integer
.MAX_VALUE
; // disabled
797 List
<Point3f
> extra_triangles
= null;
798 List
<Color3f
> triangle_colors
= null,
799 extra_triangle_colors
= null;
802 if (displ
instanceof AreaContainer
) {
803 if (-1 == resample
) rs
= Display3D
.this.resample
= adjustResampling(); // will adjust this.resample, and return it (even if it's a default value)
804 else rs
= Display3D
.this.resample
;
806 if (AreaList
.class == c
) {
807 triangles
= ((AreaList
)displ
).generateTriangles(scale
, rs
);
808 //triangles = removeNonManifold(triangles);
809 } else if (Ball
.class == c
) {
810 final double[][][] globe
= Ball
.generateGlobe(12, 12);
811 triangles
= ((Ball
)displ
).generateTriangles(scale
, globe
);
812 } else if (displ
instanceof Line3D
) {
814 // adjustResampling(); // fails horribly, needs first to correct mesh-generation code
815 triangles
= ((Line3D
)displ
).generateTriangles(scale
, 12, 1 /*Display3D.this.resample*/);
816 } else if (displ
instanceof Tree
<?
>) {
817 // A 3D wire skeleton, using CustomLineMesh
818 final Tree
.MeshData skeleton
= ((Tree
<?
>)displ
).generateSkeleton(scale
, 12, 1);
819 triangles
= skeleton
.verts
;
820 triangle_colors
= skeleton
.colors
;
821 if (displ
instanceof Treeline
) {
822 final Tree
.MeshData tube
= ((Treeline
)displ
).generateMesh(scale
, 12);
823 extra_triangles
= tube
.verts
;
824 extra_triangle_colors
= tube
.colors
;
825 } else if (displ
instanceof AreaTree
) {
826 final Tree
.MeshData mesh
= ((AreaTree
)displ
).generateMesh(scale
, rs
);
827 extra_triangles
= mesh
.verts
;
828 extra_triangle_colors
= mesh
.colors
;
830 if (null != extra_triangles
&& extra_triangles
.isEmpty()) extra_triangles
= null; // avoid issues with MultiMesh
831 } else if (Connector
.class == c
) {
832 final Tree
.MeshData octopus
= ((Connector
)displ
).generateMesh(scale
, 12);
833 triangles
= octopus
.verts
;
834 triangle_colors
= octopus
.colors
;
835 } else if (null == displ
&& pt
.getType().equals("profile_list")) {
836 triangles
= Profile
.generateTriangles(pt
, scale
);
837 //no_culling_ = true;
839 Utils
.log("Unrecognized type for 3D mesh generation: " + (null != displ ? displ
.getClass() : null) + " : " + displ
);
843 if (null == triangles
) {
844 Utils
.log("Some error ocurred: can't create triangles for " + displ
);
847 if (0 == triangles
.size()) {
848 Utils
.log2("Skipping empty mesh for " + displ
.getTitle());
851 if (!line_mesh
&& 0 != triangles
.size() % 3) {
852 Utils
.log2("Skipping non-multiple-of-3 vertices list generated for " + displ
.getTitle());
860 color
= displ
.getColor();
861 alpha
= displ
.getAlpha();
862 title
= makeTitle(displ
);
863 } else if (pt
.getType().equals("profile_list")) {
864 // for profile_list: get from the first (what a kludge; there should be a ZDisplayable ProfileList object)
865 final Object obp
= ((ProjectThing
)pt
.getChildren().get(0)).getObject();
866 if (null == obp
) return null;
867 final Displayable di
= (Displayable
)obp
;
868 color
= di
.getColor();
869 alpha
= di
.getAlpha();
870 title
= makeProfileListTitle(pt
);
872 title
= pt
.toString() + " #" + pt
.getId();
877 // 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.
878 final boolean no_culling
= true; // for ALL
883 final Color3f c3
= new Color3f(color
);
885 // If it exists, remove and add as new:
886 universe
.removeContent(title
);
891 //ct = universe.createContent(new CustomLineMesh(triangles, line_mesh_mode, c3, 0), title);
892 cm
= new CustomLineMesh(triangles
, line_mesh_mode
, c3
, 0);
893 } else if (no_culling
) {
894 // create a mesh with the same color and zero transparency (that is, full opacity)
895 final CustomTriangleMesh mesh
= new CustomTriangleMesh(triangles
, c3
, 0);
896 // Set mesh properties for double-sided triangles
897 final PolygonAttributes pa
= mesh
.getAppearance().getPolygonAttributes();
898 pa
.setCullFace(PolygonAttributes
.CULL_NONE
);
899 pa
.setBackFaceNormalFlip(true);
901 // After setting properties, add to the viewer
902 //ct = universe.createContent(mesh, title);
905 //ct = universe.createContent(new CustomTriangleMesh(triangles, c3, 0), title);
906 cm
= new CustomTriangleMesh(triangles
, c3
, 0);
909 if (null != triangle_colors
) cm
.setColor(triangle_colors
);
911 //if (null == cm) return null;
913 if (null == extra_triangles
|| 0 == extra_triangles
.size()) {
914 ct
= universe
.createContent(cm
, title
);
916 final CustomTriangleMesh extra
= new CustomTriangleMesh(extra_triangles
, c3
, 0);
917 if (null != extra_triangle_colors
) {
918 // Set mesh properties for double-sided triangles
919 final PolygonAttributes pa
= extra
.getAppearance().getPolygonAttributes();
920 pa
.setCullFace(PolygonAttributes
.CULL_NONE
);
921 pa
.setBackFaceNormalFlip(true);
922 extra
.setColor(extra_triangle_colors
);
924 ct
= universe
.createContent(new CustomMultiMesh(Arrays
.asList(new CustomMesh
[]{cm
, extra
})), title
);
927 // Set general content properties
928 ct
.setTransparency(1f
- alpha
);
929 // Default is unlocked (editable) transformation; set it to locked:
932 // register mesh title
933 synchronized (ht_pt_meshes
) {
934 ht_pt_meshes
.put(pt
, ct
.getName());
937 } catch (final Throwable e
) {
938 Utils
.logAll("Mesh generation failed for \"" + title
+ "\" from " + pt
);
943 Utils
.log2(pt
.toString() + " n points: " + triangles
.size());
947 } catch (final Exception e
) {
955 static public class VectorStringContent
{
961 public VectorStringContent(final VectorString3D vs
, final String title
, final Color color
, final double[] widths
, final float alpha
){
965 this.widths
= widths
;
968 public Content
asContent(final Display3D d3d
) {
969 double[] wi
= widths
;
970 if (null == widths
) {
971 wi
= new double[vs
.getPoints(0).length
];
972 Arrays
.fill(wi
, 2.0);
973 } else if (widths
.length
!= vs
.length()) {
974 Utils
.log("ERROR: widths.length != VectorString3D.length()");
977 float transp
= 1 - alpha
;
978 if (transp
< 0) transp
= 0;
979 if (transp
> 1) transp
= 1;
981 Utils
.log("WARNING: adding a 3D object fully transparent.");
983 final List
<Point3f
> triangles
= Pipe
.generateTriangles(Pipe
.makeTube(vs
.getPoints(0), vs
.getPoints(1), vs
.getPoints(2), wi
, 1, 12, null), d3d
.scale
);
984 final Content ct
= d3d
.universe
.createContent(new CustomTriangleMesh(triangles
, new Color3f(color
), 0), title
);
985 ct
.setTransparency(transp
);
991 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
992 static public Future
<Collection
<Future
<Content
>>> addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
) {
993 return addMesh(ref_ls
, vs
, title
, color
, null, 1.0f
);
996 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
997 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
) {
998 final List
<Content
> col
= new ArrayList
<Content
>();
999 final Display3D d3d
= Display3D
.get(ref_ls
);
1000 col
.add(new VectorStringContent(vs
, title
, color
, widths
, alpha
).asContent(d3d
));
1001 return d3d
.addContent(col
);
1004 static public Future
<Collection
<Future
<Content
>>> show(final LayerSet ref_ls
, final Collection
<Content
> col
) {
1005 final Display3D d3d
= get(ref_ls
);
1006 return d3d
.addContent(col
);
1009 public Future
<Collection
<Future
<Content
>>> addContent(final Collection
<Content
> col
) {
1011 final FutureTask
<Collection
<Future
<Content
>>> fu
= new FutureTask
<Collection
<Future
<Content
>>>(new Callable
<Collection
<Future
<Content
>>>() {
1013 public Collection
<Future
<Content
>> call() {
1014 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
1016 return universe
.addContentLater(col
);
1017 } catch (final Throwable e
) {
1023 launchers
.submit(new Runnable() { @Override
1025 executors
.submit(fu
);
1031 public Future
<Content
> addContent(final Content c
) {
1032 final FutureTask
<Content
> fu
= new FutureTask
<Content
>(new Callable
<Content
>() {
1034 public Content
call() {
1035 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
1037 return universe
.addContentLater(c
).get();
1038 } catch (final Throwable e
) {
1045 launchers
.submit(new Runnable() { @Override
1047 executors
.submit(fu
);
1053 static public final int estimateResamplingFactor(final LayerSet ls
, final double width
, final double height
) {
1054 final int max_dimension
= ls
.getPixelsMaxDimension();
1055 return (int)(DEFAULT_RESAMPLE
/ (Math
.max(width
, height
) > max_dimension ?
1056 max_dimension
/ Math
.max(width
, height
)
1060 /** Estimate a scaling factor, to be used as a multiplier of the suggested default resampling. */
1061 private final int estimateResamplingFactor() {
1062 return estimateResamplingFactor(layer_set
, width
, height
);
1065 // This method has the exclusivity in adjusting the resampling value, and it also returns it.
1066 synchronized private final int adjustResampling() {
1067 if (resample
> 0) return resample
;
1068 final GenericDialog gd
= new GenericDialog("Resample");
1069 final int default_resample
= estimateResamplingFactor();
1070 gd
.addSlider("Resample: ", 1, Math
.max(default_resample
, 100), -1 != resample ? resample
: default_resample
);
1072 if (gd
.wasCanceled()) {
1073 resample
= -1 != resample ? resample
: default_resample
; // current or default value
1076 resample
= ((java
.awt
.Scrollbar
)gd
.getSliders().get(0)).getValue();
1080 /** Checks if there is any Display3D instance currently showing the given Displayable. */
1081 static public boolean isDisplayed(final Displayable d
) {
1082 if (null == d
) return false;
1083 final String title
= makeTitle(d
);
1084 for (final Display3D d3d
: ht_layer_sets
.values()) {
1085 if (null != d3d
.universe
.getContent(title
)) return true;
1087 if (d
.getClass() == Profile
.class) {
1088 if (null != getProfileContent(d
)) return true;
1093 /** 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. */
1094 static public Content
getProfileContent(final Displayable d
) {
1095 if (null == d
) return null;
1096 if (d
.getClass() != Profile
.class) return null;
1097 final Display3D d3d
= get(d
.getLayer().getParent());
1098 if (null == d3d
) return null;
1099 ProjectThing pt
= d
.getProject().findProjectThing(d
);
1100 if (null == pt
) return null;
1101 pt
= (ProjectThing
) pt
.getParent();
1102 return d3d
.universe
.getContent(new StringBuilder(pt
.toString()).append(" #").append(pt
.getId()).toString());
1105 static public Future
<Boolean
> setColor(final Displayable d
, final Color color
) {
1106 final Display3D d3d
= getDisplay(d
.getLayer().getParent());
1107 if (null == d3d
) return null; // no 3D displays open
1108 return d3d
.executors
.submit(new Callable
<Boolean
>() { @Override
1109 public Boolean
call() {
1110 Content content
= d3d
.universe
.getContent(makeTitle(d
));
1111 if (null == content
) content
= getProfileContent(d
);
1112 if (null != content
) {
1113 content
.setColor(new Color3f(color
));
1120 static public Future
<Boolean
> setTransparency(final Displayable d
, final float alpha
) {
1121 if (null == d
) return null;
1122 final Layer layer
= d
.getLayer();
1123 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
1124 final Display3D d3d
= ht_layer_sets
.get(layer
.getParent());
1125 if (null == d3d
) return null;
1126 return d3d
.executors
.submit(new Callable
<Boolean
>() { @Override
1127 public Boolean
call() {
1128 String title
= makeTitle(d
);
1129 Content content
= d3d
.universe
.getContent(title
);
1130 if (null == content
) content
= getProfileContent(d
);
1131 if (null != content
) content
.setTransparency(1 - alpha
);
1132 else if (null == content
&& d
.getClass().equals(Patch
.class)) {
1133 final Patch pa
= (Patch
)d
;
1135 title
= pa
.getProject().getLoader().getFileName(pa
);
1136 for (final Display3D dd
: ht_layer_sets
.values()) {
1137 for (final Iterator
<?
> cit
= dd
.universe
.getContents().iterator(); cit
.hasNext(); ) {
1138 final Content c
= (Content
)cit
.next();
1139 if (c
.getName().startsWith(title
)) {
1140 c
.setTransparency(1 - alpha
);
1141 // no break, since there could be a volume and an orthoslice
1151 static public String
makeTitle(final Displayable d
) {
1152 return d
.getProject().getMeaningfulTitle(d
) + " #" + d
.getId();
1154 static public String
makeTitle(final Patch p
) {
1155 return new File(p
.getProject().getLoader().getAbsolutePath(p
)).getName()
1156 + " #" + p
.getProject().getLoader().getNextId();
1159 /** Remake the mesh for the Displayable in a separate Thread, if it's included in a Display3D
1160 * (otherwise returns null). */
1161 static public Future
<Content
> update(final Displayable d
) {
1162 final Layer layer
= d
.getLayer();
1163 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
1164 final Display3D d3d
= ht_layer_sets
.get(layer
.getParent());
1165 if (null == d3d
) return null;
1166 return d3d
.addMesh(d
.getProject().findProjectThing(d
), d
, d3d
.resample
);
1170 static public final double computeTriangleArea() {
1171 return 0.5 * Math.sqrt(Math.pow(xA*yB + xB*yC + xC*yA, 2) +
1172 Math.pow(yA*zB + yB*zC + yC*zA, 2) +
1173 Math.pow(zA*xB + zB*xC + zC*xA, 2));
1177 static public final boolean contains(final LayerSet ls
, final String title
) {
1178 final Display3D d3d
= getDisplay(ls
);
1179 if (null == d3d
) return false;
1180 return null != d3d
.universe
.getContent(title
);
1183 static public void destroy() {
1184 launchers
.shutdownNow();
1187 static public void init() {
1188 if (launchers
.isShutdown()) {
1189 launchers
= Utils
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors(), "Display3D-launchers");
1193 /** Creates a calibrated sphere to represent a point at LayerSet pixel coordinates wx, wy, wz, with radius wr.*/
1194 public List
<Point3f
> createFatPoint(final double wx
, final double wy
, final double wz
, final double wr
, final Calibration cal
) {
1195 final double[][][] globe
= Ball
.generateGlobe(12, 12);
1196 final int sign
= cal
.pixelDepth
< 0 ?
-1 : 1;
1197 for (int z
=0; z
<globe
.length
; z
++) {
1198 for (int k
=0; k
<globe
[0].length
; k
++) {
1199 globe
[z
][k
][0] = (globe
[z
][k
][0] * wr
+ wx
) * scale
* cal
.pixelWidth
;
1200 globe
[z
][k
][1] = (globe
[z
][k
][1] * wr
+ wy
) * scale
* cal
.pixelHeight
;
1201 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.
1204 final ArrayList
<Point3f
> list
= new ArrayList
<Point3f
>();
1205 // create triangular faces and add them to the list
1206 for (int z
=0; z
<globe
.length
-1; z
++) { // the parallels
1207 for (int k
=0; k
<globe
[0].length
-1; k
++) { // meridian points
1208 // half quadrant (a triangle)
1209 list
.add(new Point3f((float)globe
[z
][k
][0], (float)globe
[z
][k
][1], (float)globe
[z
][k
][2]));
1210 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]));
1211 list
.add(new Point3f((float)globe
[z
+1][k
][0], (float)globe
[z
+1][k
][1], (float)globe
[z
+1][k
][2]));
1212 // the other half quadrant
1213 list
.add(new Point3f((float)globe
[z
][k
][0], (float)globe
[z
][k
][1], (float)globe
[z
][k
][2]));
1214 list
.add(new Point3f((float)globe
[z
][k
+1][0], (float)globe
[z
][k
+1][1], (float)globe
[z
][k
+1][2]));
1215 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]));
1221 /** Expects uncalibrated wx,wy,wz, (i.e. pixel values), to be calibrated by @param ls calibration. */
1222 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
) {
1223 final Display3D d3d
= Display3D
.get(ls
);
1224 d3d
.universe
.removeContent(title
);
1225 final Content ct
= d3d
.universe
.createContent(new CustomTriangleMesh(d3d
.createFatPoint(wx
, wy
, wz
, wr
, ls
.getCalibrationCopy()), new Color3f(color
), 0), title
);
1227 return d3d
.addContent(ct
);