1 package ini
.trakem2
.display
;
3 import ini
.trakem2
.tree
.*;
4 import ini
.trakem2
.utils
.*;
5 import ini
.trakem2
.imaging
.PatchStack
;
6 import ini
.trakem2
.vector
.VectorString3D
;
10 import ij
.process
.ImageProcessor
;
11 import ij
.process
.ByteProcessor
;
12 import ij
.gui
.ShapeRoi
;
13 import ij
.gui
.GenericDialog
;
14 import ij
.io
.DirectoryChooser
;
15 import ij
.measure
.Calibration
;
17 import java
.awt
.Color
;
18 import java
.awt
.Cursor
;
19 import java
.awt
.Rectangle
;
20 import java
.awt
.geom
.Area
;
21 import java
.awt
.MenuBar
;
23 import java
.awt
.MenuItem
;
24 import java
.awt
.CheckboxMenuItem
;
25 import java
.awt
.Component
;
26 import java
.awt
.event
.ActionListener
;
27 import java
.awt
.event
.ItemListener
;
28 import java
.awt
.event
.ActionEvent
;
29 import java
.awt
.event
.ItemEvent
;
32 import java
.awt
.geom
.AffineTransform
;
34 import java
.awt
.event
.WindowAdapter
;
35 import java
.awt
.event
.WindowEvent
;
36 import java
.awt
.event
.KeyEvent
;
37 import java
.awt
.event
.KeyAdapter
;
38 import java
.awt
.event
.KeyListener
;
40 import javax
.vecmath
.Point3f
;
41 import javax
.vecmath
.Color3f
;
42 import javax
.vecmath
.Vector3f
;
43 import javax
.media
.j3d
.View
;
44 import javax
.media
.j3d
.Transform3D
;
45 import javax
.media
.j3d
.PolygonAttributes
;
47 import ij3d
.ImageWindow3D
;
48 import ij3d
.Image3DUniverse
;
50 import ij3d
.Image3DMenubar
;
51 import customnode
.CustomMeshNode
;
52 import customnode
.CustomMesh
;
53 import customnode
.CustomTriangleMesh
;
55 import java
.lang
.reflect
.Field
;
56 import java
.util
.concurrent
.ExecutorService
;
57 import java
.util
.concurrent
.Executors
;
58 import java
.util
.concurrent
.Future
;
59 import java
.util
.concurrent
.TimeUnit
;
60 import java
.util
.concurrent
.FutureTask
;
61 import java
.util
.concurrent
.Callable
;
64 /** One Display3D instance for each LayerSet (maximum). */
65 public final class Display3D
{
67 /** The threading is so poorly done ... it BARELY works, it's fragile. */
69 /** Table of LayerSet and Display3D - since there is a one to one relationship. */
70 static private Hashtable ht_layer_sets
= new Hashtable();
71 /**Control calls to new Display3D. */
72 static private Lock htlock
= new Lock();
74 /** 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. */
75 static public Hashtable
getMasterTable() {
76 return (Hashtable
)ht_layer_sets
.clone();
79 /** Table of ProjectThing keys versus meshes, the latter represented by List of triangles in the form of thre econsecutive Point3f in the List.*/
80 private Hashtable ht_pt_meshes
= new Hashtable();
82 private Image3DUniverse universe
;
84 private Lock u_lock
= new Lock();
86 private LayerSet layer_set
;
87 private double width
, height
;
88 private int resample
= -1; // unset
89 static private final int DEFAULT_RESAMPLE
= 4;
90 /** If the LayerSet dimensions are too large, then limit to max 2048 for width or height and setup a scale.*/
91 private double scale
= 1.0;
92 static private final int MAX_DIMENSION
= 1024;
94 private String selected
= null;
96 // To fork away from the EventDispatchThread
97 static private ExecutorService launchers
= Executors
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
100 private ExecutorService executors
= Executors
.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
103 static private KeyAdapter ka = new KeyAdapter() {
104 public void keyPressed(KeyEvent ke) {
105 // F1 .. F12 keys to set tools
106 ProjectToolbar.keyPressed(ke);
111 /** Defaults to parallel projection. */
112 private Display3D(final LayerSet ls
) {
114 this.universe
= new Image3DUniverse(512, 512); // size of the initial canvas, not the universe itself
115 this.universe
.getViewer().getView().setProjectionPolicy(View
.PERSPECTIVE_PROJECTION
); // (View.PERSPECTIVE_PROJECTION);
117 this.universe
.show();
118 this.universe
.getWindow().addWindowListener(new IW3DListener(this, ls
));
119 // it ignores the listeners:
120 //preaddKeyListener(this.universe.getWindow(), ka);
121 //preaddKeyListener(this.universe.getWindow().getCanvas(), ka);
124 Display3D
.ht_layer_sets
.put(ls
, this);
128 private void preaddKeyListener(Component c, KeyListener kl) {
129 KeyListener[] all = c.getKeyListeners();
131 for (KeyListener k : all) c.removeKeyListener(k);
133 c.addKeyListener(kl);
135 for (KeyListener k : all) c.addKeyListener(k);
140 public Image3DUniverse
getUniverse() {
144 /* Take a snapshot know-it-all mode. Each Transform3D given as argument gets assigned to the (nearly) homonimous TransformGroup, which have the following relationships:
146 * scaleTG contains rotationsTG
147 * rotationsTG contains translateTG
148 * translateTG contains centerTG
149 * centerTG contains the whole scene, with all meshes, etc.
151 * Any null arguments imply the current transform in the open Display3D.
153 * 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.
155 * The TransformGroup instances may be reached like this:
157 * LayerSet layer_set = Display.getFrontLayer().getParent();
158 * Display3D d3d = Display3D.getDisplay(layer_set);
159 * TransformGroup scaleTG = d3d.getUniverse().getGlobalScale();
160 * TransformGroup rotationsTG = d3d.getUniverse().getGlobalRotate();
161 * TransformGroup translateTG = d3d.getUniverse().getGlobalTranslate();
162 * TransformGroup centerTG = d3d.getUniverse().getCenterTG();
164 * ... and the Transform3D from each may be read out indirectly like this:
166 * Transform3D t_scale = new Transform3D();
167 * scaleTG.getTransform(t_scale);
170 * 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).
173 /*public ImagePlus makeSnapshot(final Transform3D scale, final Transform3D rotate, final Transform3D translate, final Transform3D center) {
174 return universe.makeSnapshot(scale, rotate, translate, center);
177 /** Uses current scaling, translation and centering transforms! */
178 /*public ImagePlus makeSnapshotXY() { // aka posterior
180 return universe.makeSnapshot(null, new Transform3D(), null, null);
182 /** Uses current scaling, translation and centering transforms! */
183 /*public ImagePlus makeSnapshotXZ() { // aka dorsal
184 Transform3D rot1 = new Transform3D();
185 rot1.rotZ(-Math.PI/2);
186 Transform3D rot2 = new Transform3D();
187 rot2.rotX(Math.PI/2);
189 return universe.makeSnapshot(null, rot1, null, null);
192 /** Uses current scaling, translation and centering transforms! */
194 public ImagePlus makeSnapshotYZ() { // aka lateral
195 Transform3D rot = new Transform3D();
197 return universe.makeSnapshot(null, rot, null, null);
201 public ImagePlus makeSnapshotZX() { // aka frontal
202 Transform3D rot = new Transform3D();
203 rot.rotX(-Math.PI/2);
204 return universe.makeSnapshot(null, rot, null, null);
208 /** Uses current scaling, translation and centering transforms! Opposite side of XZ. */
210 public ImagePlus makeSnapshotXZOpp() {
211 Transform3D rot1 = new Transform3D();
212 rot1.rotX(-Math.PI/2); // 90 degrees clockwise
213 Transform3D rot2 = new Transform3D();
214 rot2.rotY(Math.PI); // 180 degrees around Y, to the other side.
216 return universe.makeSnapshot(null, rot1, null, null);
219 private class IW3DListener
extends WindowAdapter
{
220 private Display3D d3d
;
222 IW3DListener(Display3D d3d
, LayerSet ls
) {
226 public void windowClosing(WindowEvent we
) {
227 Utils
.log2("Display3D.windowClosing");
228 d3d
.executors
.shutdownNow();
229 /*Object ob =*/ ht_layer_sets
.remove(ls
);
231 Utils.log2("Removed Display3D from table for LayerSet " + ls);
234 public void windowClosed(WindowEvent we
) {
235 Utils
.log2("Display3D.windowClosed");
236 ht_layer_sets
.remove(ls
);
240 /** Reads the #ID in the name, which is immutable. */
241 private ProjectThing
find(String name
) {
242 long id
= Long
.parseLong(name
.substring(name
.lastIndexOf('#')+1));
243 for (Iterator it
= ht_pt_meshes
.keySet().iterator(); it
.hasNext(); ) {
244 ProjectThing pt
= (ProjectThing
)it
.next();
245 Displayable d
= (Displayable
)pt
.getObject();
246 if (d
.getId() == id
) {
253 /** If the layer set is too large in width and height, then set a scale that makes it maximum MAX_DIMENSION in any of the two dimensions. */
254 private void computeScale(LayerSet ls
) {
255 this.width
= ls
.getLayerWidth();
256 this.height
= ls
.getLayerHeight();
257 if (width
> MAX_DIMENSION
) {
258 scale
= MAX_DIMENSION
/ width
;
260 width
= MAX_DIMENSION
;
262 if (height
> MAX_DIMENSION
) {
263 scale
= MAX_DIMENSION
/ height
;
265 height
= MAX_DIMENSION
;
267 //Utils.log2("scale, width, height: " + scale + ", " + width + ", " + height);
270 static private boolean check_j3d
= true;
271 static private boolean has_j3d_3dviewer
= false;
273 static private boolean hasLibs() {
277 Class p3f
= Class
.forName("javax.vecmath.Point3f");
278 has_j3d_3dviewer
= true;
279 } catch (ClassNotFoundException cnfe
) {
280 Utils
.log("Java 3D not installed.");
281 has_j3d_3dviewer
= false;
285 Class ij3d
= Class
.forName("ij3d.ImageWindow3D");
286 has_j3d_3dviewer
= true;
287 } catch (ClassNotFoundException cnfe
) {
288 Utils
.log("3D Viewer not installed.");
289 has_j3d_3dviewer
= false;
293 return has_j3d_3dviewer
;
296 /** Get an existing Display3D for the given LayerSet, or create a new one for it (and cache it). */
297 static private Display3D
get(final LayerSet ls
) {
298 synchronized (htlock
) {
302 if (!hasLibs()) return null;
304 Object ob
= ht_layer_sets
.get(ls
);
306 final boolean[] done
= new boolean[]{false};
307 javax
.swing
.SwingUtilities
.invokeAndWait(new Runnable() { public void run() {
308 Display3D ob
= new Display3D(ls
);
309 ht_layer_sets
.put(ls
, ob
);
312 // wait to avoid crashes in amd64
313 // try { Thread.sleep(500); } catch (Exception e) {}
315 try { Thread
.sleep(50); } catch (Exception e
) {}
317 ob
= ht_layer_sets
.get(ls
);
319 return (Display3D
)ob
;
320 } catch (Exception e
) {
323 // executed even when returning from within the try-catch block
330 /** Get the Display3D instance that exists for the given LayerSet, if any. */
331 static public Display3D
getDisplay(final LayerSet ls
) {
332 return (Display3D
)ht_layer_sets
.get(ls
);
335 static public void setWaitingCursor() {
336 for (Iterator it
= ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
337 ((Display3D
)it
.next()).universe
.getWindow().setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
));
341 static public void doneWaiting() {
342 for (Iterator it
= ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
343 ((Display3D
)it
.next()).universe
.getWindow().setCursor(Cursor
.getDefaultCursor());
347 static public void show(ProjectThing pt
) {
351 /** 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. */
352 static public Future
<List
<Content
>> show(final ProjectThing pt
, final boolean wait
, final int resample
) {
353 if (null == pt
) return null;
354 Callable
<List
<Content
>> c
= new Callable
<List
<Content
>>() {
355 public List
<Content
> call() {
357 // scan the given ProjectThing for 3D-viewable items not present in the ht_meshes
358 // So: find arealist, pipe, ball, and profile_list types
359 HashSet hs
= pt
.findBasicTypeChildren();
360 if (null == hs
|| 0 == hs
.size()) {
361 Utils
.log("Node " + pt
+ " contains no 3D-displayable children");
365 final List
<Content
> list
= new ArrayList
<Content
>();
367 for (Iterator it
= hs
.iterator(); it
.hasNext(); ) {
368 // obtain the Displayable object under the node
369 ProjectThing child
= (ProjectThing
)it
.next();
370 Object obc
= child
.getObject();
371 Displayable displ
= obc
.getClass().equals(String
.class) ?
null : (Displayable
)obc
;
373 if (displ
.getClass().equals(Profile
.class)) {
374 //Utils.log("Display3D can't handle Bezier profiles at the moment.");
375 // handled by profile_list Thing
378 if (!displ
.isVisible()) {
379 Utils
.log("Skipping non-visible node " + displ
);
383 //StopWatch sw = new StopWatch();
384 // obtain the containing LayerSet
385 Display3D d3d
= null;
386 if (null != displ
) d3d
= Display3D
.get(displ
.getLayerSet());
387 else if (child
.getType().equals("profile_list")) {
388 ArrayList al_children
= child
.getChildren();
389 if (null == al_children
|| 0 == al_children
.size()) continue;
390 // else, get the first Profile and get its LayerSet
391 d3d
= Display3D
.get(((Displayable
)((ProjectThing
)al_children
.get(0)).getObject()).getLayerSet());
393 Utils
.log("Don't know what to do with node " + child
);
396 Utils
.log("Could not get a proper 3D display for node " + displ
);
397 return null; // java3D not installed most likely
399 if (d3d
.ht_pt_meshes
.contains(child
)) {
400 Utils
.log2("Already here: " + child
);
401 continue; // already here
403 setWaitingCursor(); // the above may be creating a display
404 //sw.elapsed("after creating and/or retrieving Display3D");
405 Future
<Content
> fu
= d3d
.addMesh(child
, displ
, resample
);
406 if (wait
&& -1 != d3d
.resample
) {
407 Utils
.log("joining...");
412 //sw.elapsed("after creating mesh");
417 } catch (Exception e
) {
425 return launchers
.submit(c
);
428 static public void showOrthoslices(Patch p
) {
429 Display3D d3d
= get(p
.getLayerSet());
430 d3d
.adjustResampling();
431 //d3d.universe.resetView();
432 String title
= makeTitle(p
) + " orthoslices";
434 d3d
.universe
.removeContent(title
);
435 PatchStack ps
= p
.makePatchStack();
436 ImagePlus imp
= get8BitStack(ps
);
437 d3d
.universe
.addOrthoslice(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
438 Content ct
= d3d
.universe
.getContent(title
);
439 setTransform(ct
, ps
.getPatch(0));
440 ct
.toggleLock(); // locks the added content
443 static public void showVolume(Patch p
) {
444 Display3D d3d
= get(p
.getLayerSet());
445 d3d
.adjustResampling();
446 //d3d.universe.resetView();
447 String title
= makeTitle(p
) + " volume";
449 d3d
.universe
.removeContent(title
);
450 PatchStack ps
= p
.makePatchStack();
451 ImagePlus imp
= get8BitStack(ps
);
452 d3d
.universe
.addVoltex(imp
, null, title
, 0, new boolean[]{true, true, true}, d3d
.resample
);
453 Content ct
= d3d
.universe
.getContent(title
);
454 setTransform(ct
, ps
.getPatch(0));
455 ct
.toggleLock(); // locks the added content
458 static private void setTransform(Content ct
, Patch p
) {
459 final double[] a
= new double[6];
460 p
.getAffineTransform().getMatrix(a
);
461 Calibration cal
= p
.getLayerSet().getCalibration();
462 // a is: m00 m10 m01 m11 m02 m12
463 // d expects: m01 m02 m03 m04, m11 m12 ...
464 ct
.applyTransform(new Transform3D(new double[]{a
[0], a
[2], 0, a
[4] * cal
.pixelWidth
,
465 a
[1], a
[3], 0, a
[5] * cal
.pixelWidth
,
466 0, 0, 1, p
.getLayer().getZ() * cal
.pixelWidth
,
470 /** Returns a stack suitable for the ImageJ 3D Viewer, either 8-bit gray or 8-bit color.
471 * If the PatchStach is already of the right type, it is returned,
472 * otherwise a copy is made in the proper type.
474 static private ImagePlus
get8BitStack(final PatchStack ps
) {
475 switch (ps
.getType()) {
476 case ImagePlus
.COLOR_RGB
:
477 // convert stack to 8-bit color
478 return ps
.createColor256Copy();
479 case ImagePlus
.GRAY16
:
480 case ImagePlus
.GRAY32
:
481 // convert stack to 8-bit
482 return ps
.createGray8Copy();
488 /** A Material, but avoiding name colisions. */
489 static private int mat_index
= 1;
490 static private class Mtl
{
496 Mtl(float alpha
, float R
, float G
, float B
) {
501 name
= "mat_" + mat_index
;
504 public boolean equals(Object ob
) {
505 if (ob
instanceof Display3D
.Mtl
) {
507 if (mat
.alpha
== alpha
516 void fill(StringBuffer sb
) {
517 sb
.append("\nnewmtl ").append(name
).append('\n')
518 .append("Ns 96.078431\n")
519 .append("Ka 0.0 0.0 0.0\n")
520 .append("Kd ").append(R
).append(' ').append(G
).append(' ').append(B
).append('\n') // this is INCORRECT but I'll figure out the conversion later
521 .append("Ks 0.5 0.5 0.5\n")
523 .append("d ").append(alpha
).append('\n')
524 .append("illum 2\n\n");
527 return (int)((R
+ G
+ B
) / 3 * 255); // something silly
531 /** Generates DXF file from a table of ProjectThing and their associated triangles. */
532 private String
createDXF(Hashtable ht_content
) {
533 StringBuffer sb_data
= new StringBuffer("0\nSECTION\n2\nENTITIES\n"); //header of file
534 for (Iterator it
= ht_content
.entrySet().iterator(); it
.hasNext(); ) {
535 Map
.Entry entry
= (Map
.Entry
)it
.next();
536 ProjectThing pt
= (ProjectThing
)entry
.getKey();
537 Displayable displ
= (Displayable
)pt
.getObject();
538 List triangles
= (List
)entry
.getValue();
539 float[] color
= displ
.getColor().getColorComponents(null);
540 Mtl mtl
= new Mtl(displ
.getAlpha(), color
[0], color
[1], color
[2]);
541 writeTrianglesDXF(sb_data
, triangles
, mtl
.name
, Integer
.toString(mtl
.getAsSingle()));
543 sb_data
.append("0\nENDSEC\n0\nEOF\n"); //TRAILER of the file
544 return sb_data
.toString();
547 /** @param format works as extension as well. */
548 private void export(final ProjectThing pt
, final String format
) {
549 if (0 == ht_pt_meshes
.size()) return;
551 File file
= Utils
.chooseFile("untitled", format
);
552 if (null == file
) return;
553 final String name
= file
.getName();
555 if (!name2
.endsWith("." + format
)) {
556 name2
+= "." + format
;
558 File f2
= new File(file
.getParent() + "/" + name2
);
560 while (f2
.exists()) {
561 name2
= name
+ "_" + i
+ "." + format
;
562 f2
= new File(name2
);
564 Hashtable ht_content
= ht_pt_meshes
;
566 ht_content
= new Hashtable();
567 ht_content
.put(pt
, ht_pt_meshes
.get(pt
));
569 if (format
.equals("obj")) {
570 String
[] data
= createObjAndMtl(name2
, ht_content
);
571 Utils
.saveToFile(f2
, data
[0]);
572 Utils
.saveToFile(new File(f2
.getParent() + "/" + name2
+ ".mtl"), data
[1]);
573 } else if (format
.equals("dxf")) {
574 Utils
.saveToFile(f2
, createDXF(ht_content
));
578 /** Wavefront format. Returns the contents of two files: one for materials, another for meshes*/
579 private String
[] createObjAndMtl(final String file_name
, final Hashtable ht_content
) {
580 StringBuffer sb_obj
= new StringBuffer("# TrakEM2 OBJ File\n");
581 sb_obj
.append("mtllib ").append(file_name
).append(".mtl").append('\n');
583 Hashtable ht_mat
= new Hashtable();
585 int j
= 1; // Vert indices in .obj files are global, not reset for every object.
586 // starting at '1' because vert indices start at one.
588 for (Iterator it
= ht_content
.entrySet().iterator(); it
.hasNext(); ) {
589 Map
.Entry entry
= (Map
.Entry
)it
.next(); // I hate java's gratuituous verbosity
590 ProjectThing pt
= (ProjectThing
)entry
.getKey();
591 Displayable displ
= (Displayable
)pt
.getObject();
592 List triangles
= (List
)entry
.getValue();
593 // make material, and see whether it exists already
594 float[] color
= displ
.getColor().getColorComponents(null);
595 Mtl mat
= new Mtl(displ
.getAlpha(), color
[0], color
[1], color
[2]);
596 Object mat2
= ht_mat
.get(mat
);
597 if (null != mat2
) mat
= (Mtl
)mat2
; // recycling
598 else ht_mat
.put(mat
, mat
); // !@#$% Can't get the object in a HashSet easily
599 // make list of vertices
600 String title
= displ
.getProject().getMeaningfulTitle(displ
).replaceAll(" ", "_").replaceAll("#", "--");
601 Hashtable ht_points
= new Hashtable(); // because we like inefficiency
602 sb_obj
.append("o ").append(title
).append('\n');
603 final int len
= triangles
.size();
604 int[] index
= new int[len
];
605 int k
= 0; // iterate over index array, to make faces later
606 // j is tag for each new vert, which start at 1 (for some ridiculous reason)
607 for (Iterator tt
= triangles
.iterator(); tt
.hasNext(); ) {
608 Point3f p
= (Point3f
)tt
.next();
609 //no need if coords are not displaced//p = (Point3f)p.clone();
610 // check if point already exists
611 Object ob
= ht_points
.get(p
);
613 index
[k
] = ((Integer
)ob
).intValue();
618 ht_points
.put(p
, new Integer(j
));
620 sb_obj
.append('v').append(' ').append(p
.x
)
621 .append(' ').append(p
.y
)
622 .append(' ').append(p
.z
).append('\n');
627 sb_obj
.append("usemtl ").append(mat
.name
).append('\n');
628 sb_obj
.append("s 1\n");
629 if (0 != len
% 3) Utils
.log2("WARNING: list of triangles not multiple of 3");
631 int len_p
= ht_points
.size();
632 for (int i
=0; i
<len
; i
+=3) {
633 sb_obj
.append('f').append(' ').append(index
[i
])
634 .append(' ').append(index
[i
+1])
635 .append(' ').append(index
[i
+2]).append('\n');
636 //if (index[i] > len_p) Utils.log2("WARNING: face vert index beyond range"); // range is from 1 to len_p inclusive
637 //if (index[i+1] > len_p) Utils.log2("WARNING: face vert index beyond range");
638 //if (index[i+2] > len_p) Utils.log2("WARNING: face vert index beyond range");
639 //Utils.log2("j: " + index[i]);
645 StringBuffer sb_mtl
= new StringBuffer("# TrakEM2 MTL File\n");
646 for (Iterator it
= ht_mat
.keySet().iterator(); it
.hasNext(); ) {
647 Mtl mat
= (Mtl
)it
.next();
651 return new String
[]{sb_obj
.toString(), sb_mtl
.toString()};
654 /** Considers there is only one Display3D for each LayerSet. */
655 static public void remove(ProjectThing pt
) {
656 if (null == pt
) return;
657 if (null == pt
.getObject()) return;
658 Object ob
= pt
.getObject();
659 if (!(ob
instanceof Displayable
)) return;
660 Displayable displ
= (Displayable
)ob
;
661 Object d3ob
= ht_layer_sets
.get(displ
.getLayerSet()); // TODO profile_list is going to fail here
663 // there is no Display3D showing the pt to remove
664 Utils
.log2("No Display3D contains ProjectThing: " + pt
);
667 Display3D d3d
= (Display3D
)d3ob
;
668 Object ob_mesh
= d3d
.ht_pt_meshes
.remove(pt
);
669 if (null == ob_mesh
) {
670 Utils
.log2("No mesh contained within " + d3d
+ " for ProjectThing " + pt
);
671 return; // not contained here
673 String title
= makeTitle(displ
);
674 //Utils.log(d3d.universe.contains(title) + ": Universe contains " + displ);
675 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.
678 static private void writeTrianglesDXF(final StringBuffer sb
, final List triangles
, final String the_group
, final String the_color
) {
681 final String s10
= "10\n"; final String s11
= "11\n"; final String s12
= "12\n"; final String s13
= "13\n";
682 final String s20
= "20\n"; final String s21
= "21\n"; final String s22
= "22\n"; final String s23
= "23\n";
683 final String s30
= "30\n"; final String s31
= "31\n"; final String s32
= "32\n"; final String s33
= "33\n";
684 final String triangle_header
= "0\n3DFACE\n8\n" + the_group
+ "\n6\nCONTINUOUS\n62\n" + the_color
+ L
;
686 final int len
= triangles
.size();
687 final Point3f
[] vert
= new Point3f
[len
];
688 triangles
.toArray(vert
);
689 for (int i
=0; i
<len
; i
+=3) {
691 sb
.append(triangle_header
)
693 .append(s10
).append(vert
[i
].x
).append(L
)
694 .append(s20
).append(vert
[i
].y
).append(L
)
695 .append(s30
).append(vert
[i
].z
).append(L
)
697 .append(s11
).append(vert
[i
+1].x
).append(L
)
698 .append(s21
).append(vert
[i
+1].y
).append(L
)
699 .append(s31
).append(vert
[i
+1].z
).append(L
)
701 .append(s12
).append(vert
[i
+2].x
).append(L
)
702 .append(s22
).append(vert
[i
+2].y
).append(L
)
703 .append(s32
).append(vert
[i
+2].z
).append(L
)
705 .append(s13
).append(vert
[i
+2].x
).append(L
) // repeated point
706 .append(s23
).append(vert
[i
+2].y
).append(L
)
707 .append(s33
).append(vert
[i
+2].z
).append(L
);
711 /** Creates a mesh for the given Displayable in a separate Thread. */
712 private Future
<Content
> addMesh(final ProjectThing pt
, final Displayable displ
, final int resample
) {
713 final double scale
= this.scale
;
714 FutureTask
<Content
> fu
= new FutureTask
<Content
>(new Callable
<Content
>() {
715 public Content
call() {
716 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
719 // 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.)
720 List triangles
= null;
721 boolean no_culling
= false; // don't show back faces when false
722 if (displ
instanceof AreaList
) {
724 if (-1 == resample
) rs
= Display3D
.this.resample
= adjustResampling(); // will adjust this.resample, and return it (even if it's a default value)
725 else rs
= Display3D
.this.resample
;
726 triangles
= ((AreaList
)displ
).generateTriangles(scale
, rs
);
727 //triangles = removeNonManifold(triangles);
728 } else if (displ
instanceof Ball
) {
729 double[][][] globe
= Ball
.generateGlobe(12, 12);
730 triangles
= ((Ball
)displ
).generateTriangles(scale
, globe
);
731 } else if (displ
instanceof Line3D
) {
733 // adjustResampling(); // fails horribly, needs first to correct mesh-generation code
734 triangles
= ((Line3D
)displ
).generateTriangles(scale
, 12, 1 /*Display3D.this.resample*/);
735 } else if (null == displ
&& pt
.getType().equals("profile_list")) {
736 triangles
= Profile
.generateTriangles(pt
, scale
);
740 if (null == triangles
) {
741 Utils
.log("Some error ocurred: can't create triangles for " + displ
);
744 if (0 == triangles
.size()) {
745 Utils
.log2("Skipping empty mesh for " + displ
.getTitle());
748 if (0 != triangles
.size() % 3) {
749 Utils
.log2("Skipping non-multiple-of-3 vertices list generated for " + displ
.getTitle());
755 color
= displ
.getColor();
756 alpha
= displ
.getAlpha();
758 // for profile_list: get from the first (what a kludge)
759 Object obp
= ((ProjectThing
)pt
.getChildren().get(0)).getObject();
760 if (null == obp
) return null;
761 Displayable di
= (Displayable
)obp
;
762 color
= di
.getColor();
763 alpha
= di
.getAlpha();
768 // add to 3D view (synchronized)
769 synchronized (u_lock
) {
772 // craft a unique title (id is always unique)
773 String title
= null == displ ? pt
.toString() + " #" + pt
.getId() : makeTitle(displ
);
774 if (ht_pt_meshes
.contains(pt
) || universe
.contains(title
)) {
775 // remove content from universe
776 universe
.removeContent(title
);
777 // no need to remove entry from table, it's overwritten below
780 ht_pt_meshes
.put(pt
, triangles
);
781 // ensure proper default transform
782 //universe.resetView();
784 Color3f c3
= new Color3f(color
);
787 // create a mesh with the same color and zero transparency (that is, full opacity)
788 CustomTriangleMesh mesh
= new CustomTriangleMesh(triangles
, c3
, 0);
789 // Set mesh properties for double-sided triangles
790 PolygonAttributes pa
= mesh
.getAppearance().getPolygonAttributes();
791 pa
.setCullFace(PolygonAttributes
.CULL_NONE
);
792 pa
.setBackFaceNormalFlip(true);
793 // After setting properties, add to the viewer
794 ct
= universe
.addCustomMesh(mesh
, c3
, title
);
796 ct
= universe
.addTriangleMesh(triangles
, new Color3f(color
), title
);
799 if (null == ct
) return null;
801 // Set general content properties
802 ct
.setTransparency(1f
- alpha
);
803 // Default is unlocked (editable) transformation; set it to locked:
806 } catch (Exception e
) {
812 Utils
.log2(pt
.toString() + " n points: " + triangles
.size());
816 } catch (Exception e
) {
822 executors
.execute(fu
);
826 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
827 static public Future
<Content
> addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
) {
828 return addMesh(ref_ls
, vs
, title
, color
, null, 1.0f
);
831 /** Creates a mesh from the given VectorString3D, which is unbound to any existing Pipe. */
832 static public Future
<Content
> addMesh(final LayerSet ref_ls
, final VectorString3D vs
, final String title
, final Color color
, final double[] widths
, final float alpha
) {
833 final FutureTask
<Content
> fu
= new FutureTask
<Content
>(new Callable
<Content
>() {
834 public Content
call() {
835 Thread
.currentThread().setPriority(Thread
.NORM_PRIORITY
);
838 final Display3D d3d
= Display3D
.get(ref_ls
);
839 final double scale
= d3d
.scale
;
840 final double width
= d3d
.width
;
841 float transp
= 1 - alpha
;
842 if (transp
< 0) transp
= 0;
843 if (transp
> 1) transp
= 1;
845 Utils
.log("WARNING: adding a 3D object fully transparent.");
848 double[] wi
= widths
;
849 if (null == widths
) {
850 wi
= new double[vs
.getPoints(0).length
];
851 //Utils.log2("len: " + wi.length + vs.getPoints(0).length + vs.getPoints(1).length);
852 Arrays
.fill(wi
, 2.0);
853 } else if (widths
.length
!= vs
.length()) {
854 Utils
.log("ERROR: widths.length != VectorString3D.length()");
858 List triangles
= Pipe
.generateTriangles(Pipe
.makeTube(vs
.getPoints(0), vs
.getPoints(1), vs
.getPoints(2), wi
, 1, 12, null), scale
);
862 // add to 3D view (synchronized)
863 synchronized (d3d
.u_lock
) {
866 // ensure proper default transform
867 //d3d.universe.resetView();
869 //Utils.log2(title + " : vertex count % 3 = " + triangles.size() % 3 + " for " + triangles.size() + " vertices");
870 //d3d.universe.ensureScale((float)(width*scale));
871 ct
= d3d
.universe
.addMesh(triangles
, new Color3f(color
), title
, /*(float)(width*scale),*/ 1);
872 ct
.setTransparency(transp
);
874 } catch (Exception e
) {
883 } catch (Exception e
) {
891 launchers
.execute(new Runnable() { public void run() {
892 final Display3D d3d
= Display3D
.get(ref_ls
);
893 d3d
.executors
.execute(fu
);
899 // This method has the exclusivity in adjusting the resampling value.
900 synchronized private final int adjustResampling() {
901 if (resample
> 0) return resample
;
902 final GenericDialog gd
= new GenericDialog("Resample");
903 gd
.addSlider("Resample: ", 1, 20, -1 != resample ? resample
: DEFAULT_RESAMPLE
);
905 if (gd
.wasCanceled()) {
906 resample
= -1 != resample ? resample
: DEFAULT_RESAMPLE
; // current or default value
909 resample
= ((java
.awt
.Scrollbar
)gd
.getSliders().get(0)).getValue();
913 /** Checks if there is any Display3D instance currently showing the given Displayable. */
914 static public boolean isDisplayed(final Displayable d
) {
915 if (null == d
) return false;
916 final String title
= makeTitle(d
);
917 for (Iterator it
= Display3D
.ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
918 Display3D d3d
= (Display3D
)it
.next();
919 if (null != d3d
.universe
.getContent(title
)) return true;
921 if (d
.getClass() == Profile
.class) {
922 Content content
= getProfileContent(d
);
927 /** 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. */
928 static public Content
getProfileContent(final Displayable d
) {
929 if (null == d
) return null;
930 if (d
.getClass() != Profile
.class) return null;
931 Display3D d3d
= get(d
.getLayer().getParent());
932 if (null == d3d
) return null;
933 ProjectThing pt
= d
.getProject().findProjectThing(d
);
934 if (null == pt
) return null;
935 pt
= (ProjectThing
) pt
.getParent();
936 return d3d
.universe
.getContent(new StringBuffer(pt
.toString()).append(" #").append(pt
.getId()).toString());
939 static public void setColor(final Displayable d
, final Color color
) {
940 launchers
.execute(new Runnable() { public void run() {
941 final Display3D d3d
= get(d
.getLayer().getParent());
942 if (null == d3d
) return; // no 3D displays open
943 d3d
.executors
.execute(new Runnable() { public void run() {
944 Content content
= d3d
.universe
.getContent(makeTitle(d
));
945 if (null == content
) content
= getProfileContent(d
);
946 if (null != content
) content
.setColor(new Color3f(color
));
951 static public void setTransparency(final Displayable d
, final float alpha
) {
952 if (null == d
) return;
953 Layer layer
= d
.getLayer();
954 if (null == layer
) return; // some objects have no layer, such as the parent LayerSet.
955 Object ob
= ht_layer_sets
.get(layer
.getParent());
956 if (null == ob
) return;
957 Display3D d3d
= (Display3D
)ob
;
958 String title
= makeTitle(d
);
959 Content content
= d3d
.universe
.getContent(title
);
960 if (null == content
) content
= getProfileContent(d
);
961 if (null != content
) content
.setTransparency(1 - alpha
);
962 else if (null == content
&& d
.getClass().equals(Patch
.class)) {
965 title
= pa
.getProject().getLoader().getFileName(pa
);
966 for (Iterator it
= Display3D
.ht_layer_sets
.values().iterator(); it
.hasNext(); ) {
967 d3d
= (Display3D
)it
.next();
968 for (Iterator cit
= d3d
.universe
.getContents().iterator(); cit
.hasNext(); ) {
969 Content c
= (Content
)cit
.next();
970 if (c
.getName().startsWith(title
)) {
971 c
.setTransparency(1 - alpha
);
972 // no break, since there could be a volume and an orthoslice
980 static public String
makeTitle(final Displayable d
) {
981 return d
.getProject().getMeaningfulTitle(d
) + " #" + d
.getId();
983 static public String
makeTitle(final Patch p
) {
984 return new File(p
.getProject().getLoader().getAbsolutePath(p
)).getName()
985 + " #" + p
.getProject().getLoader().getNextId();
988 /** Remake the mesh for the Displayable in a separate Thread, if it's included in a Display3D
989 * (otherwise returns null). */
990 static public Future
<Content
> update(final Displayable d
) {
991 Layer layer
= d
.getLayer();
992 if (null == layer
) return null; // some objects have no layer, such as the parent LayerSet.
993 Object ob
= ht_layer_sets
.get(layer
.getParent());
994 if (null == ob
) return null;
995 Display3D d3d
= (Display3D
)ob
;
996 return d3d
.addMesh(d
.getProject().findProjectThing(d
), d
, d3d
.resample
);
1000 static public final double computeTriangleArea() {
1001 return 0.5 * Math.sqrt(Math.pow(xA*yB + xB*yC + xC*yA, 2) +
1002 Math.pow(yA*zB + yB*zC + yC*zA, 2) +
1003 Math.pow(zA*xB + zB*xC + zC*xA, 2));
1007 static public final boolean contains(final LayerSet ls
, final String title
) {
1008 final Display3D d3d
= getDisplay(ls
);
1009 if (null == d3d
) return false;
1010 return null != d3d
.universe
.getContent(title
);
1013 static public void destroy() {
1014 launchers
.shutdownNow();