diff --git a/pom.xml b/pom.xml index d78f323..91f8c3b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -97,6 +98,11 @@ https://imagej.net/User:Saalfeld axtimwalde + + Alex Herbert + https://imagej.net/User:Aherbert + aherbert + @@ -169,5 +175,22 @@ org.scijava vecmath + + + + junit + junit + test + + + org.jogamp.gluegen + gluegen-rt-main + test + + + org.jogamp.jogl + jogl-all-main + test + diff --git a/src/main/java/customnode/STLLoader.java b/src/main/java/customnode/STLLoader.java index eebc7e2..4735606 100644 --- a/src/main/java/customnode/STLLoader.java +++ b/src/main/java/customnode/STLLoader.java @@ -209,7 +209,6 @@ private CustomMesh createCustomMesh() { cm = new CustomTriangleMesh(vertices); cm.loadedFromName = name; cm.changed = false; - cm.changed = false; return cm; } diff --git a/src/main/java/customnode/WavefrontExporter.java b/src/main/java/customnode/WavefrontExporter.java index 317f5d1..ad52a84 100644 --- a/src/main/java/customnode/WavefrontExporter.java +++ b/src/main/java/customnode/WavefrontExporter.java @@ -160,13 +160,13 @@ public static void save(final Map meshes, objWriter.write(mat.name); objWriter.write('\n'); // print faces - if (cmesh.getClass() == CustomTriangleMesh.class) writeTriangleFaces( - index, objWriter, name); - else if (cmesh.getClass() == CustomQuadMesh.class) writeQuadFaces(index, + if (cmesh instanceof CustomTriangleMesh) + writeTriangleFaces(index, objWriter, name); + else if (cmesh instanceof CustomQuadMesh) writeQuadFaces(index, objWriter, name); - else if (cmesh.getClass() == CustomPointMesh.class) writePointFaces( + else if (cmesh instanceof CustomPointMesh) writePointFaces( index, objWriter, name); - else if (cmesh.getClass() == CustomLineMesh.class) { + else if (cmesh instanceof CustomLineMesh) { final CustomLineMesh clm = (CustomLineMesh) cmesh; switch (clm.getMode()) { case CustomLineMesh.PAIRWISE: diff --git a/src/main/java/ij3d/Content.java b/src/main/java/ij3d/Content.java index 0b8d10a..6d16615 100644 --- a/src/main/java/ij3d/Content.java +++ b/src/main/java/ij3d/Content.java @@ -122,7 +122,7 @@ public Content(final String name, public void addInstant(final ContentInstant ci) { final int timepoint = ci.timepoint; contents.put(timepoint, ci); - if (!contents.containsKey(timepoint)) { + if (!timepointToSwitchIndex.containsKey(timepoint)) { timepointToSwitchIndex.put(timepoint, contentSwitch.numChildren()); contentSwitch.addChild(ci); } diff --git a/src/main/java/ij3d/Executer.java b/src/main/java/ij3d/Executer.java index 4cec5e1..8209c72 100644 --- a/src/main/java/ij3d/Executer.java +++ b/src/main/java/ij3d/Executer.java @@ -359,7 +359,7 @@ public void saveAsU3D() { } public void loadView() { - final OpenDialog sd = new OpenDialog("Open view...", "", ".view"); + final OpenDialog sd = new OpenDialog("Open view...", OpenDialog.getDefaultDirectory(), ".view"); final String dir = sd.getDirectory(); final String name = sd.getFileName(); if (dir == null || name == null) return; @@ -386,7 +386,7 @@ public void saveView() { public void loadSession() { final OpenDialog sd = - new OpenDialog("Open session...", "session", ".scene"); + new OpenDialog("Open session...", OpenDialog.getDefaultDirectory(), ".scene"); final String dir = sd.getDirectory(); final String name = sd.getFileName(); if (dir == null || name == null) return; diff --git a/src/main/java/ij3d/Image3DUniverse.java b/src/main/java/ij3d/Image3DUniverse.java index 570b32f..fdaecdf 100644 --- a/src/main/java/ij3d/Image3DUniverse.java +++ b/src/main/java/ij3d/Image3DUniverse.java @@ -129,6 +129,7 @@ public class Image3DUniverse extends DefaultAnimatableUniverse { /** A reference to the universe's shortcuts */ private ShortCuts shortcuts; + /** A reference to the universe's context menu */ private ContextMenu contextmenu; /** @@ -210,11 +211,16 @@ public void mouseClicked(final MouseEvent e) { @Override public void mousePressed(final MouseEvent e) { - contextmenu.showPopup(e); + // Remove redundant call to show pop-up. + // Pop-up is shown on mouse release allowing the mouse + // to be moved over content and the menu will be + // for the correct selected content + //contextmenu.showPopup(e); } @Override public void mouseReleased(final MouseEvent e) { + if (e.isConsumed()) return; contextmenu.showPopup(e); } }); @@ -333,6 +339,7 @@ public boolean isFullScreen() { */ @Override public void cleanup() { + sync(false); timeline.pause(); removeAllContents(); contents.clear(); @@ -443,7 +450,24 @@ public Executer getExecuter() { public ShortCuts getShortcuts() { return shortcuts; } + + /** + * Refresh the shortcuts using the menubar. This should be called if the standard + * menubar for the ImageJ3DWindow has been modified. + * + * @see #getMenuBar() + */ + public void refreshShortcuts() { + shortcuts = new ShortCuts(menubar); + } + /** + * Returns a reference to the universe's context menu. + */ + public ContextMenu getContextmenu() { + return contextmenu; + } + /** * Returns a reference to the PointListDialog used by this universe */ @@ -647,8 +671,8 @@ public void recalculateGlobalMinMax() { final Point3d min = new Point3d(); final Point3d max = new Point3d(); - final Iterator it = contents(); - Content c = (Content) it.next(); + final Iterator it = contents(); + Content c = it.next(); c.getMin(min); c.getMax(max); globalMin.set(min); @@ -1424,7 +1448,7 @@ public void removeContent(final String name) { * universe. */ @Override - public Iterator contents() { + public Iterator contents() { return contents.values().iterator(); } @@ -1434,7 +1458,7 @@ public Iterator contents() { * * @return */ - public Collection getContents() { + public Collection getContents() { if (contents == null) return null; return contents.values(); } @@ -1495,7 +1519,7 @@ public void loadView(final String file) throws IOException { public void resetView() { fireTransformationStarted(); - // rotate so that y shows downwards + // rotate so that y shows downwards, z-axis away from the viewer final Transform3D t = new Transform3D(); final AxisAngle4d aa = new AxisAngle4d(1, 0, 0, Math.PI); t.set(aa); @@ -1521,72 +1545,84 @@ public void resetView() { * @param angle The angle in radians. */ public void rotateUniverse(final Vector3d axis, final double angle) { + fireTransformationStarted(); viewTransformer.rotate(axis, angle); + fireTransformationUpdated(); + fireTransformationFinished(); } /** - * Rotate the univere so that the user looks in the negative direction of the - * z-axis. + * Resets the rotation transform. This is used to provide specific view orientations. + * + * @param aa2 the new rotation transform (applied to the reset rotation) */ - public void rotateToNegativeXY() { + private void resetRotationTransform(AxisAngle4d aa2) { fireTransformationStarted(); - getRotationTG().setTransform(new Transform3D()); + // rotate so that y shows downwards, z-axis away from the viewer + // (i.e. reset the rotation). This is equivalent to rotateToPositiveXY. + final Transform3D t = new Transform3D(); + final AxisAngle4d aa = new AxisAngle4d(1, 0, 0, Math.PI); + t.set(aa); + if (aa2 != null) + { + final Transform3D t2 = new Transform3D(); + t2.set(aa2); + t.mul(t2); + } + getRotationTG().setTransform(t); + // TODO - The bounding box and coordinate system are not refreshed. + fireTransformationUpdated(); fireTransformationFinished(); } - + + // The methods below rotate the default view to look along a given axis + /** - * Rotate the univere so that the user looks in the positive direction of the + * Rotate the universe so that the user looks in the negative direction of the * z-axis. */ + public void rotateToNegativeXY() { + resetRotationTransform(new AxisAngle4d(0, 1, 0, Math.PI)); + } + + /** + * Rotate the universe so that the user looks in the positive direction of the + * z-axis. This is the default view. + */ public void rotateToPositiveXY() { - fireTransformationStarted(); - getRotationTG().setTransform(new Transform3D()); - waitForNextFrame(); - rotateUniverse(new Vector3d(0, 1, 0), Math.PI); + resetRotationTransform(null); } /** - * Rotate the univere so that the user looks in the negative direction of the + * Rotate the universe so that the user looks in the negative direction of the * y-axis. */ public void rotateToNegativeXZ() { - fireTransformationStarted(); - getRotationTG().setTransform(new Transform3D()); - waitForNextFrame(); - rotateUniverse(new Vector3d(1, 0, 0), Math.PI / 2); + resetRotationTransform(new AxisAngle4d(1, 0, 0, Math.PI / 2)); } /** - * Rotate the univere so that the user looks in the positive direction of the + * Rotate the universe so that the user looks in the positive direction of the * y-axis. */ public void rotateToPositiveXZ() { - fireTransformationStarted(); - getRotationTG().setTransform(new Transform3D()); - waitForNextFrame(); - rotateUniverse(new Vector3d(0, 1, 0), -Math.PI / 2); + resetRotationTransform(new AxisAngle4d(1, 0, 0, -Math.PI / 2)); } /** - * Rotate the univere so that the user looks in the negative direction of the + * Rotate the universe so that the user looks in the negative direction of the * x-axis. */ public void rotateToNegativeYZ() { - fireTransformationStarted(); - getRotationTG().setTransform(new Transform3D()); - waitForNextFrame(); - rotateUniverse(new Vector3d(0, 1, 0), Math.PI / 2); + resetRotationTransform(new AxisAngle4d(0, 1, 0, Math.PI / 2)); } /** - * Rotate the univere so that the user looks in the positive direction of the + * Rotate the universe so that the user looks in the positive direction of the * x-axis. */ public void rotateToPositiveYZ() { - fireTransformationStarted(); - getRotationTG().setTransform(new Transform3D()); - waitForNextFrame(); - rotateUniverse(new Vector3d(1, 0, 0), -Math.PI / 2); + resetRotationTransform(new AxisAngle4d(0, 1, 0, -Math.PI / 2)); } /** diff --git a/src/main/java/ij3d/UniverseSynchronizer.java b/src/main/java/ij3d/UniverseSynchronizer.java index 3c1d55a..8af235a 100644 --- a/src/main/java/ij3d/UniverseSynchronizer.java +++ b/src/main/java/ij3d/UniverseSynchronizer.java @@ -47,7 +47,13 @@ public void transformationUpdated(final View view) { @Override public void universeClosed() { - removeUniverse(u); + //removeUniverse(u); + // Do not call removeUniverse(u) + // as this will remove this adapter from the list of universe listeners + // (i.e. u.removeUniverseListener(l)) + // so modifying the current list of listeners on which universeClosed() + // is currently being invoked by the closing universe. + universes.remove(u); } }; u.addUniverseListener(l); @@ -75,6 +81,7 @@ private static final void setGlobalTransform(final Image3DUniverse u, final int num = group.getNumTransforms(); for (int i = 0; i < num; i++) group.getTransformGroup(i).setTransform(transform.transforms[i]); + u.getViewPlatformTransformer().updateFrontBackClip(); u.fireTransformationUpdated(); } diff --git a/src/main/java/ij3d/behaviors/InteractiveBehavior.java b/src/main/java/ij3d/behaviors/InteractiveBehavior.java index 319caef..dfe3042 100644 --- a/src/main/java/ij3d/behaviors/InteractiveBehavior.java +++ b/src/main/java/ij3d/behaviors/InteractiveBehavior.java @@ -323,7 +323,12 @@ else if (c != null && c.getType() == ContentConstants.ORTHO && axis != -1) case KeyEvent.VK_PAGE_DOWN: viewTransformer.zoom(-1); return; - + case KeyEvent.VK_COMMA: + viewTransformer.rotateZ(TWO_RAD); + return; + case KeyEvent.VK_PERIOD: + viewTransformer.rotateZ(-TWO_RAD); + return; } } // If we arrive here, the event was not handled. diff --git a/src/main/java/ij3d/shapes/BoundingBox.java b/src/main/java/ij3d/shapes/BoundingBox.java index 689f85e..ffa233d 100644 --- a/src/main/java/ij3d/shapes/BoundingBox.java +++ b/src/main/java/ij3d/shapes/BoundingBox.java @@ -89,6 +89,9 @@ public BoundingBox(final Point3f minp, final Point3f maxp, final Color3f color) final float ly = max.y - min.y; final float lz = max.z - min.z; final float max = Math.max(lx, Math.max(ly, lz)); + if (max <= 0) + // No bounding box + return; float min = Math.min(lx, Math.min(ly, lz)); if (min == 0 || max / min > 100) min = max; double tmp = 0.00001f; @@ -198,12 +201,17 @@ private Appearance createAppearance(final Color3f color) { private Geometry makeLine(final Point3f start, final Point3f end, final Color3f color, final float tickDistance, final float first, - final float tickSize, final boolean noTicks) + final float tickSize, boolean noTicks) { final float lineLength = start.distance(end); final int nTicks = (int) Math.floor((lineLength - first) / tickDistance) + 1; + // Avoid negative array size. This occurs when the (lineLength - first) + // is negative. + if (nTicks < 1) + noTicks = true; + final int n = noTicks ? 2 : nTicks * 6 + 2; final Point3f[] coords = new Point3f[n]; diff --git a/src/main/java/ij3d/shapes/CoordinateSystem.java b/src/main/java/ij3d/shapes/CoordinateSystem.java index fdbcf4c..bd4502b 100644 --- a/src/main/java/ij3d/shapes/CoordinateSystem.java +++ b/src/main/java/ij3d/shapes/CoordinateSystem.java @@ -49,6 +49,10 @@ public CoordinateSystem(final float length, final Color3f color) { this.length = length; this.color = color; setCapability(BranchGroup.ALLOW_DETACH); + + // No coordinate system + if (length <= 0) + return; final Shape3D lines = new Shape3D(); lines.setGeometry(createGeometry()); diff --git a/src/main/java/ij3d/shortcuts/ShortCuts.java b/src/main/java/ij3d/shortcuts/ShortCuts.java index b8f48d6..a6ec667 100644 --- a/src/main/java/ij3d/shortcuts/ShortCuts.java +++ b/src/main/java/ij3d/shortcuts/ShortCuts.java @@ -33,13 +33,15 @@ import ij3d.UniverseSettings; -public class ShortCuts { +public class ShortCuts +{ private final List commands; private final HashMap items; private final HashMap shortcuts; - public ShortCuts(final JMenuBar menubar) { + public ShortCuts(final JMenuBar menubar) + { commands = new ArrayList(); items = new HashMap(); shortcuts = UniverseSettings.shortcuts; @@ -47,63 +49,94 @@ public ShortCuts(final JMenuBar menubar) { for (int i = 0; i < menubar.getMenuCount(); i++) scan(menubar.getMenu(i), ""); - for (final String command : commands) { + for (final String command : commands) + { final String shortcut = shortcuts.get(command); - if (shortcut != null) setShortCut(command, shortcut); + if (shortcut != null) + setShortCut(command, shortcut); } } - public void save() { + public void save() + { UniverseSettings.save(); } - public void reload() { + public void reload() + { UniverseSettings.load(); } - public Iterable getCommands() { + public Iterable getCommands() + { return commands; } - public String getShortCut(final String command) { + public String getShortCut(final String command) + { return shortcuts.get(command); } - public void setShortCut(final String command, final String shortcut) { - if (shortcut.trim().length() == 0) { + public void setShortCut(final String command, final String shortcut) + { + if (shortcut.trim().length() == 0) + { clearShortCut(command); return; } shortcuts.put(command, shortcut); final KeyStroke stroke = KeyStroke.getKeyStroke(shortcut); - items.get(command).setAccelerator(stroke); + JMenuItem o = items.get(command); + if (o != null) + o.setAccelerator(stroke); } - public void clearShortCut(final String command) { - items.get(command).setAccelerator(null); + public void clearShortCut(final String command) + { + JMenuItem o = items.get(command); + if (o != null) + o.setAccelerator(null); shortcuts.remove(command); } - public int getNumberOfCommands() { + public int getNumberOfCommands() + { return commands.size(); } - public String getCommand(final int i) { + public String getCommand(final int i) + { return commands.get(i); } - private void scan(final JMenu menu, String prefix) { + private void scan(final JMenu menu, String prefix) + { prefix += menu.getText() + " > "; - for (int i = 0; i < menu.getItemCount(); i++) { + for (int i = 0; i < menu.getItemCount(); i++) + { final JMenuItem mi = menu.getItem(i); - if (mi == null) continue; - if (mi instanceof JMenu) { + if (mi == null) + continue; + if (mi instanceof JMenu) + { scan((JMenu) mi, prefix); } - else { + else + { final String c = prefix + mi.getText(); commands.add(c); items.put(c, mi); + // Detect accelerators in menu items if not in the current settings. + // This is relevant when the standard menubar has been changed with + // extra items. + if (!shortcuts.containsKey(c)) + { + KeyStroke ks = mi.getAccelerator(); + if (ks != null) + { + shortcuts.put(c, ks.toString()); + } + } } } } diff --git a/src/test/java/ij3d/shapes/BoundingBoxTest.java b/src/test/java/ij3d/shapes/BoundingBoxTest.java new file mode 100644 index 0000000..facf9c8 --- /dev/null +++ b/src/test/java/ij3d/shapes/BoundingBoxTest.java @@ -0,0 +1,18 @@ +package ij3d.shapes; + +import org.junit.Test; +import org.scijava.vecmath.Point3d; + +public class BoundingBoxTest +{ + @Test + public void canCreateWithNoRange() + { + // This used to cause a negative array size exception. + // It is relevant when adding content that is a single + // coordinate, e.g. a CustomPointMesh with 1 point. + double d = Double.parseDouble("-25.632442551701516"); + Point3d min = new Point3d(d, d, d); + new BoundingBox(min, min); + } +} \ No newline at end of file