1 package ini
.trakem2
.display
;
3 import customnode
.CustomLineMesh
;
4 import customnode
.CustomMesh
;
5 import customnode
.CustomMultiMesh
;
6 import customnode
.CustomTriangleMesh
;
8 import ij
.gui
.GenericDialog
;
9 import ij
.measure
.Calibration
;
11 import ij3d
.Image3DUniverse
;
12 import ij3d
.ImageWindow3D
;
13 import ij3d
.UniverseListener
;
14 import ini
.trakem2
.display
.d3d
.ControlClickBehavior
;
15 import ini
.trakem2
.display
.d3d
.Display3DGUI
;
16 import ini
.trakem2
.imaging
.PatchStack
;
17 import ini
.trakem2
.tree
.ProjectThing
;
18 import ini
.trakem2
.utils
.IJError
;
19 import ini
.trakem2
.utils
.Utils
;
20 import ini
.trakem2
.vector
.VectorString3D
;
22 import java
.awt
.Color
;
23 import java
.awt
.Cursor
;
24 import java
.awt
.event
.WindowAdapter
;
25 import java
.awt
.event
.WindowEvent
;
27 import java
.util
.ArrayList
;
28 import java
.util
.Arrays
;
29 import java
.util
.Collection
;
30 import java
.util
.Collections
;
31 import java
.util
.HashMap
;
32 import java
.util
.HashSet
;
33 import java
.util
.Hashtable
;
34 import java
.util
.Iterator
;
35 import java
.util
.List
;
37 import java
.util
.Vector
;
38 import java
.util
.concurrent
.Callable
;
39 import java
.util
.concurrent
.ExecutorService
;
40 import java
.util
.concurrent
.Executors
;
41 import java
.util
.concurrent
.Future
;
42 import java
.util
.concurrent
.FutureTask
;
43 import java
.util
.concurrent
.ScheduledExecutorService
;
44 import java
.util
.concurrent
.TimeUnit
;
45 import java
.util
.concurrent
.atomic
.AtomicInteger
;
47 import javax
.media
.j3d
.PolygonAttributes
;
48 import javax
.media
.j3d
.Transform3D
;
49 import javax
.media
.j3d
.View
;
50 import javax
.vecmath
.Color3f
;
51 import javax
.vecmath
.Point3f
;
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 Display3DGUI gui
= new Display3DGUI(this.universe
);
107 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(View arg0
) {
137 public void transformationStarted(View arg0
) {
140 public void transformationFinished(View arg0
) {
143 public void contentSelected(Content arg0
) {
144 // TODO could select in TrakEM2's Display
147 public void contentRemoved(Content arg0
) {
148 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(Content arg0
) {
162 public void contentAdded(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(Display3D d3d
, LayerSet ls
) {
269 public void windowClosing(WindowEvent we
) {
270 //Utils.log2("Display3D.windowClosing");
271 d3d
.executors
.shutdownNow();
272 /*Object ob =*/ ht_layer_sets
.remove(ls
);
274 Utils.log2("Removed Display3D from table for LayerSet " + ls);
277 public void windowClosed(WindowEvent we
) {
278 //Utils.log2("Display3D.windowClosed");
279 ht_layer_sets
.remove(ls
);
283 static private boolean check_j3d
= true;
284 static private boolean has_j3d_3dviewer
= false;
286 static private boolean hasLibs() {
290 Class
.forName("javax.vecmath.Point3f");
291 has_j3d_3dviewer
= true;
292 } catch (ClassNotFoundException cnfe
) {
293 Utils
.log("Java 3D not installed.");
294 has_j3d_3dviewer
= false;
298 Class
.forName("ij3d.ImageWindow3D");
299 has_j3d_3dviewer
= true;
300 } catch (ClassNotFoundException cnfe
) {
301 Utils
.log("3D Viewer not installed.");
302 has_j3d_3dviewer
= false;
306 return has_j3d_3dviewer
;
309 /** Get an existing Display3D for the given LayerSet, or create a new one for it (and cache it). */
310 static public Display3D
get(final LayerSet ls
) {
311 synchronized (htlock
) {
314 if (!hasLibs()) return null;
316 Display3D d3d
= ht_layer_sets
.get(ls
);
317 if (null != d3d
) return d3d
;
319 final boolean[] done
= new boolean[]{false};
320 javax
.swing
.SwingUtilities
.invokeAndWait(new Runnable() { public void run() {
321 ht_layer_sets
.put(ls
, new Display3D(ls
));
324 // wait to avoid crashes in amd64
325 // try { Thread.sleep(500); } catch (Exception e) {}
327 try { Thread
.sleep(10); } catch (Exception e
) {}
329 return ht_layer_sets
.get(ls
);
330 } catch (Exception e
) {
337 /** Get the Display3D instance that exists for the given LayerSet, if any. */
338 static public Display3D
getDisplay(final LayerSet ls
) {
339 return ht_layer_sets
.get(ls
);
342 static public void setWaitingCursor() {
343 Utils
.invokeLater(new Runnable() { public void run() {
344 for (Display3D d3d
: ht_layer_sets
.values()) {
345 d3d
.universe
.getWindow().setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
350 static public void doneWaiting() {
351 Utils
.invokeLater(new Runnable() { public void run() {
352 for (Display3D d3d
: ht_layer_sets
.values()) {
353 d3d
.universe
.getWindow().setCursor(Cursor
.getDefaultCursor());
358 static public Future
<Vector
<Future
<Content
>>> show(ProjectThing pt
) {
359 return show(pt
, false, -1);
362 static public void showAndResetView(final ProjectThing pt
) {
363 launchers
.submit(new Runnable() {
366 Future
<Vector
<Future
<Content
>>> fu
= show(pt
, true, -1);
367 Vector
<Future
<Content
>> vc
;
369 vc
= fu
.get(); // wait until done
370 } catch (Exception e
) {
374 for (Future
<Content
> fc
: vc
) {
376 Content c
= fc
.get();
377 if (null == c
) continue;
378 ArrayList
<Display3D
> d3ds
= new ArrayList
<Display3D
>();
379 synchronized (ht_layer_sets
) {
380 d3ds
.addAll(ht_layer_sets
.values());
382 /* // Disabled, it's annoying
383 for (Display3D d3d : d3ds) {
385 if (d3d.universe.getContents().contains(c)) {
386 d3d.universe.resetView(); // reset the absolute center
387 d3d.universe.adjustView(); // zoom out to bring all elements in universe within view
391 } catch (Exception e
) {
395 Utils
.logAll("Reset 3D view if not within field of view!");
400 /** 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. */
401 static public Future
<Vector
<Future
<Content
>>> show(final ProjectThing pt
, final boolean wait
, final int resample
) {
402 if (null == pt
) return null;
404 Future
<Vector
<Future
<Content
>>> fu
= launchers
.submit(new Callable
<Vector
<Future
<Content
>>>() {
405 public Vector
<Future
<Content
>> call() {
407 // Scan the given ProjectThing for 3D-viewable items
408 // So: find arealist, pipe, ball, and profile_list types
409 final HashSet
<ProjectThing
> hs
= pt
.findBasicTypeChildren();
410 if (null == hs
|| 0 == hs
.size()) {
411 Utils
.logAll("Node " + pt
+ " does not contain any 3D-displayable children");
415 // Remove profile if it lives under a profile_list
416 for (Iterator
<ProjectThing
> it
= hs
.iterator(); it
.hasNext(); ) {
417 ProjectThing pt
= it
.next();
418 if (null != pt
.getObject() && pt
.getObject().getClass() == Profile
.class && pt
.getParent().getType().equals("profile_list")) {
425 // Start new scheduler to publish/add meshes to the 3D Viewer every 5 seconds and when done.
426 final Hashtable
<Display3D
,Vector
<Content
>> contents
= new Hashtable
<Display3D
,Vector
<Content
>>();
427 final ScheduledExecutorService updater
= Executors
.newScheduledThreadPool(1);
428 final AtomicInteger counter
= new AtomicInteger();
429 updater
.scheduleWithFixedDelay(new Runnable() {
431 // Obtain a copy of the contents queue
432 HashMap
<Display3D
,Vector
<Content
>> m
= new HashMap
<Display3D
,Vector
<Content
>>();
433 synchronized (contents
) {
437 if (m
.isEmpty()) return;
438 // Add all to the corresponding Display3D
439 for (Map
.Entry
<Display3D
,Vector
<Content
>> e
: m
.entrySet()) {
440 e
.getKey().universe
.addContentLater(e
.getValue());
441 counter
.getAndAdd(e
.getValue().size());
443 Utils
.showStatus(new StringBuilder("Rendered ").append(counter
.get()).append('/').append(hs
.size()).toString());
445 }, 100, 4000, TimeUnit
.MILLISECONDS
);
447 // A list of all generated Content objects
448 final Vector
<Future
<Content
>> list
= new Vector
<Future
<Content
>>();
450 for (final Iterator
<ProjectThing
> it
= hs
.iterator(); it
.hasNext(); ) {
451 // obtain the Displayable object under the node
452 final ProjectThing child
= it
.next();
454 Object obc
= child
.getObject();
455 final Displayable displ
= obc
.getClass().equals(String
.class) ?
null : (Displayable
)obc
;
457 if (displ
.getClass().equals(Profile
.class)) {
458 //Utils.log("Display3D can't handle Bezier profiles at the moment.");
459 // handled by profile_list Thing
462 if (!displ
.isVisible()) {
463 Utils
.log("Skipping non-visible node " + displ
);
467 // obtain the containing LayerSet
469 if (null != displ
) d3d
= Display3D
.get(displ
.getLayerSet());
470 else if (child
.getType().equals("profile_list")) {
471 ArrayList
<ProjectThing
> al_children
= child
.getChildren();
472 if (null == al_children
|| 0 == al_children
.size()) continue;
473 // else, get the first Profile and get its LayerSet
474 d3d
= Display3D
.get(((Displayable
)((ProjectThing
)al_children
.get(0)).getObject()).getLayerSet());
476 Utils
.log("Don't know what to do with node " + child
);
480 Utils
.log("Could not get a proper 3D display for node " + displ
);
481 return null; // java3D not installed most likely
485 synchronized (d3d
.ht_pt_meshes
) {
486 already
= d3d
.ht_pt_meshes
.containsKey(child
);
489 if (child
.getObject() instanceof ZDisplayable
) {
490 Utils
.log("Updating 3D view of " + child
.getObject());
492 Utils
.log("Updating 3D view of " + child
);
496 list
.add(d3d
.executors
.submit(new Callable
<Content
>() {
497 public Content
call() {
500 c
= d3d
.createMesh(child
, displ
, resample
).call();
502 synchronized (contents
) {
503 vc
= contents
.get(d3d
);
504 if (null == vc
) vc
= new Vector
<Content
>();
505 contents
.put(d3d
, vc
);
508 } catch (Exception e
) {
515 // If it's the last one:
517 // Add the concluding task, that waits on all and shuts down the scheduler
518 d3d
.executors
.submit(new Runnable() {
520 // Wait until all are done
521 for (Future
<Content
> c
: list
) {
524 } catch (Throwable t
) {
529 // Shutdown scheduler and execute remaining tasks
530 for (Runnable r
: updater
.shutdownNow()) {
533 } catch (Throwable e
) {
538 Utils
.showStatus(new StringBuilder("Done rendering ").append(counter
.get()).append('/').append(hs
.size()).toString());
548 if (wait
&& -1 != resample
) {
551 } catch (Throwable t
) {
559 static public void resetView(final LayerSet ls
) {
560 Display3D d3d
= ht_layer_sets
.get(ls
);
561 if (null != d3d
) d3d
.universe
.resetView();
564 static public void showOrthoslices(Patch p
) {
565 Display3D d3d
= get(p
.getLayerSet());
566 d3d
.adjustResampling();
567 //d3d.universe.resetView();
568 String title
= makeTitle(p
) + " orthoslices";
570 d3d
.universe
.removeContent(title
);
571 PatchStack ps
= p
.makePatchStack();
572 ImagePlus imp
= get8BitStack(ps
);
573 Content ct
= d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
574 setTransform(ct
, ps
.getPatch(0));
575 ct
.setLocked(true); // locks the added content
578 static public void showVolume(Patch p
) {
579 Display3D d3d
= get(p
.getLayerSet());
580 d3d
.adjustResampling();
581 //d3d.universe.resetView();
582 String title
= makeTitle(p
) + " volume";
584 d3d
.universe
.removeContent(title
);
585 PatchStack ps
= p
.makePatchStack();
586 ImagePlus imp
= get8BitStack(ps
);
587 Content ct
= d3d
.universe
.addVoltex(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
588 setTransform(ct
, ps
.getPatch(0));
589 ct
.setLocked(true); // locks the added content
592 static private void setTransform(Content ct
, Patch p
) {
593 final double[] a
= new double[6];
594 p
.getAffineTransform().getMatrix(a
);
595 Calibration cal
= p
.getLayerSet().getCalibration();
596 // a is: m00 m10 m01 m11 m02 m12
597 // d expects: m01 m02 m03 m04, m11 m12 ...
598 ct
.applyTransform(new Transform3D(new double[]{a
[0], a
[2], 0, a
[4] * cal
.pixelWidth
,
599 a
[1], a
[3], 0, a
[5] * cal
.pixelWidth
,
600 0, 0, 1, p
.getLayer().getZ() * cal
.pixelWidth
,
604 static public void showOrthoslices(final ImagePlus imp
, final String title
, final int wx
, final int wy
, final float scale2D
, final Layer first
) {
605 Display3D d3d
= get(first
.getParent());
606 d3d
.universe
.removeContent(title
);
607 Content ct
= d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, 1);
608 Calibration cal
= imp
.getCalibration();
609 Transform3D t
= new Transform3D(new double[]{1, 0, 0, wx
* cal
.pixelWidth
* scale2D
,
610 0, 1, 0, wy
* cal
.pixelHeight
* scale2D
,
611 0, 0, scale2D
, first
.getZ() * cal
.pixelWidth
* scale2D
, // not pixelDepth!
613 // why scale2D has to be there at all reflects a horrible underlying setting of the calibration, plus of the scaling in the Display3D.
615 ct
.applyTransform(t
);
619 /** Returns a stack suitable for the ImageJ 3D Viewer, either 8-bit gray or 8-bit color.
620 * If the PatchStack is already of the right type, it is returned,
621 * otherwise a copy is made in the proper type.
623 static private ImagePlus
get8BitStack(final PatchStack ps
) {
624 switch (ps
.getType()) {
625 case ImagePlus
.COLOR_RGB
:
626 // convert stack to 8-bit color
627 return ps
.createColor256Copy();
628 case ImagePlus
.GRAY16
:
629 case ImagePlus
.GRAY32
:
630 // convert stack to 8-bit
631 return ps
.createGray8Copy();
632 case ImagePlus
.GRAY8
:
633 case ImagePlus
.COLOR_256
:
636 Utils
.logAll("Cannot handle stacks of type: " + ps
.getType());
641 /** Considers there is only one Display3D for each LayerSet. */
642 static public void remove(ProjectThing pt
) {
643 if (null == pt
) return;
644 if (null == pt
.getObject()) return;
645 Object ob
= pt
.getObject();
646 if (!(ob
instanceof Displayable
)) return;
647 Displayable displ
= (Displayable
)ob
;
648 Display3D d3d
= ht_layer_sets
.get(displ
.getLayerSet()); // TODO profile_list is going to fail here
650 // there is no Display3D showing the pt to remove
651 //Utils.log2("No Display3D contains ProjectThing: " + pt);
655 synchronized (d3d
.ht_pt_meshes
) {
656 name
= d3d
.ht_pt_meshes
.remove(pt
);
659 Utils
.log2("No mesh contained within " + d3d
+ " for ProjectThing " + pt
);
662 d3d
.universe
.removeContent(name
);
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
) {
694 static private final String
makeProfileListTitle(final ProjectThing pt
) {
696 Object ob
= pt
.getParent().getTitle();
697 if (null == ob
|| ob
.equals(pt
.getParent().getType())) title
= pt
.toString() + " #" + pt
.getId(); // Project.getMeaningfulTitle can't handle profile_list properly
698 else title
= ob
.toString() + " /[" + pt
.getParent().getType() + "]/[profile_list] #" + pt
.getId();
702 /** Remove all basic type children contained in {@param pt} and its children, recursively.
706 static public void removeFrom3D(final ProjectThing pt
) {
707 final HashSet
<ProjectThing
> hs
= pt
.findBasicTypeChildren();
708 if (null == hs
|| 0 == hs
.size()) {
709 Utils
.logAll("Nothing to remove from 3D.");
712 // Ignore Profile instances ("profile_list" takes care of them)
713 for (final ProjectThing child
: hs
) {
714 if (child
.getByType().equals("profile")) continue;
716 LayerSet lset
= null;
717 if (child
.getType().equals("profile_list")) {
718 if (!child
.hasChildren()) continue;
719 for (ProjectThing p
: child
.getChildren()) {
720 if (null != p
.getObject() && p
.getObject() instanceof Profile
) {
721 lset
= ((Displayable
)p
.getObject()).getLayerSet();
725 if (null == lset
) continue;
726 } else if (child
.getType().equals("profile")) {
727 // Taken care of by "profile list"
730 final Displayable d
= (Displayable
)child
.getObject();
732 Utils
.log("Null object for ProjectThing " + child
);
735 lset
= d
.getLayerSet();
738 Utils
.log("No LayerSet found for " + child
);
741 Display3D d3d
= getDisplay(lset
);
743 Utils
.log("No Display 3D found for " + child
);
744 continue; // no Display3D window open
746 String oldTitle
= d3d
.ht_pt_meshes
.remove(child
);
747 if (null == oldTitle
) {
748 Utils
.log("Could not find a title for " + child
);
751 Utils
.log("Removed from 3D view: " + oldTitle
);
752 d3d
.getUniverse().removeContent(oldTitle
);
757 /** Returns a function that returns a Content object.
758 * Does NOT add the Content to the universe; it merely creates it. */
759 public Callable
<Content
> createMesh(final ProjectThing pt
, final Displayable displ
, final int resample
) {
760 final double scale
= 1.0; // OBSOLETE
761 return new Callable
<Content
>() {
762 public Content
call() {
763 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
766 // 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.)
767 final List
<Point3f
> triangles
;
768 //boolean no_culling_ = false;
771 final boolean line_mesh
;
772 final int line_mesh_mode
;
776 line_mesh_mode
= Integer
.MAX_VALUE
;
778 c
= displ
.getClass();
779 line_mesh
= Tree
.class.isAssignableFrom(c
) || Polyline
.class == c
;
780 if (Tree
.class.isAssignableFrom(c
)) line_mesh_mode
= CustomLineMesh
.PAIRWISE
;
781 else if (Polyline
.class == c
) line_mesh_mode
= CustomLineMesh
.CONTINUOUS
;
782 else line_mesh_mode
= Integer
.MAX_VALUE
; // disabled
785 List
<Point3f
> extra_triangles
= null;
786 List
<Color3f
> triangle_colors
= null,
787 extra_triangle_colors
= null;
790 if (displ
instanceof AreaContainer
) {
791 if (-1 == resample
) rs
= Display3D
.this.resample
= adjustResampling(); // will adjust this.resample, and return it (even if it's a default value)
792 else rs
= Display3D
.this.resample
;
794 if (AreaList
.class == c
) {
795 triangles
= ((AreaList
)displ
).generateTriangles(scale
, rs
);
796 //triangles = removeNonManifold(triangles);
797 } else if (Ball
.class == c
) {
798 double[][][] globe
= Ball
.generateGlobe(12, 12);
799 triangles
= ((Ball
)displ
).generateTriangles(scale
, globe
);
800 } else if (displ
instanceof Line3D
) {
802 // adjustResampling(); // fails horribly, needs first to correct mesh-generation code
803 triangles
= ((Line3D
)displ
).generateTriangles(scale
, 12, 1 /*Display3D.this.resample*/);
804 } else if (displ
instanceof Tree
<?
>) {
805 // A 3D wire skeleton, using CustomLineMesh
806 final Tree
.MeshData skeleton
= ((Tree
<?
>)displ
).generateSkeleton(scale
, 12, 1);
807 triangles
= skeleton
.verts
;
808 triangle_colors
= skeleton
.colors
;
809 if (displ
instanceof Treeline
) {
810 final Tree
.MeshData tube
= ((Treeline
)displ
).generateMesh(scale
, 12);
811 extra_triangles
= tube
.verts
;
812 extra_triangle_colors
= tube
.colors
;
813 } else if (displ
instanceof AreaTree
) {
814 final Tree
.MeshData mesh
= ((AreaTree
)displ
).generateMesh(scale
, rs
);
815 extra_triangles
= mesh
.verts
;
816 extra_triangle_colors
= mesh
.colors
;
818 if (null != extra_triangles
&& extra_triangles
.isEmpty()) extra_triangles
= null; // avoid issues with MultiMesh
819 } else if (Connector
.class == c
) {
820 final Tree
.MeshData octopus
= ((Connector
)displ
).generateMesh(scale
, 12);
821 triangles
= octopus
.verts
;
822 triangle_colors
= octopus
.colors
;
823 } else if (null == displ
&& pt
.getType().equals("profile_list")) {
824 triangles
= Profile
.generateTriangles(pt
, scale
);
825 //no_culling_ = true;
827 Utils
.log("Unrecognized type for 3D mesh generation: " + (null != displ ? displ
.getClass() : null) + " : " + displ
);
831 if (null == triangles
) {
832 Utils
.log("Some error ocurred: can't create triangles for " + displ
);
835 if (0 == triangles
.size()) {
836 Utils
.log2("Skipping empty mesh for " + displ
.getTitle());
839 if (!line_mesh
&& 0 != triangles
.size() % 3) {
840 Utils
.log2("Skipping non-multiple-of-3 vertices list generated for " + displ
.getTitle());
848 color
= displ
.getColor();
849 alpha
= displ
.getAlpha();
850 title
= makeTitle(displ
);
851 } else if (pt
.getType().equals("profile_list")) {
852 // for profile_list: get from the first (what a kludge; there should be a ZDisplayable ProfileList object)
853 Object obp
= ((ProjectThing
)pt
.getChildren().get(0)).getObject();
854 if (null == obp
) return null;
855 Displayable di
= (Displayable
)obp
;
856 color
= di
.getColor();
857 alpha
= di
.getAlpha();
858 title
= makeProfileListTitle(pt
);
860 title
= pt
.toString() + " #" + pt
.getId();
865 // 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.
866 final boolean no_culling
= true; // for ALL
871 Color3f c3
= new Color3f(color
);
873 // If it exists, remove and add as new:
874 universe
.removeContent(title
);
879 //ct = universe.createContent(new CustomLineMesh(triangles, line_mesh_mode, c3, 0), title);
880 cm
= new CustomLineMesh(triangles
, line_mesh_mode
, c3
, 0);
881 } else if (no_culling
) {
882 // create a mesh with the same color and zero transparency (that is, full opacity)
883 CustomTriangleMesh mesh
= new CustomTriangleMesh(triangles
, c3
, 0);
884 // Set mesh properties for double-sided triangles
885 PolygonAttributes pa
= mesh
.getAppearance().getPolygonAttributes();
886 pa
.setCullFace(PolygonAttributes
.CULL_NONE
);
887 pa
.setBackFaceNormalFlip(true);
889 // After setting properties, add to the viewer
890 //ct = universe.createContent(mesh, title);
893 //ct = universe.createContent(new CustomTriangleMesh(triangles, c3, 0), title);
894 cm
= new CustomTriangleMesh(triangles
, c3
, 0);
897 if (null != triangle_colors
) cm
.setColor(triangle_colors
);
899 //if (null == cm) return null;
901 if (null == extra_triangles
|| 0 == extra_triangles
.size()) {
902 ct
= universe
.createContent(cm
, title
);
904 final CustomTriangleMesh extra
= new CustomTriangleMesh(extra_triangles
, c3
, 0);
905 if (null != extra_triangle_colors
) {
906 // Set mesh properties for double-sided triangles
907 PolygonAttributes pa
= extra
.getAppearance().getPolygonAttributes();
908 pa
.setCullFace(PolygonAttributes
.CULL_NONE
);
909 pa
.setBackFaceNormalFlip(true);
910 extra
.setColor(extra_triangle_colors
);
912 ct
= universe
.createContent(new CustomMultiMesh(Arrays
.asList(new CustomMesh
[]{cm
, extra
})), title
);
915 // Set general content properties
916 ct
.setTransparency(1f
- alpha
);
917 // Default is unlocked (editable) transformation; set it to locked:
920 // register mesh title
921 synchronized (ht_pt_meshes
) {
922 ht_pt_meshes
.put(pt
, ct
.getName());
925 } catch (Throwable e
) {
926 Utils
.logAll("Mesh generation failed for \"" + title
+ "\" from " + pt
);
931 Utils
.log2(pt
.toString() + " n points: " + triangles
.size());
935 } catch (Exception e
) {
943 static public class VectorStringContent
{
949 public VectorStringContent(VectorString3D vs
, String title
, Color color
, double[] widths
, float alpha
){
953 this.widths
= widths
;
956 public Content
asContent(Display3D d3d
) {
957 double[] wi
= widths
;
958 if (null == widths
) {
959 wi
= new double[vs
.getPoints(0).length
];
960 Arrays
.fill(wi
, 2.0);
961 } else if (widths
.length
!= vs
.length()) {
962 Utils
.log("ERROR: widths.length != VectorString3D.length()");
965 float transp
= 1 - alpha
;
966 if (transp
< 0) transp
= 0;
967 if (transp
> 1) transp
= 1;
969 Utils
.log("WARNING: adding a 3D object fully transparent.");
971 List
<Point3f
> triangles
= Pipe
.generateTriangles(Pipe
.makeTube(vs
.getPoints(0), vs
.getPoints(1), vs
.getPoints(2), wi
, 1, 12, null), d3d
.scale
);
972 Content ct
= d3d
.universe
.createContent(new CustomTriangleMesh(triangles
, new Color3f(color
), 0), title
);
973 ct
.setTransparency(transp
);
979 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
980 static public Future
<Collection
<Future
<Content
>>> addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
) {
981 return addMesh(ref_ls
, vs
, title
, color
, null, 1.0f
);
984 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
985 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
) {
986 List
<Content
> col
= new ArrayList
<Content
>();
987 Display3D d3d
= Display3D
.get(ref_ls
);
988 col
.add(new VectorStringContent(vs
, title
, color
, widths
, alpha
).asContent(d3d
));
989 return d3d
.addContent(col
);
992 static public Future
<Collection
<Future
<Content
>>> show(final LayerSet ref_ls
, final Collection
<Content
> col
) {
993 Display3D d3d
= get(ref_ls
);
994 return d3d
.addContent(col
);
997 public Future
<Collection
<Future
<Content
>>> addContent(final Collection
<Content
> col
) {
999 final FutureTask
<Collection
<Future
<Content
>>> fu
= new FutureTask
<Collection
<Future
<Content
>>>(new Callable
<Collection
<Future
<Content
>>>() {
1000 public Collection
<Future
<Content
>> call() {
1001 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
1003 return universe
.addContentLater(col
);
1004 } catch (Throwable e
) {
1010 launchers
.submit(new Runnable() { public void run() {
1011 executors
.submit(fu
);
1017 public Future
<Content
> addContent(final Content c
) {
1018 final FutureTask
<Content
> fu
= new FutureTask
<Content
>(new Callable
<Content
>() {
1019 public Content
call() {
1020 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
1022 return universe
.addContentLater(c
).get();
1023 } catch (Throwable e
) {
1030 launchers
.submit(new Runnable() { public void run() {
1031 executors
.submit(fu
);
1037 static public final int estimateResamplingFactor(final LayerSet ls
, final double width
, final double height
) {
1038 final int max_dimension
= ls
.getPixelsMaxDimension();
1039 return (int)(DEFAULT_RESAMPLE
/ (Math
.max(width
, height
) > max_dimension ?
1040 max_dimension
/ Math
.max(width
, height
)
1044 /** Estimate a scaling factor, to be used as a multiplier of the suggested default resampling. */
1045 private final int estimateResamplingFactor() {
1046 return estimateResamplingFactor(layer_set
, width
, height
);
1049 // This method has the exclusivity in adjusting the resampling value, and it also returns it.
1050 synchronized private final int adjustResampling() {
1051 if (resample
> 0) return resample
;
1052 final GenericDialog gd
= new GenericDialog("Resample");
1053 final int default_resample
= estimateResamplingFactor();
1054 gd
.addSlider("Resample: ", 1, Math
.max(default_resample
, 100), -1 != resample ? resample
: default_resample
);
1056 if (gd
.wasCanceled()) {
1057 resample
= -1 != resample ? resample
: default_resample
; // current or default value
1060 resample
= ((java
.awt
.Scrollbar
)gd
.getSliders().get(0)).getValue();
1064 /** Checks if there is any Display3D instance currently showing the given Displayable. */
1065 static public boolean isDisplayed(final Displayable d
) {
1066 if (null == d
) return false;
1067 final String title
= makeTitle(d
);
1068 for (Display3D d3d
: ht_layer_sets
.values()) {
1069 if (null != d3d
.universe
.getContent(title
)) return true;
1071 if (d
.getClass() == Profile
.class) {
1072 if (null != getProfileContent(d
)) return true;
1077 /** 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. */
1078 static public Content
getProfileContent(final Displayable d
) {
1079 if (null == d
) return null;
1080 if (d
.getClass() != Profile
.class) return null;
1081 Display3D d3d
= get(d
.getLayer().getParent());
1082 if (null == d3d
) return null;
1083 ProjectThing pt
= d
.getProject().findProjectThing(d
);
1084 if (null == pt
) return null;
1085 pt
= (ProjectThing
) pt
.getParent();
1086 return d3d
.universe
.getContent(new StringBuilder(pt
.toString()).append(" #").append(pt
.getId()).toString());
1089 static public Future
<Boolean
> setColor(final Displayable d
, final Color color
) {
1090 final Display3D d3d
= getDisplay(d
.getLayer().getParent());
1091 if (null == d3d
) return null; // no 3D displays open
1092 return d3d
.executors
.submit(new Callable
<Boolean
>() { public Boolean
call() {
1093 Content content
= d3d
.universe
.getContent(makeTitle(d
));
1094 if (null == content
) content
= getProfileContent(d
);
1095 if (null != content
) {
1096 content
.setColor(new Color3f(color
));
1103 static public Future
<Boolean
> setTransparency(final Displayable d
, final float alpha
) {
1104 if (null == d
) return null;
1105 Layer layer
= d
.getLayer();
1106 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
1107 final Display3D d3d
= ht_layer_sets
.get(layer
.getParent());
1108 if (null == d3d
) return null;
1109 return d3d
.executors
.submit(new Callable
<Boolean
>() { public Boolean
call() {
1110 String title
= makeTitle(d
);
1111 Content content
= d3d
.universe
.getContent(title
);
1112 if (null == content
) content
= getProfileContent(d
);
1113 if (null != content
) content
.setTransparency(1 - alpha
);
1114 else if (null == content
&& d
.getClass().equals(Patch
.class)) {
1115 Patch pa
= (Patch
)d
;
1117 title
= pa
.getProject().getLoader().getFileName(pa
);
1118 for (Display3D dd
: ht_layer_sets
.values()) {
1119 for (Iterator
<?
> cit
= dd
.universe
.getContents().iterator(); cit
.hasNext(); ) {
1120 Content c
= (Content
)cit
.next();
1121 if (c
.getName().startsWith(title
)) {
1122 c
.setTransparency(1 - alpha
);
1123 // no break, since there could be a volume and an orthoslice
1133 static public String
makeTitle(final Displayable d
) {
1134 return d
.getProject().getMeaningfulTitle(d
) + " #" + d
.getId();
1136 static public String
makeTitle(final Patch p
) {
1137 return new File(p
.getProject().getLoader().getAbsolutePath(p
)).getName()
1138 + " #" + p
.getProject().getLoader().getNextId();
1141 /** Remake the mesh for the Displayable in a separate Thread, if it's included in a Display3D
1142 * (otherwise returns null). */
1143 static public Future
<Content
> update(final Displayable d
) {
1144 Layer layer
= d
.getLayer();
1145 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
1146 Display3D d3d
= ht_layer_sets
.get(layer
.getParent());
1147 if (null == d3d
) return null;
1148 return d3d
.addMesh(d
.getProject().findProjectThing(d
), d
, d3d
.resample
);
1152 static public final double computeTriangleArea() {
1153 return 0.5 * Math.sqrt(Math.pow(xA*yB + xB*yC + xC*yA, 2) +
1154 Math.pow(yA*zB + yB*zC + yC*zA, 2) +
1155 Math.pow(zA*xB + zB*xC + zC*xA, 2));
1159 static public final boolean contains(final LayerSet ls
, final String title
) {
1160 final Display3D d3d
= getDisplay(ls
);
1161 if (null == d3d
) return false;
1162 return null != d3d
.universe
.getContent(title
);
1165 static public void destroy() {
1166 launchers
.shutdownNow();
1169 static public void init() {
1170 if (launchers
.isShutdown()) {
1171 launchers
= Utils
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors(), "Display3D-launchers");
1175 /** Creates a calibrated sphere to represent a point at LayerSet pixel coordinates wx, wy, wz, with radius wr.*/
1176 public List
<Point3f
> createFatPoint(final double wx
, final double wy
, final double wz
, final double wr
, final Calibration cal
) {
1177 final double[][][] globe
= Ball
.generateGlobe(12, 12);
1178 final int sign
= cal
.pixelDepth
< 0 ?
-1 : 1;
1179 for (int z
=0; z
<globe
.length
; z
++) {
1180 for (int k
=0; k
<globe
[0].length
; k
++) {
1181 globe
[z
][k
][0] = (globe
[z
][k
][0] * wr
+ wx
) * scale
* cal
.pixelWidth
;
1182 globe
[z
][k
][1] = (globe
[z
][k
][1] * wr
+ wy
) * scale
* cal
.pixelHeight
;
1183 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.
1186 final ArrayList
<Point3f
> list
= new ArrayList
<Point3f
>();
1187 // create triangular faces and add them to the list
1188 for (int z
=0; z
<globe
.length
-1; z
++) { // the parallels
1189 for (int k
=0; k
<globe
[0].length
-1; k
++) { // meridian points
1190 // half quadrant (a triangle)
1191 list
.add(new Point3f((float)globe
[z
][k
][0], (float)globe
[z
][k
][1], (float)globe
[z
][k
][2]));
1192 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]));
1193 list
.add(new Point3f((float)globe
[z
+1][k
][0], (float)globe
[z
+1][k
][1], (float)globe
[z
+1][k
][2]));
1194 // the other half quadrant
1195 list
.add(new Point3f((float)globe
[z
][k
][0], (float)globe
[z
][k
][1], (float)globe
[z
][k
][2]));
1196 list
.add(new Point3f((float)globe
[z
][k
+1][0], (float)globe
[z
][k
+1][1], (float)globe
[z
][k
+1][2]));
1197 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]));
1203 /** Expects uncalibrated wx,wy,wz, (i.e. pixel values), to be calibrated by @param ls calibration. */
1204 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
) {
1205 Display3D d3d
= Display3D
.get(ls
);
1206 d3d
.universe
.removeContent(title
);
1207 Content ct
= d3d
.universe
.createContent(new CustomTriangleMesh(d3d
.createFatPoint(wx
, wy
, wz
, wr
, ls
.getCalibrationCopy()), new Color3f(color
), 0), title
);
1209 return d3d
.addContent(ct
);