diff --git a/pom.xml b/pom.xml index 242fda1e..7c3827be 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ deploy-to-scijava - 94e8d97 + 9d42647 2.0.3 a40d46f diff --git a/src/main/java/sc/iview/vector/FloatVector3.java b/src/main/java/sc/iview/ImageJMain.kt similarity index 63% rename from src/main/java/sc/iview/vector/FloatVector3.java rename to src/main/java/sc/iview/ImageJMain.kt index 4c57084a..7f98b55f 100644 --- a/src/main/java/sc/iview/vector/FloatVector3.java +++ b/src/main/java/sc/iview/ImageJMain.kt @@ -6,13 +6,13 @@ * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -26,39 +26,35 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.vector; +package sc.iview + +import net.imagej.lut.LUTService +import net.imagej.ops.OpService +import net.imagej.units.UnitService +import org.scijava.Context +import org.scijava.console.ConsoleService +import org.scijava.io.IOService +import org.scijava.menu.MenuService +import org.scijava.options.OptionsService +import org.scijava.tool.ToolService +import org.scijava.ui.UIService +import org.scijava.ui.swing.SwingIconService /** - * {@link Vector3} backed by three {@code float}s. - * - * @author Curtis Rueden + * Entry point for testing SciView functionality. + * * @author Kyle Harrington + * @author Ulrik Guenther */ -public class FloatVector3 implements Vector3 { - - private float x, y, z; - - public FloatVector3( float x, float y, float z ) { - this.x = x; - this.y = y; - this.z = z; - } - - @Override public float xf() { return x; } - @Override public float yf() { return y; } - @Override public float zf() { return z; } - - @Override public void setX( float position ) { x = position; } - @Override public void setY( float position ) { y = position; } - @Override public void setZ( float position ) { z = position; } - - @Override - public Vector3 copy() { - return new FloatVector3(xf(),yf(),zf()); - } +object ImageJMain { + @Throws(Exception::class) + @JvmStatic + fun main(args: Array) { + val context = Context() + val uiService = context.service(UIService::class.java) + val sciViewService = context.service(SciViewService::class.java) - @Override - public String toString() { - return "[" + xf() + "; " + yf() + "; " + zf() + "]"; + uiService.showUI() + sciViewService.createSciView() } } diff --git a/src/main/java/sc/iview/SciView.java b/src/main/java/sc/iview/SciView.java deleted file mode 100644 index e396c8b8..00000000 --- a/src/main/java/sc/iview/SciView.java +++ /dev/null @@ -1,2727 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview; - -import bdv.BigDataViewer; -import bdv.cache.CacheControl; -import bdv.tools.brightness.ConverterSetup; -import bdv.util.AxisOrder; -import bdv.util.RandomAccessibleIntervalSource; -import bdv.util.RandomAccessibleIntervalSource4D; -import bdv.util.volatiles.VolatileView; -import bdv.util.volatiles.VolatileViewData; -import bdv.viewer.Source; -import bdv.viewer.SourceAndConverter; -import cleargl.GLVector; -import com.formdev.flatlaf.FlatLightLaf; -import com.intellij.ui.tabs.JBTabsPosition; -import com.intellij.ui.tabs.TabInfo; -import com.intellij.ui.tabs.impl.JBEditorTabs; -import graphics.scenery.Box; -import graphics.scenery.*; -import graphics.scenery.backends.RenderedImage; -import graphics.scenery.backends.Renderer; -import graphics.scenery.backends.opengl.OpenGLRenderer; -import graphics.scenery.backends.vulkan.VulkanRenderer; -import graphics.scenery.controls.InputHandler; -import graphics.scenery.controls.OpenVRHMD; -import graphics.scenery.controls.TrackerInput; -import graphics.scenery.controls.behaviours.ArcballCameraControl; -import graphics.scenery.controls.behaviours.FPSCameraControl; -import graphics.scenery.controls.behaviours.MovementCommand; -import graphics.scenery.controls.behaviours.SelectCommand; -import graphics.scenery.utils.*; -import graphics.scenery.volumes.Colormap; -import graphics.scenery.volumes.RAIVolume; -import graphics.scenery.volumes.TransferFunction; -import graphics.scenery.volumes.Volume; -import io.scif.SCIFIOService; -import kotlin.Unit; -import kotlin.jvm.functions.Function0; -import kotlin.jvm.functions.Function1; -import kotlin.jvm.functions.Function3; -import net.imagej.Dataset; -import net.imagej.ImageJService; -import net.imagej.axis.CalibratedAxis; -import net.imagej.axis.DefaultAxisType; -import net.imagej.axis.DefaultLinearAxis; -import net.imagej.interval.CalibratedRealInterval; -import net.imagej.lut.LUTService; -import net.imagej.ops.OpService; -import net.imagej.units.UnitService; -import net.imglib2.Cursor; -import net.imglib2.RandomAccess; -import net.imglib2.*; -import net.imglib2.display.ColorTable; -import net.imglib2.img.Img; -import net.imglib2.realtransform.AffineTransform3D; -import net.imglib2.type.numeric.ARGBType; -import net.imglib2.type.numeric.RealType; -import net.imglib2.type.numeric.integer.UnsignedByteType; -import net.imglib2.view.Views; -import org.jetbrains.annotations.NotNull; -import org.joml.Quaternionf; -import org.joml.Vector2f; -import org.joml.Vector3f; -import org.lwjgl.system.Platform; -import org.scijava.Context; -import org.scijava.command.CommandService; -import org.scijava.display.Display; -import org.scijava.display.DisplayService; -import org.scijava.event.EventHandler; -import org.scijava.event.EventService; -import org.scijava.io.IOService; -import org.scijava.log.LogLevel; -import org.scijava.log.LogService; -import org.scijava.menu.MenuService; -import org.scijava.object.ObjectService; -import org.scijava.plugin.Parameter; -import org.scijava.service.SciJavaService; -import org.scijava.thread.ThreadService; -import org.scijava.ui.behaviour.Behaviour; -import org.scijava.ui.behaviour.ClickBehaviour; -import org.scijava.ui.behaviour.InputTrigger; -import org.scijava.ui.swing.menu.SwingJMenuBarCreator; -import org.scijava.util.ColorRGB; -import org.scijava.util.Colors; -import org.scijava.util.VersionUtils; -import sc.iview.commands.help.Help; -import sc.iview.commands.view.NodePropertyEditor; -import sc.iview.controls.behaviours.*; -import sc.iview.event.NodeActivatedEvent; -import sc.iview.event.NodeAddedEvent; -import sc.iview.event.NodeChangedEvent; -import sc.iview.event.NodeRemovedEvent; -import sc.iview.process.MeshConverter; -import sc.iview.ui.ContextPopUpNodeChooser; -import sc.iview.ui.REPLPane; -import sc.iview.vector.JOMLVector3; -import sc.iview.vector.Vector3; -import tpietzsch.example2.VolumeViewerOptions; - -import javax.imageio.ImageIO; -import javax.script.ScriptException; -import javax.swing.*; -import java.awt.Image; -import java.awt.*; -import java.awt.event.*; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; -import java.io.*; -import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.List; -import java.util.Queue; -import java.util.*; -import java.util.concurrent.Future; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -/** - * Main SciView class. - * - * @author Kyle Harrington - */ -// we suppress unused warnings here because @Parameter-annotated fields -// get updated automatically by SciJava. -@SuppressWarnings({"unused", "WeakerAccess"}) -public class SciView extends SceneryBase implements CalibratedRealInterval { - - public static final ColorRGB DEFAULT_COLOR = Colors.LIGHTGRAY; - private final SceneryPanel[] sceneryPanel = { null }; - /** - * Mouse controls for FPS movement and Arcball rotation - */ - protected AnimatedCenteringBeforeArcBallControl targetArcball; - protected FPSCameraControl fpsControl; - /** - * The floor that orients the user in the scene - */ - protected Node floor; - protected boolean vrActive = false; - /** - * The primary camera/observer in the scene - */ - Camera camera = null; - /** - * Geometry/Image information of scene - */ - private CalibratedAxis[] axes; - - @Parameter - private LogService log; - @Parameter - private MenuService menus; - @Parameter - private IOService io; - @Parameter - private OpService ops; - @Parameter - private EventService eventService; - @Parameter - private DisplayService displayService; - @Parameter - private LUTService lutService; - @Parameter - private ThreadService threadService; - @Parameter - private ObjectService objectService; - @Parameter - private UnitService unitService; - /** - * Queue keeps track of the currently running animations - **/ - private Queue animations; - /** - * Animation pause tracking - **/ - private boolean animating; - /** - * This tracks the actively selected Node in the scene - */ - private Node activeNode = null; - - /** - * Speeds for input controls - */ - public ControlsParameters controlsParameters = new ControlsParameters(); - - private Display scijavaDisplay; - private SplashLabel splashLabel; - private SceneryJPanel panel; - private JSplitPane mainSplitPane; - private JSplitPane inspector; - private JSplitPane interpreterSplitPane; - private REPLPane interpreterPane; - private NodePropertyEditor nodePropertyEditor; - private ArrayList lights; - private Stack> controlStack; - private JFrame frame; - private Predicate notAbstractNode = (Predicate) node -> !( (node instanceof Camera) || (node instanceof Light) || (node==getFloor())); - private boolean isClosed = false; - private Function> notAbstractBranchingFunction = node -> node.getChildren().stream().filter(notAbstractNode).collect(Collectors.toList()); - - // If true, then when a new node is added to the scene, the camera will refocus on this node by default - private boolean centerOnNewNodes; - - // If true, then when a new node is added the thread will block until the node is added to the scene. This is required for - // centerOnNewNodes - private boolean blockOnNewNodes; - private PointLight headlight; - - public SciView( Context context ) { - super( "SciView", 1280, 720, false, context ); - context.inject( this ); - } - - public SciView( String applicationName, int windowWidth, int windowHeight ) { - super( applicationName, windowWidth, windowHeight, false ); - } - - public boolean isClosed() { - return isClosed; - } - - public InputHandler publicGetInputHandler() { - return getInputHandler(); - } - - /** - * Toggle video recording with scenery's video recording mechanism - * Note: this video recording may skip frames because it is asynchronous - */ - public void toggleRecordVideo() { - if( getRenderer() instanceof OpenGLRenderer ) - ((OpenGLRenderer)getRenderer()).recordMovie(); - else - ((VulkanRenderer)getRenderer()).recordMovie(); - } - - /** - * Toggle video recording with scenery's video recording mechanism - * Note: this video recording may skip frames because it is asynchronous - * - * @param filename destination for saving video - * @param overwrite should the file be replaced, otherwise a unique incrementing counter will be appended - */ - public void toggleRecordVideo(String filename, boolean overwrite) { - if( getRenderer() instanceof OpenGLRenderer ) - ((OpenGLRenderer)getRenderer()).recordMovie(filename, overwrite); - else - ((VulkanRenderer)getRenderer()).recordMovie(filename, overwrite); - } - - /** - * This pushes the current input setup onto a stack that allows them to be restored with restoreControls. - * It stacks in particular: all keybindings, all Behaviours, and all step sizes and mouse sensitivities - * (which are held together in {@link ControlsParameters}). - * - * Word of warning: The stashing memorizes references only on currently used controls - * (such as, e.g., {@link MovementCommand}, {@link FPSCameraControl} or {@link NodeTranslateControl}), - * it does not create an extra copy of any control. That said, if you modify any control - * object despite it was already stashed with this method, the change will be visible in the "stored" - * control and will not go away after the restore... To be on the safe side for now at least, create - * new and modified controls rather than directly changing them. - */ - public void stashControls() { - final InputHandler inputHandler = getInputHandler(); - if (inputHandler == null) { - getLogger().error( "InputHandler is null, cannot store controls" ); - return; - } - - final HashMap controlState = new HashMap<>(); - - //behaviours: - for ( String actionName: inputHandler.getAllBehaviours() ) - controlState.put( STASH_BEHAVIOUR_KEY+actionName, inputHandler.getBehaviour(actionName) ); - - //bindings: - for ( String actionName: inputHandler.getAllBehaviours() ) - for ( InputTrigger trigger : inputHandler.getKeyBindings(actionName) ) - controlState.put( STASH_BINDING_KEY+actionName, trigger.toString() ); - - //finally, stash the control parameters - controlState.put( STASH_CONTROLSPARAMS_KEY, controlsParameters ); - - //...and stash it! - controlStack.push(controlState); - } - - /** - * This pops/restores the previously stashed controls. Emits a warning if there are no stashed controls. - * It first clears all controls, and then resets in particular: all keybindings, all Behaviours, - * and all step sizes and mouse sensitivities (which are held together in {@link ControlsParameters}). - * - * Some recent changes may not be removed with this restore -- - * see discussion in the docs of {@link SciView#stashControls()} for more details. - */ - public void restoreControls() { - if (controlStack.empty()) { - getLogger().warn("Not restoring controls, the controls stash stack is empty!"); - return; - } - - final InputHandler inputHandler = getInputHandler(); - if (inputHandler == null) { - getLogger().error( "InputHandler is null, cannot restore controls" ); - return; - } - - //clear the input handler entirely - for ( String actionName : inputHandler.getAllBehaviours() ) { - inputHandler.removeKeyBinding( actionName ); - inputHandler.removeBehaviour( actionName ); - } - - //retrieve the most recent stash with controls - final HashMap controlState = controlStack.pop(); - for (Map.Entry control : controlState.entrySet()) { - String key; - if (control.getKey().startsWith(STASH_BEHAVIOUR_KEY)) { - //processing behaviour - key = control.getKey().substring(STASH_BEHAVIOUR_KEY.length()); - inputHandler.addBehaviour( key, (Behaviour)control.getValue() ); - } - else - if (control.getKey().startsWith(STASH_BINDING_KEY)) { - //processing key binding - key = control.getKey().substring(STASH_BINDING_KEY.length()); - inputHandler.addKeyBinding( key, (String)control.getValue() ); - } - else - if (control.getKey().startsWith(STASH_CONTROLSPARAMS_KEY)) { - //processing mouse sensitivities and step sizes... - controlsParameters = (ControlsParameters)control.getValue(); - } - } - } - - private static final String STASH_BEHAVIOUR_KEY = "behaviour:"; - private static final String STASH_BINDING_KEY = "binding:"; - private static final String STASH_CONTROLSPARAMS_KEY = "parameters:"; - - public ArrayList getLights() { - return lights; - } - - /** - * Reset the scene to initial conditions - */ - public void reset() { - // Initialize the 3D axes - axes = new CalibratedAxis[3]; - - axes[0] = new DefaultLinearAxis(new DefaultAxisType("X", true), "um", 1); - axes[1] = new DefaultLinearAxis(new DefaultAxisType("Y", true), "um", 1); - axes[2] = new DefaultLinearAxis(new DefaultAxisType("Z", true), "um", 1); - - // Remove everything except camera - Node[] toRemove = getSceneNodes( n -> !( n instanceof Camera ) ); - for( Node n : toRemove ) { - deleteNode(n, false); - } - - // Setup camera - Camera cam; - if( getCamera() == null ) { - cam = new DetachedHeadCamera(); - this.camera = cam; - cam.setPosition(new Vector3f(0.0f, 1.65f, 0.0f)); - getScene().addChild( cam ); - } else { - cam = getCamera(); - } - cam.setPosition( new Vector3f( 0.0f, 1.65f, 5.0f ) ); - cam.perspectiveCamera( 50.0f, getWindowWidth(), getWindowHeight(), 0.1f, 1000.0f ); - - // Setup lights - Vector3f[] tetrahedron = new Vector3f[4]; - tetrahedron[0] = new Vector3f( 1.0f, 0f, -1.0f/(float)Math.sqrt(2.0f) ); - tetrahedron[1] = new Vector3f( -1.0f,0f,-1.0f/(float)Math.sqrt(2.0) ); - tetrahedron[2] = new Vector3f( 0.0f,1.0f,1.0f/(float)Math.sqrt(2.0) ); - tetrahedron[3] = new Vector3f( 0.0f,-1.0f,1.0f/(float)Math.sqrt(2.0) ); - - lights = new ArrayList<>(); - - for( int i = 0; i < 4; i++ ) {// TODO allow # initial lights to be customizable? - PointLight light = new PointLight(150.0f); - light.setPosition( tetrahedron[i].mul(25.0f) ); - light.setEmissionColor( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - light.setIntensity( 1.0f ); - lights.add( light ); - //camera.addChild( light ); - getScene().addChild( light ); - } - - // Make a headlight for the camera - headlight = new PointLight(150.0f); - headlight.setPosition( new Vector3f(0f, 0f, -1f).mul(25.0f) ); - headlight.setEmissionColor( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - headlight.setIntensity( 0.5f ); - headlight.setName("headlight"); - - - Icosphere lightSphere = new Icosphere(1.0f, 2); - headlight.addChild(lightSphere); - lightSphere.getMaterial().setDiffuse(headlight.getEmissionColor()); - lightSphere.getMaterial().setSpecular(headlight.getEmissionColor()); - lightSphere.getMaterial().setAmbient(headlight.getEmissionColor()); - lightSphere.getMaterial().setWireframe(true); - lightSphere.setVisible(false); - //lights.add( light ); - camera.setNearPlaneDistance(0.01f); - camera.setFarPlaneDistance(1000.0f); - camera.addChild( headlight ); - - floor = new InfinitePlane();//new Box( new Vector3f( 500f, 0.2f, 500f ) ); - ((InfinitePlane)floor).setType(InfinitePlane.Type.Grid); - floor.setName( "Floor" ); - getScene().addChild( floor ); - - } - - /** - * Initialization of SWING and scenery. Also triggers an initial population of lights/camera in the scene - */ - @SuppressWarnings("restriction") @Override public void init() { - - // Darcula dependency went missing from maven repo, factor it out -// if(Boolean.parseBoolean(System.getProperty("sciview.useDarcula", "false"))) { -// try { -// BasicLookAndFeel darcula = new DarculaLaf(); -// UIManager.setLookAndFeel(darcula); -// } catch (Exception e) { -// getLogger().info("Could not load Darcula Look and Feel"); -// } -// } - final String logLevel = System.getProperty("scenery.LogLevel", "info"); - log.setLevel(LogLevel.value(logLevel)); - - LogbackUtils.setLogLevel(null, logLevel); - - System.getProperties().stringPropertyNames().forEach(name -> { - if(name.startsWith("scenery.LogLevel")) { - LogbackUtils.setLogLevel("", System.getProperty(name, "info")); - } - }); - - // determine imagej-launcher version and to disable Vulkan if XInitThreads() fix - // is not deployed - try { - final Class launcherClass = Class.forName("net.imagej.launcher.ClassLauncher"); - String versionString = VersionUtils.getVersion(launcherClass); - - if (versionString != null && ExtractsNatives.Companion.getPlatform() == ExtractsNatives.Platform.LINUX) { - versionString = versionString.substring(0, 5); - - final Version launcherVersion = new Version(versionString); - final Version nonWorkingVersion = new Version("4.0.5"); - - if (launcherVersion.compareTo(nonWorkingVersion) <= 0 - && !Boolean.parseBoolean(System.getProperty("sciview.DisableLauncherVersionCheck", "false"))) { - getLogger().info("imagej-launcher version smaller or equal to non-working version (" + versionString + " vs. 4.0.5), disabling Vulkan as rendering backend. Disable check by setting 'scenery.DisableLauncherVersionCheck' system property to 'true'."); - System.setProperty("scenery.Renderer", "OpenGLRenderer"); - } else { - getLogger().info("imagej-launcher version bigger that non-working version (" + versionString + " vs. 4.0.5), all good."); - } - } - } catch (ClassNotFoundException cnfe) { - // Didn't find the launcher, so we're probably good. - getLogger().info("imagej-launcher not found, not touching renderer preferences."); - } - - // TODO: check for jdk 8 v. jdk 11 on linux and choose renderer accordingly - if( Platform.get() == Platform.LINUX ) { - String version = System.getProperty("java.version"); - if( version.startsWith("1.") ) { - version = version.substring(2, 3); - } else { - int dot = version.indexOf("."); - if (dot != -1) { - version = version.substring(0, dot); - } - } - - // If Linux and JDK 8, then use OpenGLRenderer - if( version.equals("8") ) - System.setProperty("scenery.Renderer", "OpenGLRenderer"); - } - - int x, y; - - try { - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - - x = screenSize.width/2 - getWindowWidth()/2; - y = screenSize.height/2 - getWindowHeight()/2; - } catch(HeadlessException e) { - x = 10; - y = 10; - } - - frame = new JFrame("SciView"); - frame.setLayout(new BorderLayout(0, 0)); - frame.setSize(getWindowWidth(), getWindowHeight()); - frame.setLocation(x, y); - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - nodePropertyEditor = new NodePropertyEditor( this ); - - final JPanel p = new JPanel(new BorderLayout(0, 0)); - panel = new SceneryJPanel(); - - JPopupMenu.setDefaultLightWeightPopupEnabled(false); - final JMenuBar swingMenuBar = new JMenuBar(); - new SwingJMenuBarCreator().createMenus(menus.getMenu("SciView"), swingMenuBar); - frame.setJMenuBar(swingMenuBar); - - p.setLayout(new OverlayLayout(p)); - p.setBackground(new Color(50, 48, 47)); - p.add(panel, BorderLayout.CENTER); - panel.setVisible(true); - - nodePropertyEditor.getComponent(); // Initialize node property panel - - JTree inspectorTree = nodePropertyEditor.getTree(); - inspectorTree.setToggleClickCount(0);// This disables expanding menus on double click - JPanel inspectorProperties = nodePropertyEditor.getProps(); - - JBEditorTabs tp = new JBEditorTabs(null); - tp.setTabsPosition(JBTabsPosition.right); - tp.setSideComponentVertical(true); - - inspector = new JSplitPane(JSplitPane.VERTICAL_SPLIT, // - new JScrollPane( inspectorTree ), - new JScrollPane( inspectorProperties )); - inspector.setDividerLocation( getWindowHeight() / 3 ); - inspector.setContinuousLayout(true); - inspector.setBorder(BorderFactory.createEmptyBorder()); - inspector.setDividerSize(1); - ImageIcon inspectorIcon = getScaledImageIcon(this.getClass().getResource("toolbox.png"), 16, 16); - - TabInfo tiInspector = new TabInfo(inspector, inspectorIcon); - tiInspector.setText(""); - tp.addTab(tiInspector); - - // We need to get the surface scale here before initialising scenery's renderer, as - // the information is needed already at initialisation time. - final AffineTransform dt = frame.getGraphicsConfiguration().getDefaultTransform(); - final Vector2f surfaceScale = new Vector2f((float)dt.getScaleX(), (float)dt.getScaleY()); - getSettings().set("Renderer.SurfaceScale", surfaceScale); - - interpreterPane = new REPLPane(getScijavaContext()); - interpreterPane.getComponent().setBorder(BorderFactory.createEmptyBorder()); - ImageIcon interpreterIcon = getScaledImageIcon(this.getClass().getResource("terminal.png"), 16, 16); - - TabInfo tiREPL = new TabInfo(interpreterPane.getComponent(), interpreterIcon); - tiREPL.setText(""); - tp.addTab(tiREPL); - - tp.addTabMouseListener(new MouseListener() { - private boolean hidden = false; - private int previousPosition = 0; - - @Override - public void mouseClicked(MouseEvent e) { - if(e.getClickCount() == 2) { - toggleSidebar(); - } - } - - @Override - public void mousePressed(MouseEvent e) { - - } - - @Override - public void mouseReleased(MouseEvent e) { - - } - - @Override - public void mouseEntered(MouseEvent e) { - - } - - @Override - public void mouseExited(MouseEvent e) { - - } - }); - - initializeInterpreter(); - - mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, // - p, - tp.getComponent() - ); - mainSplitPane.setDividerLocation(frame.getSize().width - 36); - mainSplitPane.setBorder(BorderFactory.createEmptyBorder()); - mainSplitPane.setDividerSize(1); - mainSplitPane.setResizeWeight(0.9); - sidebarHidden = true; - - //frame.add(mainSplitPane, BorderLayout.CENTER); - frame.add(mainSplitPane, BorderLayout.CENTER); - - SciView sciView = this; - frame.addWindowListener(new WindowAdapter() { - @Override public void windowClosing(WindowEvent e) { - getLogger().debug("Closing SciView window."); - close(); - getScijavaContext().service(SciViewService.class).close(sciView); - isClosed = true; - } - }); - - splashLabel = new SplashLabel(); - frame.setGlassPane(splashLabel); - frame.getGlassPane().setVisible(true); - frame.getGlassPane().requestFocusInWindow(); -// frame.getGlassPane().setBackground(new java.awt.Color(50, 48, 47, 255)); - frame.setVisible(true); - - sceneryPanel[0] = panel; - - setRenderer( Renderer.createRenderer( getHub(), getApplicationName(), getScene(), - getWindowWidth(), getWindowHeight(), - sceneryPanel[0]) ); - - getHub().add( SceneryElement.Renderer, getRenderer() ); - - reset(); - - animations = new LinkedList<>(); - controlStack = new Stack<>(); - - SwingUtilities.invokeLater(() -> { - try { - while (!getSceneryRenderer().getFirstImageReady()) { - getLogger().debug("Waiting for renderer initialisation"); - Thread.sleep(300); - } - - Thread.sleep(200); - } catch (InterruptedException e) { - getLogger().error("Renderer construction interrupted."); - } - - nodePropertyEditor.rebuildTree(); - getLogger().info("Done initializing SciView"); - - // subscribe to Node{Added, Removed, Changed} events happens automagically owing to the annotations - frame.getGlassPane().setVisible(false); - panel.setVisible(true); - - // install hook to keep inspector updated on external changes (scripting, etc) - getScene().getOnNodePropertiesChanged().put("updateInspector", - node -> { - if( node == nodePropertyEditor.getCurrentNode() ) { - nodePropertyEditor.updateProperties(node); - } - return null; - }); - - // Enable push rendering by default - getRenderer().setPushMode( true ); - - sciView.getCamera().setPosition(1.65, 1); - - }); - } - - private boolean sidebarHidden = false; - private int previousSidebarPosition = 0; - - public boolean toggleSidebar() { - if(!sidebarHidden) { - previousSidebarPosition = mainSplitPane.getDividerLocation(); - // TODO: remove hard-coded tab width - mainSplitPane.setDividerLocation(frame.getSize().width - 36); - sidebarHidden = true; - } else { - if(previousSidebarPosition == 0) { - previousSidebarPosition = getWindowWidth()/3 * 2; - } - - mainSplitPane.setDividerLocation(previousSidebarPosition); - sidebarHidden = false; - } - - return sidebarHidden; - } - - private ImageIcon getScaledImageIcon(final URL resource, int width, int height) { - final ImageIcon first = new ImageIcon(resource); - final Image image = first.getImage(); - - BufferedImage resizedImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2 = resizedImg.createGraphics(); - - g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g2.drawImage(first.getImage(), 0, 0, width, height, null); - g2.dispose(); - - return new ImageIcon(resizedImg); - } - - private void initializeInterpreter() { - String startupCode = ""; - startupCode = new Scanner(SciView.class.getResourceAsStream("startup.py"), "UTF-8").useDelimiter("\\A").next(); - interpreterPane.getREPL().getInterpreter().getBindings().put("sciView", this); - try { - interpreterPane.getREPL().getInterpreter().eval(startupCode); - } catch (ScriptException e) { - e.printStackTrace(); - } - } - - /* - * Completely close the SciView window + cleanup - */ - public void closeWindow() { - frame.dispose(); - } - - /* - * Return the default floor object - */ - public Node getFloor() { - return floor; - } - - /* - * Set the default floor object - */ - public void setFloor( Node n ) { - floor = n; - } - - /** - * Return the current SceneryJPanel. This is necessary for custom context menus - * @return panel the current SceneryJPanel - */ - public SceneryJPanel getSceneryJPanel() { - return panel; - } - - /* - * Return true if the scene has been initialized - */ - public boolean isInitialized() { - return sceneInitialized(); - } - - /* - * Return the current camera that is rendering the scene - */ - public Camera getCamera() { - return camera; - } - - /* - * Return the SciJava Display that contains SciView - */ - public Display getDisplay() { - return scijavaDisplay; - } - - /* - * Set the SciJava Display - */ - public void setDisplay( Display display ) { - scijavaDisplay = display; - } - - /* - * Get the InputHandler that is managing mouse, input, VR controls, etc. - */ - public InputHandler getSceneryInputHandler() { - return getInputHandler(); - } - - /* - * Return a bounding box around a subgraph of the scenegraph - */ - public OrientedBoundingBox getSubgraphBoundingBox( Node n ) { - Function> predicate = node -> node.getChildren(); - return getSubgraphBoundingBox(n,predicate); - } - - /* - * Return a bounding box around a subgraph of the scenegraph - */ - public OrientedBoundingBox getSubgraphBoundingBox( Node n, Function> branchFunction ) { - if(n.getBoundingBox() == null && n.getChildren().size() != 0) { - return n.getMaximumBoundingBox().asWorld(); - } - - List branches = branchFunction.apply(n); - if( branches.size() == 0 ) { - if( n.getBoundingBox() == null ) - return null; - else - return n.getBoundingBox().asWorld(); - } - - OrientedBoundingBox bb = n.getMaximumBoundingBox(); - for( Node c : branches ){ - OrientedBoundingBox cBB = getSubgraphBoundingBox(c, branchFunction); - if( cBB != null ) - bb = bb.expand(bb, cBB); - } - return bb; - } - - /** - * Place the scene into the center of camera view, and zoom in/out such - * that the whole scene is in the view (everything would be visible if it - * would not be potentially occluded). - */ - public void fitCameraToScene() { - centerOnNode(getScene()); - //TODO: smooth zoom in/out VLADO vlado Vlado - } - - /** - * Place the scene into the center of camera view. - */ - public void centerOnScene() { - centerOnNode(getScene()); - } - - /** - * Place the active node into the center of camera view. - */ - public void centerOnActiveNode() { - if (activeNode == null) return; - centerOnNode(activeNode); - } - - /** - * Place the specified node into the center of camera view. - */ - public void centerOnNode( Node currentNode ) { - if (currentNode == null) { - log.info("Cannot center on null node."); - return; - } - - //center the on the same spot as ArcBall does - centerOnPosition( currentNode.getMaximumBoundingBox().getBoundingSphere().getOrigin() ); - } - - /** - * Center the camera on the specified Node - */ - public void centerOnPosition( Vector3f currentPos ) { - //desired view direction in world coords - final Vector3f worldDirVec = new Vector3f(currentPos).sub(camera.getPosition()); - if (worldDirVec.lengthSquared() < 0.01) - { - //ill defined task, happens typically when cam is inside the node which we want center on - log.info("Camera is on the spot you want to look at. Please, move the camera away first."); - return; - } - - Vector2f camForwardXZ = new Vector2f(camera.getForward().x, camera.getForward().z); - Vector2f wantLookAtXZ = new Vector2f(worldDirVec.x, worldDirVec.z); - double totalYawAng = camForwardXZ.normalize().dot( wantLookAtXZ.normalize() ); - //while mathematically impossible, cumulated numerical inaccuracies have different opinion - totalYawAng = totalYawAng > 1 ? 0 : Math.acos( totalYawAng ); - - //switch direction? - camForwardXZ.set(camForwardXZ.y, -camForwardXZ.x); - if ( wantLookAtXZ.dot(camForwardXZ) > 0) totalYawAng *= -1; - - final Vector3f camForwardYed = new Vector3f( camera.getForward() ); - new Quaternionf().rotateXYZ(0,-(float)totalYawAng,0).normalize().transform( camForwardYed ); - double totalPitchAng = camForwardYed.normalize().dot( worldDirVec.normalize() ); - totalPitchAng = totalPitchAng > 1 ? 0 : Math.acos( totalPitchAng ); - - //switch direction? - if (camera.getForward().y > worldDirVec.y) totalPitchAng *= -1; - if (camera.getUp().y < 0) totalPitchAng *= -1; - - //animation options: control delay between animation frames -- fluency - final long rotPausePerStep = 30; //miliseconds - - //animation options: control max number of steps -- upper limit on total time for animation - final int rotMaxSteps = 999999; //effectively disabled.... - - //animation options: the hardcoded 5 deg (0.087 rad) -- smoothness - //how many steps when max update/move is 5 deg - float totalDeltaAng = (float)Math.max( Math.abs(totalPitchAng), Math.abs(totalYawAng) ); - int rotSteps = (int)Math.ceil( totalDeltaAng / 0.087 ); - if (rotSteps > rotMaxSteps) rotSteps = rotMaxSteps; - - /* - log.info("centering over "+rotSteps+" steps the pitch " + 180*totalPitchAng/Math.PI - + " and the yaw " + 180*totalYawAng/Math.PI); - */ - - //angular progress aux variables - double donePitchAng = 0, doneYawAng = 0; - float deltaAng; - - camera.setTargeted(false); - for (int i = 1; i <= rotSteps; ++i) { - //this emulates ease-in ease-out animation, both vars are in [0:1] - float timeProgress = (float)i / rotSteps; - final float angProgress = ((timeProgress *= 2) <= 1 ? //two cubics connected smoothly into S-shape curve from [0,0] to [1,1] - timeProgress * timeProgress * timeProgress : - (timeProgress -= 2) * timeProgress * timeProgress + 2) / 2; - - //rotate now by this ang: "where should I be by now" minus "where I got last time" - deltaAng = (float)(angProgress * totalPitchAng - donePitchAng); - Quaternionf pitchQ = new Quaternionf().rotateXYZ(-deltaAng, 0f, 0f).normalize(); - - deltaAng = (float)(angProgress * totalYawAng - doneYawAng); - Quaternionf yawQ = new Quaternionf().rotateXYZ(0f, deltaAng, 0f).normalize(); - - camera.setRotation( pitchQ.mul(camera.getRotation()).mul(yawQ).normalize() ); - donePitchAng = angProgress * totalPitchAng; - doneYawAng = angProgress * totalYawAng; - - try { - Thread.sleep(rotPausePerStep); - } catch (InterruptedException e) { - i = rotSteps; - } - } - } - - //a couple of shortcut methods to readout controls params - public float getFPSSpeedSlow() { - return controlsParameters.getFpsSpeedSlow(); - } - public float getFPSSpeedFast() { - return controlsParameters.getFpsSpeedFast(); - } - public float getFPSSpeedVeryFast() { - return controlsParameters.getFpsSpeedVeryFast(); - } - - public float getMouseSpeed() { - return controlsParameters.getMouseSpeedMult(); - } - public float getMouseScrollSpeed() { - return controlsParameters.getMouseScrollMult(); - } - - //a couple of setters with scene sensible boundary checks - public void setFPSSpeedSlow( float slowSpeed ) { - controlsParameters.setFpsSpeedSlow( paramWithinBounds(slowSpeed, FPSSPEED_MINBOUND_SLOW,FPSSPEED_MAXBOUND_SLOW) ); - } - public void setFPSSpeedFast( float fastSpeed ) { - controlsParameters.setFpsSpeedFast( paramWithinBounds(fastSpeed, FPSSPEED_MINBOUND_FAST,FPSSPEED_MAXBOUND_FAST) ); - } - public void setFPSSpeedVeryFast( float veryFastSpeed ) { - controlsParameters.setFpsSpeedVeryFast( paramWithinBounds(veryFastSpeed, FPSSPEED_MINBOUND_VERYFAST,FPSSPEED_MAXBOUND_VERYFAST) ); - } - - public void setFPSSpeed( float newBaseSpeed ) { - // we don't want to escape bounds checking - // (so we call "our" methods rather than directly the controlsParameters) - setFPSSpeedSlow( 1f * newBaseSpeed ); - setFPSSpeedFast( 20f * newBaseSpeed ); - setFPSSpeedVeryFast( 500f * newBaseSpeed ); - - //report what's been set in the end - log.debug( "FPS speeds: slow=" + controlsParameters.getFpsSpeedSlow() - + ", fast=" + controlsParameters.getFpsSpeedFast() - + ", very fast=" + controlsParameters.getFpsSpeedVeryFast() ); - } - - public void setMouseSpeed( float newSpeed ) { - controlsParameters.setMouseSpeedMult( paramWithinBounds(newSpeed, MOUSESPEED_MINBOUND,MOUSESPEED_MAXBOUND) ); - log.debug( "Mouse movement speed: " + controlsParameters.getMouseSpeedMult() ); - } - public void setMouseScrollSpeed( float newSpeed ) { - controlsParameters.setMouseScrollMult( paramWithinBounds(newSpeed, MOUSESCROLL_MINBOUND,MOUSESCROLL_MAXBOUND) ); - log.debug( "Mouse scroll speed: " + controlsParameters.getMouseScrollMult() ); - } - - //bounds for the controls - public static final float FPSSPEED_MINBOUND_SLOW = 0.01f; - public static final float FPSSPEED_MAXBOUND_SLOW = 30.0f; - public static final float FPSSPEED_MINBOUND_FAST = 0.2f; - public static final float FPSSPEED_MAXBOUND_FAST = 600f; - public static final float FPSSPEED_MINBOUND_VERYFAST = 10f; - public static final float FPSSPEED_MAXBOUND_VERYFAST = 2000f; - - public static final float MOUSESPEED_MINBOUND = 0.1f; - public static final float MOUSESPEED_MAXBOUND = 3.0f; - public static final float MOUSESCROLL_MINBOUND = 0.3f; - public static final float MOUSESCROLL_MAXBOUND = 10.0f; - - private float paramWithinBounds(float param, final float minBound, final float maxBound) - { - if( param < minBound ) param = minBound; - else if( param > maxBound ) param = maxBound; - return param; - } - - - public void setObjectSelectionMode() { - Function3 selectAction = (nearest,x,y) -> { - if( !nearest.getMatches().isEmpty() ) { - // copy reference on the last object picking result into "public domain" - // (this must happen before the ContextPopUpNodeChooser menu!) - objectSelectionLastResult = nearest; - - // Setup the context menu for this picking - // (in the menu, the user will chose node herself) - new ContextPopUpNodeChooser(this).show(panel,x,y); - } - return Unit.INSTANCE; - }; - setObjectSelectionMode(selectAction); - } - - public Scene.RaycastResult objectSelectionLastResult; - - /* - * Set the action used during object selection - */ - public void setObjectSelectionMode(Function3 selectAction) { - final InputHandler h = getInputHandler(); - List> ignoredObjects = new ArrayList<>(); - ignoredObjects.add( BoundingGrid.class ); - ignoredObjects.add( Camera.class ); //do not mess with "scene params", allow only "scene data" to be selected - ignoredObjects.add( DetachedHeadCamera.class ); - ignoredObjects.add( DirectionalLight.class ); - ignoredObjects.add( PointLight.class ); - - if(h == null) { - getLogger().error("InputHandler is null, cannot change object selection mode."); - return; - } - h.addBehaviour( "node: choose one from the view panel", - new SelectCommand( "objectSelector", getRenderer(), getScene(), - () -> getScene().findObserver(), false, ignoredObjects, - selectAction ) ); - h.addKeyBinding( "node: choose one from the view panel", "double-click button1" ); - } - - /* - * Initial configuration of the scenery InputHandler - * This is automatically called and should not be used directly - */ - @Override public void inputSetup() { - final InputHandler h = getInputHandler(); - if ( h == null ) { - getLogger().error( "InputHandler is null, cannot run input setup." ); - return; - } - //when we get here, the Behaviours and key bindings from scenery are already in place - - //possibly, disable some (unused?) controls from scenery - /* - h.removeBehaviour( "gamepad_camera_control"); - h.removeKeyBinding("gamepad_camera_control"); - h.removeBehaviour( "gamepad_movement_control"); - h.removeKeyBinding("gamepad_movement_control"); - */ - - // node-selection and node-manipulation (translate & rotate) controls - setObjectSelectionMode(); - final NodeTranslateControl nodeTranslateControl = new NodeTranslateControl(this); - h.addBehaviour( "node: move selected one left, right, up, or down", nodeTranslateControl); - h.addKeyBinding( "node: move selected one left, right, up, or down", "ctrl button1" ); - h.addBehaviour( "node: move selected one closer or further away", nodeTranslateControl); - h.addKeyBinding( "node: move selected one closer or further away", "ctrl scroll" ); - h.addBehaviour( "node: rotate selected one", new NodeRotateControl(this) ); - h.addKeyBinding( "node: rotate selected one", "ctrl shift button1" ); - - // within-scene navigation: ArcBall and FPS - enableArcBallControl(); - enableFPSControl(); - - // whole-scene rolling - h.addBehaviour( "view: rotate (roll) clock-wise", new SceneRollControl(this,+0.05f) ); //2.8 deg - h.addKeyBinding("view: rotate (roll) clock-wise", "R"); - h.addBehaviour( "view: rotate (roll) counter clock-wise", new SceneRollControl(this,-0.05f) ); - h.addKeyBinding("view: rotate (roll) counter clock-wise", "shift R"); - h.addBehaviour( "view: rotate (roll) with mouse", h.getBehaviour("view: rotate (roll) clock-wise")); - h.addKeyBinding("view: rotate (roll) with mouse", "ctrl button3"); - - // adjusters of various controls sensitivities - h.addBehaviour( "moves: step size decrease", (ClickBehaviour)(x,y) -> setFPSSpeed( getFPSSpeedSlow() - 0.01f ) ); - h.addKeyBinding("moves: step size decrease", "MINUS"); - h.addBehaviour( "moves: step size increase", (ClickBehaviour)(x,y) -> setFPSSpeed( getFPSSpeedSlow() + 0.01f ) ); - h.addKeyBinding("moves: step size increase", "EQUALS" ); - - h.addBehaviour( "mouse: move sensitivity decrease", (ClickBehaviour)(x,y) -> setMouseSpeed( getMouseSpeed() - 0.02f ) ); - h.addKeyBinding("mouse: move sensitivity decrease", "M MINUS"); - h.addBehaviour( "mouse: move sensitivity increase", (ClickBehaviour)(x,y) -> setMouseSpeed( getMouseSpeed() + 0.02f ) ); - h.addKeyBinding("mouse: move sensitivity increase", "M EQUALS" ); - - h.addBehaviour( "mouse: scroll sensitivity decrease", (ClickBehaviour)(x,y) -> setMouseScrollSpeed( getMouseScrollSpeed() - 0.3f ) ); - h.addKeyBinding("mouse: scroll sensitivity decrease", "S MINUS"); - h.addBehaviour( "mouse: scroll sensitivity increase", (ClickBehaviour)(x,y) -> setMouseScrollSpeed( getMouseScrollSpeed() + 0.3f ) ); - h.addKeyBinding("mouse: scroll sensitivity increase", "S EQUALS" ); - - // help window - h.addBehaviour( "show help", new showHelpDisplay() ); - h.addKeyBinding("show help", "F1" ); - } - - /* - * Change the control mode to circle around the active object in an arcball - */ - private void enableArcBallControl() { - final InputHandler h = getInputHandler(); - if ( h == null ) { - getLogger().error( "InputHandler is null, cannot setup arcball control." ); - return; - } - - Vector3f target; - if( getActiveNode() == null ) { - target = new Vector3f( 0, 0, 0 ); - } else { - target = getActiveNode().getPosition(); - } - - //setup ArcballCameraControl from scenery, register it with SciView's controlsParameters - Supplier cameraSupplier = () -> getScene().findObserver(); - targetArcball = new AnimatedCenteringBeforeArcBallControl( "view: rotate it around selected node", cameraSupplier, - getRenderer().getWindow().getWidth(), - getRenderer().getWindow().getHeight(), target ); - targetArcball.setMaximumDistance( Float.MAX_VALUE ); - controlsParameters.registerArcballCameraControl( targetArcball ); - - h.addBehaviour( "view: rotate around selected node", targetArcball ); - h.addKeyBinding( "view: rotate around selected node", "shift button1" ); - h.addBehaviour( "view: zoom outward or toward selected node", targetArcball ); - h.addKeyBinding( "view: zoom outward or toward selected node", "shift scroll" ); - } - - /* - * A wrapping class for the {@ArcballCameraControl} that calls {@link CenterOnPosition()} - * before the actual Arcball camera movement takes place. This way, the targeted node is - * first smoothly brought into the centre along which Arcball is revolving, preventing - * from sudden changes of view (and lost of focus from the user. - */ - class AnimatedCenteringBeforeArcBallControl extends ArcballCameraControl { - //a bunch of necessary c'tors (originally defined in the ArcballCameraControl class) - public AnimatedCenteringBeforeArcBallControl(@NotNull String name, @NotNull Function0 n, int w, int h, @NotNull Function0 target) { - super(name, n, w, h, target); - } - - public AnimatedCenteringBeforeArcBallControl(@NotNull String name, @NotNull Supplier n, int w, int h, @NotNull Supplier target) { - super(name, n, w, h, target); - } - - public AnimatedCenteringBeforeArcBallControl(@NotNull String name, @NotNull Function0 n, int w, int h, @NotNull Vector3f target) { - super(name, n, w, h, target); - } - - public AnimatedCenteringBeforeArcBallControl(@NotNull String name, @NotNull Supplier n, int w, int h, @NotNull Vector3f target) { - super(name, n, w, h, target); - } - - @Override - public void init( int x, int y ) - { - centerOnPosition( targetArcball.getTarget().invoke() ); - super.init(x,y); - } - - @Override - public void scroll(double wheelRotation, boolean isHorizontal, int x, int y) - { - centerOnPosition( targetArcball.getTarget().invoke() ); - super.scroll(wheelRotation,isHorizontal,x,y); - } - } - - /* - * Enable FPS style controls - */ - private void enableFPSControl() { - final InputHandler h = getInputHandler(); - if ( h == null ) { - getLogger().error( "InputHandler is null, cannot setup fps control." ); - return; - } - - // Mouse look around (Lclick) and move around (Rclick) - // - //setup FPSCameraControl from scenery, register it with SciView's controlsParameters - Supplier cameraSupplier = () -> getScene().findObserver(); - fpsControl = new FPSCameraControl( "view: freely look around", cameraSupplier, getRenderer().getWindow().getWidth(), - getRenderer().getWindow().getHeight() ); - controlsParameters.registerFpsCameraControl( fpsControl ); - - h.addBehaviour( "view: freely look around", fpsControl ); - h.addKeyBinding( "view: freely look around", "button1" ); - - //slow and fast camera motion - h.addBehaviour( "move_withMouse_back/forward/left/right", new CameraTranslateControl( this, 1f ) ); - h.addKeyBinding( "move_withMouse_back/forward/left/right", "button3" ); - // - //fast and very fast camera motion - h.addBehaviour( "move_withMouse_back/forward/left/right_fast", new CameraTranslateControl( this, 10f ) ); - h.addKeyBinding( "move_withMouse_back/forward/left/right_fast", "shift button3" ); - - // Keyboard move around (WASD keys) - // - //override 'WASD' from Scenery - MovementCommand mcW,mcA,mcS,mcD; - mcW = new MovementCommand( "move_forward", "forward", () -> getScene().findObserver(), controlsParameters.getFpsSpeedSlow() ); - mcS = new MovementCommand( "move_backward", "back", () -> getScene().findObserver(), controlsParameters.getFpsSpeedSlow() ); - mcA = new MovementCommand( "move_left", "left", () -> getScene().findObserver(), controlsParameters.getFpsSpeedSlow() ); - mcD = new MovementCommand( "move_right", "right", () -> getScene().findObserver(), controlsParameters.getFpsSpeedSlow() ); - controlsParameters.registerSlowStepMover( mcW ); - controlsParameters.registerSlowStepMover( mcS ); - controlsParameters.registerSlowStepMover( mcA ); - controlsParameters.registerSlowStepMover( mcD ); - h.addBehaviour( "move_forward", mcW ); - h.addBehaviour( "move_back", mcS ); - h.addBehaviour( "move_left", mcA ); - h.addBehaviour( "move_right", mcD ); - // 'WASD' keys are registered already in scenery - - //override shift+'WASD' from Scenery - mcW = new MovementCommand( "move_forward_fast", "forward", () -> getScene().findObserver(), controlsParameters.getFpsSpeedFast() ); - mcS = new MovementCommand( "move_backward_fast", "back", () -> getScene().findObserver(), controlsParameters.getFpsSpeedFast() ); - mcA = new MovementCommand( "move_left_fast", "left", () -> getScene().findObserver(), controlsParameters.getFpsSpeedFast() ); - mcD = new MovementCommand( "move_right_fast", "right", () -> getScene().findObserver(), controlsParameters.getFpsSpeedFast() ); - controlsParameters.registerFastStepMover( mcW ); - controlsParameters.registerFastStepMover( mcS ); - controlsParameters.registerFastStepMover( mcA ); - controlsParameters.registerFastStepMover( mcD ); - h.addBehaviour( "move_forward_fast", mcW ); - h.addBehaviour( "move_back_fast", mcS ); - h.addBehaviour( "move_left_fast", mcA ); - h.addBehaviour( "move_right_fast", mcD ); - // shift+'WASD' keys are registered already in scenery - - //define additionally shift+ctrl+'WASD' - mcW = new MovementCommand( "move_forward_veryfast", "forward", () -> getScene().findObserver(), controlsParameters.getFpsSpeedVeryFast() ); - mcS = new MovementCommand( "move_back_veryfast", "back", () -> getScene().findObserver(), controlsParameters.getFpsSpeedVeryFast() ); - mcA = new MovementCommand( "move_left_veryfast", "left", () -> getScene().findObserver(), controlsParameters.getFpsSpeedVeryFast() ); - mcD = new MovementCommand( "move_right_veryfast", "right", () -> getScene().findObserver(), controlsParameters.getFpsSpeedVeryFast() ); - controlsParameters.registerVeryFastStepMover( mcW ); - controlsParameters.registerVeryFastStepMover( mcS ); - controlsParameters.registerVeryFastStepMover( mcA ); - controlsParameters.registerVeryFastStepMover( mcD ); - h.addBehaviour( "move_forward_veryfast", mcW ); - h.addBehaviour( "move_back_veryfast", mcS ); - h.addBehaviour( "move_left_veryfast", mcA ); - h.addBehaviour( "move_right_veryfast", mcD ); - h.addKeyBinding( "move_forward_veryfast", "ctrl shift W" ); - h.addKeyBinding( "move_back_veryfast", "ctrl shift S" ); - h.addKeyBinding( "move_left_veryfast", "ctrl shift A" ); - h.addKeyBinding( "move_right_veryfast", "ctrl shift D" ); - - // Keyboard only move up/down (XC keys) - // - //[[ctrl]+shift]+'XC' - mcW = new MovementCommand( "move_up", "up", () -> getScene().findObserver(), controlsParameters.getFpsSpeedSlow() ); - mcS = new MovementCommand( "move_down", "down", () -> getScene().findObserver(), controlsParameters.getFpsSpeedSlow() ); - controlsParameters.registerSlowStepMover( mcW ); - controlsParameters.registerSlowStepMover( mcS ); - h.addBehaviour( "move_up", mcW ); - h.addBehaviour( "move_down", mcS ); - h.addKeyBinding( "move_up", "C" ); - h.addKeyBinding( "move_down", "X" ); - - mcW = new MovementCommand( "move_up_fast", "up", () -> getScene().findObserver(), controlsParameters.getFpsSpeedFast() ); - mcS = new MovementCommand( "move_down_fast", "down", () -> getScene().findObserver(), controlsParameters.getFpsSpeedFast() ); - controlsParameters.registerFastStepMover( mcW ); - controlsParameters.registerFastStepMover( mcS ); - h.addBehaviour( "move_up_fast", mcW ); - h.addBehaviour( "move_down_fast", mcS ); - h.addKeyBinding( "move_up_fast", "shift C" ); - h.addKeyBinding( "move_down_fast", "shift X" ); - - mcW = new MovementCommand( "move_up_veryfast", "up", () -> getScene().findObserver(), controlsParameters.getFpsSpeedVeryFast() ); - mcS = new MovementCommand( "move_down_veryfast", "down", () -> getScene().findObserver(), controlsParameters.getFpsSpeedVeryFast() ); - controlsParameters.registerVeryFastStepMover( mcW ); - controlsParameters.registerVeryFastStepMover( mcS ); - h.addBehaviour( "move_up_veryfast", mcW ); - h.addBehaviour( "move_down_veryfast", mcS ); - h.addKeyBinding( "move_up_veryfast", "ctrl shift C" ); - h.addKeyBinding( "move_down_veryfast", "ctrl shift X" ); - } - - /** - * Add a box to the scene with default parameters - * @return the Node corresponding to the box - */ - public Node addBox() { - return addBox( new JOMLVector3( 0.0f, 0.0f, 0.0f ) ); - } - - /** - * Add a box at the specific position and unit size - * @param position position to put the box - * @return the Node corresponding to the box - */ - public Node addBox( Vector3 position ) { - return addBox( position, new JOMLVector3( 1.0f, 1.0f, 1.0f ) ); - } - - /** - * Add a box at the specified position and with the specified size - * @param position position to put the box - * @param size size of the box - * @return the Node corresponding to the box - */ - public Node addBox( Vector3 position, Vector3 size ) { - return addBox( position, size, DEFAULT_COLOR, false ); - } - - /** - * Add a box at the specified position with specified size, color, and normals on the inside/outside - * @param position position to put the box - * @param size size of the box - * @param color color of the box - * @param inside are normals inside the box? - * @return the Node corresponding to the box - */ - public Node addBox( final Vector3 position, final Vector3 size, final ColorRGB color, - final boolean inside ) { - // TODO: use a material from the current palate by default - final Material boxmaterial = new Material(); - boxmaterial.setAmbient( new Vector3f( 1.0f, 0.0f, 0.0f ) ); - boxmaterial.setDiffuse( Utils.convertToVector3f( color ) ); - boxmaterial.setSpecular( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - - final Box box = new Box( JOMLVector3.convert( size ), inside ); - box.setMaterial( boxmaterial ); - box.setPosition( JOMLVector3.convert( position ) ); - - return addNode( box ); - } - - /** - * Add a unit sphere at the origin - * @return the Node corresponding to the sphere - */ - public Node addSphere() { - return addSphere( new JOMLVector3( 0.0f, 0.0f, 0.0f ), 1 ); - } - - /** - * Add a sphere at the specified position with a given radius - * @param position position to put the sphere - * @param radius radius of the sphere - * @return the Node corresponding to the sphere - */ - public Node addSphere( Vector3 position, float radius ) { - return addSphere( position, radius, DEFAULT_COLOR ); - } - - /** - * Add a sphere at the specified positoin with a given radius and color - * @param position position to put the sphere - * @param radius radius the sphere - * @param color color of the sphere - * @return the Node corresponding to the sphere - */ - public Node addSphere( final Vector3 position, final float radius, final ColorRGB color ) { - final Material material = new Material(); - material.setAmbient( new Vector3f( 1.0f, 0.0f, 0.0f ) ); - material.setDiffuse( Utils.convertToVector3f( color ) ); - material.setSpecular( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - - final Sphere sphere = new Sphere( radius, 20 ); - sphere.setMaterial( material ); - sphere.setPosition( JOMLVector3.convert( position ) ); - - return addNode( sphere ); - } - - /** - * Add a Cylinder at the given position with radius, height, and number of faces/segments - * @param position position of the cylinder - * @param radius radius of the cylinder - * @param height height of the cylinder - * @param num_segments number of segments to represent the cylinder - * @return the Node corresponding to the cylinder - */ - public Node addCylinder( final Vector3 position, final float radius, final float height, final int num_segments ) { - final Cylinder cyl = new Cylinder( radius, height, num_segments ); - cyl.setPosition( JOMLVector3.convert( position ) ); - return addNode( cyl ); - } - - /** - * Add a Cone at the given position with radius, height, and number of faces/segments - * @param position position to put the cone - * @param radius radius of the cone - * @param height height of the cone - * @param num_segments number of segments used to represent cone - * @return the Node corresponding to the cone - */ - public Node addCone( final Vector3 position, final float radius, final float height, final int num_segments ) { - final Cone cone = new Cone( radius, height, num_segments, new Vector3f(0,0,1) ); - cone.setPosition( JOMLVector3.convert( position ) ); - return addNode( cone ); - } - - /** - * Add a Line from 0,0,0 to 1,1,1 - * @return the Node corresponding to the line - */ - public Node addLine() { - return addLine( new JOMLVector3( 0.0f, 0.0f, 0.0f ), new JOMLVector3( 1.0f, 1.0f, 1.0f ) ); - } - - /** - * Add a line from start to stop - * @param start start position of line - * @param stop stop position of line - * @return the Node corresponding to the line - */ - public Node addLine( Vector3 start, Vector3 stop ) { - return addLine( start, stop, DEFAULT_COLOR ); - } - - /** - * Add a line from start to stop with the given color - * @param start start position of line - * @param stop stop position of line - * @param color color of line - * @return the Node corresponding to the line - */ - public Node addLine( Vector3 start, Vector3 stop, ColorRGB color ) { - return addLine( new Vector3[] { start, stop }, color, 0.1f ); - } - - /** - * Add a multi-segment line that goes through the supplied points with a single color and edge width - * @param points points along line including first and terminal points - * @param color color of line - * @param edgeWidth width of line segments - * @return the Node corresponding to the line - */ - public Node addLine( final Vector3[] points, final ColorRGB color, final double edgeWidth ) { - final Material material = new Material(); - material.setAmbient( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - material.setDiffuse( Utils.convertToVector3f( color ) ); - material.setSpecular( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - - final Line line = new Line( points.length ); - for( final Vector3 pt : points ) { - line.addPoint( JOMLVector3.convert( pt ) ); - } - - line.setEdgeWidth( ( float ) edgeWidth ); - - line.setMaterial( material ); - line.setPosition( JOMLVector3.convert( points[0] ) ); - - return addNode( line ); - } - - /** - * Add a PointLight source at the origin - * @return a Node corresponding to the PointLight - */ - public Node addPointLight() { - final Material material = new Material(); - material.setAmbient( new Vector3f( 1.0f, 0.0f, 0.0f ) ); - material.setDiffuse( new Vector3f( 0.0f, 1.0f, 0.0f ) ); - material.setSpecular( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - - final PointLight light = new PointLight( 5.0f ); - light.setMaterial( material ); - light.setPosition( new Vector3f( 0.0f, 0.0f, 0.0f ) ); - lights.add(light); - - return addNode( light ); - } - - /** - * Position all lights that were initialized by default around the scene in a circle at Y=0 - */ - public void surroundLighting() { - OrientedBoundingBox bb = getSubgraphBoundingBox(getScene(), notAbstractBranchingFunction); - OrientedBoundingBox.BoundingSphere boundingSphere = bb.getBoundingSphere(); - // Choose a good y-position, then place lights around the cross-section through this plane - float y = 0; - Vector3f c = boundingSphere.getOrigin(); - float r = boundingSphere.getRadius(); - for( int k = 0; k < lights.size(); k++ ) { - PointLight light = lights.get(k); - float x = (float) (c.x() + r * Math.cos( k == 0 ? 0 : Math.PI * 2 * ((float)k / (float)lights.size()) )); - float z = (float) (c.y() + r * Math.sin( k == 0 ? 0 : Math.PI * 2 * ((float)k / (float)lights.size()) )); - light.setLightRadius( 2 * r ); - light.setPosition( new Vector3f( x, y, z ) ); - } - } - - /** - * Write a scenery mesh as an stl to the given file - * @param filename filename of the stl - * @param scMesh mesh to save - */ - public void writeSCMesh( String filename, Mesh scMesh ) { - File f = new File( filename ); - BufferedOutputStream out; - try { - out = new BufferedOutputStream( new FileOutputStream( f ) ); - out.write( "solid STL generated by FIJI\n".getBytes() ); - - FloatBuffer normalsFB = scMesh.getNormals().duplicate(); - FloatBuffer verticesFB = scMesh.getVertices().duplicate(); - - while( verticesFB.hasRemaining() && normalsFB.hasRemaining() ) { - out.write( ( "facet normal " + normalsFB.get() + " " + normalsFB.get() + " " + normalsFB.get() + - "\n" ).getBytes() ); - out.write( "outer loop\n".getBytes() ); - for( int v = 0; v < 3; v++ ) { - out.write( ( "vertex\t" + verticesFB.get() + " " + verticesFB.get() + " " + verticesFB.get() + - "\n" ).getBytes() ); - } - out.write( "endloop\n".getBytes() ); - out.write( "endfacet\n".getBytes() ); - } - out.write( "endsolid vcg\n".getBytes() ); - out.close(); - } catch( FileNotFoundException e ) { - e.printStackTrace(); - } catch( IOException e ) { - e.printStackTrace(); - } - - } - - /** - * Return the default point size to use for point clouds - * @return default point size used for point clouds - */ - public float getDefaultPointSize() { - return 0.025f; - } - - /** - * Create an array of normal vectors from a set of vertices corresponding to triangles - * - * @param verts vertices to use for computing normals, assumed to be ordered as triangles - * @return array of normals - */ - public float[] makeNormalsFromVertices( ArrayList verts ) { - float[] normals = new float[verts.size()];// div3 * 3coords - - for( int k = 0; k < verts.size(); k += 3 ) { - Vector3f v1 = new Vector3f( verts.get( k ).getFloatPosition( 0 ), // - verts.get( k ).getFloatPosition( 1 ), // - verts.get( k ).getFloatPosition( 2 ) ); - Vector3f v2 = new Vector3f( verts.get( k + 1 ).getFloatPosition( 0 ), - verts.get( k + 1 ).getFloatPosition( 1 ), - verts.get( k + 1 ).getFloatPosition( 2 ) ); - Vector3f v3 = new Vector3f( verts.get( k + 2 ).getFloatPosition( 0 ), - verts.get( k + 2 ).getFloatPosition( 1 ), - verts.get( k + 2 ).getFloatPosition( 2 ) ); - Vector3f a = v2.sub( v1 ); - Vector3f b = v3.sub( v1 ); - Vector3f n = a.cross( b ).normalize(); - normals[k / 3] = n.get( 0 ); - normals[k / 3 + 1] = n.get( 1 ); - normals[k / 3 + 2] = n.get( 2 ); - } - return normals; - } - - /** - * Open a file specified by the source path. The file can be anything that SciView knows about: mesh, volume, point cloud - * @param source string of a data source - * @throws IOException - */ - public void open( final String source ) throws IOException { - if(source.endsWith(".xml")) { - addNode(Volume.Companion.fromXML(source, getHub(), new VolumeViewerOptions())); - return; - } - - final Object data = io.open( source ); - if( data instanceof net.imagej.mesh.Mesh ) addMesh( ( net.imagej.mesh.Mesh ) data ); - else if( data instanceof Mesh ) addMesh( ( Mesh ) data ); - else if( data instanceof PointCloud ) addPointCloud( ( PointCloud ) data ); - else if( data instanceof Dataset ) addVolume( ( Dataset ) data ); - else if( data instanceof RandomAccessibleInterval ) addVolume( ( ( RandomAccessibleInterval ) data ), source ); - else if( data instanceof List ) { - final List list = ( List ) data; - if( list.isEmpty() ) { - throw new IllegalArgumentException( "Data source '" + source + "' appears empty." ); - } - final Object element = list.get( 0 ); - if( element instanceof RealLocalizable ) { - // NB: For now, we assume all elements will be RealLocalizable. - // Highly likely to be the case, barring antagonistic importers. - @SuppressWarnings("unchecked") final List points = ( List ) list; - addPointCloud( points, source ); - } else { - final String type = element == null ? "" : element.getClass().getName(); - throw new IllegalArgumentException( "Data source '" + source + // - "' contains elements of unknown type '" + type + "'" ); - } - } else { - final String type = data == null ? "" : data.getClass().getName(); - throw new IllegalArgumentException( "Data source '" + source + // - "' contains data of unknown type '" + type + "'" ); - } - } - - /** - * Add the given points to the scene as a PointCloud - * @param points points to use in a PointCloud - * @return a Node corresponding to the PointCloud - */ - public Node addPointCloud( Collection points ) { - return addPointCloud( points, "PointCloud" ); - } - - /** - * Add the given points to the scene as a PointCloud with a given name - * @param points points to use in a PointCloud - * @param name name of the PointCloud - * @return - */ - public Node addPointCloud( final Collection points, - final String name ) { - final float[] flatVerts = new float[points.size() * 3]; - int k = 0; - for( final RealLocalizable point : points ) { - flatVerts[k * 3] = point.getFloatPosition( 0 ); - flatVerts[k * 3 + 1] = point.getFloatPosition( 1 ); - flatVerts[k * 3 + 2] = point.getFloatPosition( 2 ); - k++; - } - - final PointCloud pointCloud = new PointCloud( getDefaultPointSize(), name ); - final Material material = new Material(); - final FloatBuffer vBuffer = BufferUtils.allocateFloat( flatVerts.length * 4 ); - final FloatBuffer nBuffer = BufferUtils.allocateFloat( 0 ); - - vBuffer.put( flatVerts ); - vBuffer.flip(); - - pointCloud.setVertices( vBuffer ); - pointCloud.setNormals( nBuffer ); - pointCloud.setIndices( BufferUtils.allocateInt( 0 ) ); - pointCloud.setupPointCloud(); - material.setAmbient( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - material.setDiffuse( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - material.setSpecular( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - pointCloud.setMaterial( material ); - pointCloud.setPosition( new Vector3f( 0f, 0f, 0f ) ); - - return addNode( pointCloud ); - } - - /** - * Add a PointCloud to the scene - * @param pointCloud existing PointCloud to add to scene - * @return a Node corresponding to the PointCloud - */ - public Node addPointCloud( final PointCloud pointCloud ) { - pointCloud.setupPointCloud(); - pointCloud.getMaterial().setAmbient( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - pointCloud.getMaterial().setDiffuse( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - pointCloud.getMaterial().setSpecular( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - pointCloud.setPosition( new Vector3f( 0f, 0f, 0f ) ); - - return addNode( pointCloud ); - } - - /** - * Add a Node to the scene and publish it to the eventservice - * @param n node to add to scene - * @return a Node corresponding to the Node - */ - public Node addNode( final Node n ) { - return addNode(n, true); - } - - /** - * Add Node n to the scene and set it as the active node/publish it to the event service if activePublish is true - * @param n node to add to scene - * @param activePublish flag to specify whether the node becomes active *and* is published in the inspector/services - * @return a Node corresponding to the Node - */ - public Node addNode( final Node n, final boolean activePublish ) { - getScene().addChild( n ); - - objectService.addObject(n); - - if( blockOnNewNodes ) { - blockWhile(sciView -> (sciView.find(n.getName()) == null), 20); - //System.out.println("find(name) " + find(n.getName()) ); - } - - // Set new node as active and centered? - setActiveNode(n); - if( centerOnNewNodes ) centerOnNode(n); - if( activePublish ) eventService.publish(new NodeAddedEvent(n)); - - return n; - } - - /** - * Add a scenery Mesh to the scene - * @param scMesh scenery mesh to add to scene - * @return a Node corresponding to the mesh - */ - public Node addMesh( final Mesh scMesh ) { - final Material material = new Material(); - material.setAmbient( new Vector3f( 1.0f, 0.0f, 0.0f ) ); - material.setDiffuse( new Vector3f( 0.0f, 1.0f, 0.0f ) ); - material.setSpecular( new Vector3f( 1.0f, 1.0f, 1.0f ) ); - - scMesh.setMaterial( material ); - scMesh.setPosition( new Vector3f( 0.0f, 0.0f, 0.0f ) ); - - objectService.addObject(scMesh); - - return addNode( scMesh ); - } - - /** - * Add an ImageJ mesh to the scene - * @param mesh net.imagej.mesh to add to scene - * @return a Node corresponding to the mesh - */ - public Node addMesh( net.imagej.mesh.Mesh mesh ) { - Mesh scMesh = MeshConverter.toScenery( mesh ); - - return addMesh( scMesh ); - } - - /** - * [Deprecated: use deleteNode] - * Remove a Mesh from the scene - * @param scMesh mesh to remove from scene - */ - public void removeMesh( Mesh scMesh ) { - getScene().removeChild( scMesh ); - } - - /** - * @return a Node corresponding to the currently active node - */ - public Node getActiveNode() { - return activeNode; - } - - /** - * Activate the node (without centering view on it). The node becomes a target - * of the Arcball camera movement, will become subject of the node dragging - * (ctrl[+shift]+mouse-left-click-and-drag), will be selected in the scene graph - * inspector (the {@link NodePropertyEditor}) - * and {@link sc.iview.event.NodeActivatedEvent} will be published. - * - * @param n existing node that should become active focus of this SciView - * @return the currently active node - */ - public Node setActiveNode( Node n ) { - if( activeNode == n ) return activeNode; - activeNode = n; - targetArcball.setTarget( n == null ? () -> new Vector3f( 0, 0, 0 ) : () -> n.getMaximumBoundingBox().getBoundingSphere().getOrigin()); - nodePropertyEditor.trySelectNode( activeNode ); - eventService.publish( new NodeActivatedEvent( activeNode ) ); - - return activeNode; - } - - /** - * Activate the node, and center the view on it. - * @param n - * @return the currently active node - */ - public Node setActiveCenteredNode( Node n ) { - //activate... - Node ret = setActiveNode(n); - //...and center it - if (ret != null) centerOnNode(ret); - return ret; - } - - @EventHandler - protected void onNodeAdded(NodeAddedEvent event) { - nodePropertyEditor.rebuildTree(); - } - - @EventHandler - protected void onNodeRemoved(NodeRemovedEvent event) { - nodePropertyEditor.rebuildTree(); - } - - @EventHandler - protected void onNodeChanged(NodeChangedEvent event) { - nodePropertyEditor.rebuildTree(); - } - - @EventHandler - protected void onNodeActivated(NodeActivatedEvent event) { - // TODO: add listener code for node activation, if necessary - // NOTE: do not update property window here, this will lead to a loop. - } - - public void toggleInspectorWindow() - { - toggleSidebar(); - } - - public void setInspectorWindowVisibility(boolean visible) - { -// inspector.setVisible(visible); -// if( visible ) -// mainSplitPane.setDividerLocation(getWindowWidth()/4 * 3); -// else -// mainSplitPane.setDividerLocation(getWindowWidth()); - } - - public void setInterpreterWindowVisibility(boolean visible) - { -// interpreterPane.getComponent().setVisible(visible); -// if( visible ) -// interpreterSplitPane.setDividerLocation(getWindowHeight()/10 * 6); -// else -// interpreterSplitPane.setDividerLocation(getWindowHeight()); - } - - - /** - * Create an animation thread with the given fps speed and the specified action - * @param fps frames per second at which this action should be run - * @param action Runnable that contains code to run fps times per second - * @return a Future corresponding to the thread - */ - public synchronized Future animate(int fps, Runnable action ) { - // TODO: Make animation speed less laggy and more accurate. - final int delay = 1000 / fps; - Future thread = threadService.run(() -> { - while (animating) { - action.run(); - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - break; - } - } - }); - animations.add( thread ); - animating = true; - return thread; - } - - /** - * Stop all animations - */ - public synchronized void stopAnimation() { - animating = false; - while( !animations.isEmpty() ) { - animations.peek().cancel( true ); - animations.remove(); - } - } - - /** - * Take a screenshot and save it to the default scenery location - */ - public void takeScreenshot() { - getRenderer().screenshot(); - } - - /** - * Take a screenshot and save it to the specified path - * @param path path for saving the screenshot - */ - public void takeScreenshot( String path ) { - getRenderer().screenshot( path, false ); - } - - /** - * Take a screenshot and return it as an Img - * @return an Img of type UnsignedByteType - */ - public Img getScreenshot() { - RenderedImage screenshot = getSceneryRenderer().requestScreenshot(); - - BufferedImage image = new BufferedImage(screenshot.getWidth(), screenshot.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); - byte[] imgData = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); - System.arraycopy(screenshot.getData(), 0, imgData, 0, screenshot.getData().length); - - Img img = null; - File tmpFile = null; - try { - tmpFile = File.createTempFile("sciview-", "-tmp.png"); - ImageIO.write(image, "png", tmpFile); - img = (Img)io.open(tmpFile.getAbsolutePath()); - tmpFile.delete(); - } catch (IOException e) { - e.printStackTrace(); - } - return img; - } - - /** - * Take a screenshot and return it as an Img - * @return an Img of type UnsignedByteType - */ - public Img getARGBScreenshot() { - Img screenshot = getScreenshot(); - - return Utils.convertToARGB(screenshot); - } - - /** - * @param name The name of the node to find. - * @return the node object or null, if the node has not been found. - */ - public Node find(final String name) { - final Node n = getScene().find(name); - - if(n == null) { - getLogger().warn("Node with name " + name + " not found."); - } - - return n; - } - - /** - * @return an array of all nodes in the scene except Cameras and PointLights - */ - public Node[] getSceneNodes() { - return getSceneNodes( n -> !( n instanceof Camera ) && !( n instanceof PointLight ) ); - } - - /** - * Get a list of nodes filtered by filter predicate - * @param filter, a predicate that filters the candidate nodes - * @return all nodes that match the predicate - */ - public Node[] getSceneNodes( Predicate filter ) { - return getScene().getChildren().stream().filter( filter ).toArray( Node[]::new ); - } - - /** - * @return an array of all Node's in the scene - */ - public Node[] getAllSceneNodes() { - return getSceneNodes( n -> true ); - } - - /** - * Delete the current active node - */ - public void deleteActiveNode() { - deleteNode( getActiveNode() ); - } - - /** - * Delete the specified node, this event is published - * @param node node to delete from scene - */ - public void deleteNode( Node node ) { - deleteNode( node, true ); - } - - /** - * Delete a specified node and control whether the event is published - * @param node node to delete from scene - * @param activePublish whether the deletion should be published - */ - public void deleteNode( Node node, boolean activePublish ) { - for( Node child : node.getChildren() ) { - deleteNode(child, activePublish); - } - - objectService.removeObject(node); - node.getParent().removeChild( node ); - if (activeNode == node) setActiveNode(null); //maintain consistency - if( activePublish ) eventService.publish(new NodeRemovedEvent(node)); - } - - /** - * Dispose the current scenery renderer, hub, and other scenery things - */ - public void dispose() { - List objs = objectService.getObjects(Node.class); - for( Node obj : objs ) { - objectService.removeObject(obj); - } - getScijavaContext().service(SciViewService.class).close(this); - this.close(); - } - - - public void close() { - super.close(); - - frame.dispose(); - } - - /** - * Move the current active camera to the specified position - * @param position position to move the camera to - */ - public void moveCamera( float[] position ) { - getCamera().setPosition( new Vector3f( position[0], position[1], position[2] ) ); - } - - /** - * Move the current active camera to the specified position - * @param position position to move the camera to - */ - public void moveCamera( double[] position ) { - getCamera().setPosition( new Vector3f( ( float ) position[0], ( float ) position[1], ( float ) position[2] ) ); - } - - /** - * Get the current application name - * @return a String of the application name - */ - public String getName() { - return getApplicationName(); - } - - /** - * Add a child to the scene. you probably want addNode - * @param node node to add as a child to the scene - */ - public void addChild( Node node ) { - getScene().addChild( node ); - } - - /** - * Add a Dataset to the scene as a volume. Voxel resolution and name are extracted from the Dataset itself - * @param image image to add as a volume - * @return a Node corresponding to the Volume - */ - public Node addVolume( Dataset image ) { - - float[] voxelDims = new float[image.numDimensions()]; - for( int d = 0; d < voxelDims.length; d++ ) { - double inValue = image.axis(d).averageScale(0, 1); - if( image.axis(d).unit() == null ) - voxelDims[d] = (float) inValue; - else - voxelDims[d] = (float) unitService.value( inValue, image.axis(d).unit(), axis(d).unit() ); - } - - return addVolume( image, voxelDims ); - } - - /** - * Add a Dataset as a Volume with the specified voxel dimensions - * @param image image to add as a volume - * @param voxelDimensions dimensions of voxels in volume - * @return a Node corresponding to the Volume - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) public Node addVolume( Dataset image, float[] voxelDimensions ) { - return addVolume( ( RandomAccessibleInterval ) image.getImgPlus(), image.getName(), - voxelDimensions ); - } - - /** - * Add a RandomAccessibleInterval to the image - * @param image image to add as a volume - * @param name name of image - * @param extra, kludge argument to prevent matching issues - * @param pixel type of image - * @return a Node corresponding to the volume - */ - public > Node addVolume( RandomAccessibleInterval image, String name, String extra ) { - return addVolume( image, name, 1, 1, 1 ); - } - - /** - * Add a RandomAccessibleInterval to the image - * @param image image to add as a volume - * @param pixel type of image - * @return a Node corresponding to the volume - */ - public > Node addVolume(RandomAccessibleInterval image, String name) { - return addVolume(image, name, 1f, 1f, 1f); - } - - /** - * Add a RandomAccessibleInterval to the image - * @param image image to add as a volume - * @param pixel type of image - * @return a Node corresponding to the volume - */ - public > Node addVolume( RandomAccessibleInterval image, float[] voxelDimensions ) { - long[] pos = new long[]{10, 10, 10}; - - return addVolume( image, "volume", voxelDimensions ); - } - - /** - * Add an IterableInterval as a Volume - * @param image - * @param - * @return a Node corresponding to the Volume - */ - public > Node addVolume( IterableInterval image ) throws Exception { - if( image instanceof RandomAccessibleInterval ) { - return addVolume((RandomAccessibleInterval) image, "Volume"); - } else { - throw new Exception("Unsupported Volume type:" + image); - } - } - - /** - * Add an IterableInterval as a Volume - * @param image image to add as a volume - * @param name name of image - * @param pixel type of image - * @return a Node corresponding to the Volume - */ - public > Node addVolume( IterableInterval image, String name ) throws Exception { - if( image instanceof RandomAccessibleInterval ) { - return addVolume( (RandomAccessibleInterval) image, name, 1, 1, 1 ); - } else { - throw new Exception("Unsupported Volume type:" + image); - } - } - - /** - * Set the colormap using an ImageJ LUT name - * @param n node to apply colormap to - * @param lutName name of LUT according to imagej LUTService - */ - public void setColormap( Node n, String lutName ) { - try { - setColormap( n, lutService.loadLUT( lutService.findLUTs().get( lutName ) ) ); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Set the ColorMap of node n to the supplied colorTable - * @param n node to apply colortable to - * @param colorTable ColorTable to use - */ - public void setColormap( Node n, ColorTable colorTable ) { - final int copies = 16; - - final ByteBuffer byteBuffer = ByteBuffer.allocateDirect( - 4 * colorTable.getLength() * copies );// Num bytes * num components * color map length * height of color map texture - - final byte[] tmp = new byte[4 * colorTable.getLength()]; - for( int k = 0; k < colorTable.getLength(); k++ ) { - for( int c = 0; c < colorTable.getComponentCount(); c++ ) { - // TODO this assumes numBits is 8, could be 16 - tmp[4 * k + c] = ( byte ) colorTable.get( c, k ); - } - - if( colorTable.getComponentCount() == 3 ) { - tmp[4 * k + 3] = (byte)255; - } - } - - for( int i = 0; i < copies; i++ ) { - byteBuffer.put(tmp); - } - - byteBuffer.flip(); - - n.getMetadata().put("sciviewColormap", colorTable); - - if(n instanceof Volume) { - ((Volume) n).setColormap(Colormap.fromColorTable(colorTable)); - n.setDirty(true); - n.setNeedsUpdate(true); - } - } - - /** - * Adss a SourceAndConverter to the scene. - * - * @param sac The SourceAndConverter to add - * @param name Name of the dataset - * @param voxelDimensions Array with voxel dimensions. - * @param Type of the dataset. - * @return THe node corresponding to the volume just added. - */ - public > Node addVolume(SourceAndConverter sac, - int numTimepoints, - String name, - float... voxelDimensions ) { - List> sources = new ArrayList<>(); - sources.add(sac); - - return addVolume(sources, numTimepoints, name, voxelDimensions); - } - - /** - * Add an IterableInterval to the image with the specified voxelDimensions and name - * This version of addVolume does most of the work - * @param image image to add as a volume - * @param name name of image - * @param voxelDimensions dimensions of voxel in volume - * @param pixel type of image - * @return a Node corresponding to the Volume - */ - public > Node addVolume( RandomAccessibleInterval image, String name, - float... voxelDimensions ) { - //log.debug( "Add Volume " + name + " image: " + image ); - - long[] dimensions = new long[image.numDimensions()]; - image.dimensions( dimensions ); - - long[] minPt = new long[image.numDimensions()]; - - // Get type at min point - RandomAccess imageRA = image.randomAccess(); - image.min(minPt); - imageRA.setPosition(minPt); - T voxelType = imageRA.get().createVariable(); - - ArrayList converterSetups = new ArrayList(); - ArrayList> stacks = AxisOrder.splitInputStackIntoSourceStacks(image, AxisOrder.getAxisOrder(AxisOrder.DEFAULT, image, false)); - AffineTransform3D sourceTransform = new AffineTransform3D(); - ArrayList> sources = new ArrayList(); - - int numTimepoints = 1; - for (RandomAccessibleInterval stack : stacks) { - Source s; - if (stack.numDimensions() > 3) { - numTimepoints = (int) (stack.max(3) + 1); - s = new RandomAccessibleIntervalSource4D(stack, voxelType, sourceTransform, name); - } else { - s = new RandomAccessibleIntervalSource(stack, voxelType, sourceTransform, name); - } - SourceAndConverter source = BigDataViewer.wrapWithTransformedSource( - new SourceAndConverter(s, BigDataViewer.createConverterToARGB(voxelType))); - converterSetups.add(BigDataViewer.createConverterSetup(source, Volume.Companion.getSetupId().getAndIncrement())); - sources.add(source); - } - - Node v = addVolume(sources, numTimepoints, name, voxelDimensions); - - v.getMetadata().put("RandomAccessibleInterval", image); - - return v; - } - - /** - * Adds a SourceAndConverter to the scene. - * - * This method actually instantiates the volume. - * - * @param sources The list of SourceAndConverter to add - * @param name Name of the dataset - * @param voxelDimensions Array with voxel dimensions. - * @param Type of the dataset. - * @return THe node corresponding to the volume just added. - */ - public > Node addVolume(List> sources, - ArrayList converterSetups, - int numTimepoints, - String name, - float... voxelDimensions ) { - - CacheControl cacheControl = null; - -// RandomAccessibleInterval image = -// ((RandomAccessibleIntervalSource4D) sources.get(0).getSpimSource()). -// .getSource(0, 0); - RandomAccessibleInterval image = sources.get(0).getSpimSource().getSource(0, 0); - - if (image instanceof VolatileView) { - VolatileViewData> viewData = ((VolatileView>) image).getVolatileViewData(); - cacheControl = viewData.getCacheControl(); - } - - long[] dimensions = new long[image.numDimensions()]; - image.dimensions( dimensions ); - - long[] minPt = new long[image.numDimensions()]; - - // Get type at min point - RandomAccess imageRA = image.randomAccess(); - image.min(minPt); - imageRA.setPosition(minPt); - T voxelType = imageRA.get().createVariable(); - - System.out.println("addVolume " + image.numDimensions() + " interval " + ((Interval) image) ); - - //int numTimepoints = 1; - if( image.numDimensions() > 3 ) { - numTimepoints = (int) image.dimension(3); - } - - Volume.VolumeDataSource.RAISource ds = new Volume.VolumeDataSource.RAISource(voxelType, sources, converterSetups, numTimepoints, cacheControl); - VolumeViewerOptions options = new VolumeViewerOptions(); - - Volume v = new RAIVolume(ds, options, getHub()); - v.setName(name); - - v.getMetadata().put("sources", sources); - - TransferFunction tf = v.getTransferFunction(); - float rampMin = 0f; - float rampMax = 0.1f; - tf.clear(); - tf.addControlPoint(0.0f, 0.0f); - tf.addControlPoint(rampMin, 0.0f); - tf.addControlPoint(1.0f, rampMax); - - BoundingGrid bg = new BoundingGrid(); - bg.setNode(v); - - return addNode(v); - } - - /** - * Block while predicate is true - * - * @param predicate predicate function that returns true as long as this function should block - * @param waitTime wait time before predicate re-evaluation - */ - private void blockWhile(Function predicate, int waitTime) { - while( predicate.apply(this) ) { - try { - Thread.sleep(waitTime); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - /** - * Adds a SourceAndConverter to the scene. - * - * @param sources The list of SourceAndConverter to add - * @param name Name of the dataset - * @param voxelDimensions Array with voxel dimensions. - * @param Type of the dataset. - * @return THe node corresponding to the volume just added. - */ - public > Node addVolume(List> sources, - int numTimepoints, - String name, - float... voxelDimensions ) { - int setupId = 0; - ArrayList converterSetups = new ArrayList<>(); - for( SourceAndConverter source: sources ) { - converterSetups.add(BigDataViewer.createConverterSetup(source, setupId++)); - } - - return addVolume(sources, converterSetups, numTimepoints, name, voxelDimensions); - } - - /** - * Update a volume with the given IterableInterval. - * This method actually populates the volume - * @param image image to update into volume - * @param name name of image - * @param voxelDimensions dimensions of voxel in volume - * @param v existing volume to update - * @param pixel type of image - * @return a Node corresponding to the input volume - */ - public > Node updateVolume( IterableInterval image, String name, - float[] voxelDimensions, Volume v ) { - List> sacs = (List>) v.getMetadata().get("sources"); - - RandomAccessibleInterval source = sacs.get(0).getSpimSource().getSource(0, 0);// hard coded to timepoint and mipmap 0 - - Cursor sCur = Views.iterable(source).cursor(); - Cursor iCur = image.cursor(); - while( sCur.hasNext() ) { - sCur.fwd(); - iCur.fwd(); - sCur.get().set(iCur.get()); - } - - v.getVolumeManager().notifyUpdate(v); - v.getVolumeManager().requestRepaint(); - //v.getCacheControls().clear(); - //v.setDirty( true ); - v.setNeedsUpdate( true ); - //v.setNeedsUpdateWorld( true ); - - return v; - } - - /** - * - * @return whether PushMode is currently active - */ - public boolean getPushMode() { - return getRenderer().getPushMode(); - } - - /** - * Set the status of PushMode, which only updates the render panel when there is a change in the scene - * @param push true if push mode should be used - * @return current PushMode status - */ - public boolean setPushMode( boolean push ) { - getRenderer().setPushMode( push ); - return getRenderer().getPushMode(); - } - - public ArcballCameraControl getTargetArcball() { - return targetArcball; - } - - @Override - protected void finalize() { - stopAnimation(); - } - - public Settings getScenerySettings() { - return this.getSettings(); - } - - public Statistics getSceneryStatistics() { - return this.getStats(); - } - - public Renderer getSceneryRenderer() { - return this.getRenderer(); - } - - /** - * Enable VR rendering - */ - public void toggleVRRendering() { - vrActive = !vrActive; - Camera cam = getScene().getActiveObserver(); - if(!(cam instanceof DetachedHeadCamera)) { - return; - } - - TrackerInput ti = null; - boolean hmdAdded = false; - - if (!getHub().has(SceneryElement.HMDInput)) { - try { - final OpenVRHMD hmd = new OpenVRHMD(false, true); - - if(hmd.initializedAndWorking()) { - getHub().add(SceneryElement.HMDInput, hmd); - ti = hmd; - } else { - getLogger().warn("Could not initialise VR headset, just activating stereo rendering."); - } - - hmdAdded = true; - } catch (Exception e) { - getLogger().error("Could not add OpenVRHMD: " + e.toString()); - } - } else { - ti = getHub().getWorkingHMD(); - } - - if(vrActive && ti != null) { - ((DetachedHeadCamera) cam).setTracker(ti); - } else { - ((DetachedHeadCamera) cam).setTracker(null); - } - - getRenderer().setPushMode(false); - // we need to force reloading the renderer as the HMD might require device or instance extensions - if(getRenderer() instanceof VulkanRenderer && hmdAdded) { - replaceRenderer(getRenderer().getClass().getSimpleName(), true, true); - getRenderer().toggleVR(); - - while(!getRenderer().getInitialized()/* || !getRenderer().getFirstImageReady()*/) { - getLogger().debug("Waiting for renderer reinitialisation"); - try { - Thread.sleep(200); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } else { - getRenderer().toggleVR(); - } - - } - - /** - * Utility function to generate GLVector in cases like usage from Python - * @param x x coord - * @param y y coord - * @param z z coord - * @return a GLVector of x,y,z - */ - static public GLVector getGLVector(float x, float y, float z) { - return new GLVector(x, y, z); - } - - /** - * Set the rotation of Node N by generating a quaternion from the supplied arguments - * @param n node to set rotation for - * @param x x coord of rotation quat - * @param y y coord of rotation quat - * @param z z coord of rotation quat - * @param w w coord of rotation quat - */ - public void setRotation(Node n, float x, float y, float z, float w) { - n.setRotation(new Quaternionf(x,y,z,w)); - } - - public void setScale(Node n, float x, float y, float z) { - n.setScale(new Vector3f(x,y,z)); - } - - public void setColor(Node n, float x, float y, float z, float w) { - Vector3f col = new Vector3f(x, y, z); - n.getMaterial().setAmbient(col); - n.getMaterial().setDiffuse(col); - n.getMaterial().setSpecular(col); - } - - public void setPosition(Node n, float x, float y, float z) { - n.setPosition(new Vector3f(x,y,z)); - } - - public void addWindowListener(WindowListener wl) { - frame.addWindowListener(wl); - } - - @Override - public CalibratedAxis axis(int i) { - return axes[i]; - } - - @Override - public void axes(CalibratedAxis[] calibratedAxes) { - axes = calibratedAxes; - } - - @Override - public void setAxis(CalibratedAxis calibratedAxis, int i) { - axes[i] = calibratedAxis; - } - - @Override - public double realMin(int i) { - return Double.NEGATIVE_INFINITY; - } - - @Override - public void realMin(double[] doubles) { - for( int i = 0; i < doubles.length; i++ ) { - doubles[i] = Double.NEGATIVE_INFINITY; - } - } - - @Override - public void realMin(RealPositionable realPositionable) { - for( int i = 0; i < realPositionable.numDimensions(); i++ ) { - realPositionable.move(Double.NEGATIVE_INFINITY, i); - } - } - - @Override - public double realMax(int i) { - return Double.POSITIVE_INFINITY; - } - - @Override - public void realMax(double[] doubles) { - for( int i = 0; i < doubles.length; i++ ) { - doubles[i] = Double.POSITIVE_INFINITY; - } - } - - @Override - public void realMax(RealPositionable realPositionable) { - for( int i = 0; i < realPositionable.numDimensions(); i++ ) { - realPositionable.move(Double.POSITIVE_INFINITY, i); - } - } - - @Override - public int numDimensions() { - return axes.length; - } - - public void setCamera(Camera camera) { - this.camera = camera; - setActiveObserver(camera); - } - - public void setActiveObserver(Camera screenshotCam) { - getScene().setActiveObserver(screenshotCam); - } - - public Camera getActiveObserver() { - return getScene().getActiveObserver(); - } - - public void setCenterOnNewNodes(boolean centerOnNewNodes) { - this.centerOnNewNodes = centerOnNewNodes; - } - - public boolean getCenterOnNewNodes() { - return centerOnNewNodes; - } - - public void setBlockOnNewNodes(boolean blockOnNewNodes) { - this.blockOnNewNodes = blockOnNewNodes; - } - - public boolean getBlockOnNewNodes() { - return blockOnNewNodes; - } - - public class TransparentSlider extends JSlider { - - public TransparentSlider() { - // Important, we taking over the filling of the - // component... - setOpaque(false); - setBackground(Color.DARK_GRAY); - setForeground(Color.LIGHT_GRAY); - } - - @Override - protected void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g.create(); - g2d.setColor(getBackground()); - g2d.setComposite(AlphaComposite.SrcOver.derive(0.9f)); - g2d.fillRect(0, 0, getWidth(), getHeight()); - g2d.dispose(); - - super.paintComponent(g); - } - - } - - class showHelpDisplay implements ClickBehaviour { - - @Override public void click( int x, int y ) { - // show nice dialog box with the help - getScijavaContext().getService(CommandService.class).run(Help.class,true); - } - } - - /** - * Return a list of all nodes that match a given predicate function - * @param nodeMatchPredicate, returns true if a node is a match - * @return list of nodes that match the predicate - */ - public List findNodes(Function1 nodeMatchPredicate) { - return getScene().discover(getScene(), nodeMatchPredicate, false); - } - - /* - * Convenience function for getting a string of info about a Node - */ - public String nodeInfoString(Node n) { - return "Node name: " + n.getName() + " Node type: " + n.getNodeType() + " To String: " + n; - } - - /** - * Static launching method - * - * @return a newly created SciView - */ - public static SciView create() throws Exception { - SceneryBase.xinitThreads(); - - FlatLightLaf.install(); - try { - UIManager.setLookAndFeel( new FlatLightLaf() ); - } catch( Exception ex ) { - System.err.println( "Failed to initialize Flat Light LaF, falling back to Swing default." ); - } - - System.setProperty( "scijava.log.level:sc.iview", "debug" ); - Context context = new Context( ImageJService.class, SciJavaService.class, SCIFIOService.class, ThreadService.class); - - SciViewService sciViewService = context.service( SciViewService.class ); - SciView sciView = sciViewService.getOrCreateActiveSciView(); - - return sciView; - } - - /** - * Static launching method - * [DEPRECATED] use SciView.create() instead - * - * @return a newly created SciView - */ - @Deprecated - public static SciView createSciView() throws Exception { - return create(); - } - -} diff --git a/src/main/java/sc/iview/SciView.kt b/src/main/java/sc/iview/SciView.kt new file mode 100644 index 00000000..dbdc1a3b --- /dev/null +++ b/src/main/java/sc/iview/SciView.kt @@ -0,0 +1,2457 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview + +import bdv.BigDataViewer +import bdv.cache.CacheControl +import bdv.tools.brightness.ConverterSetup +import bdv.util.AxisOrder +import bdv.util.RandomAccessibleIntervalSource +import bdv.util.RandomAccessibleIntervalSource4D +import bdv.util.volatiles.VolatileView +import bdv.viewer.Source +import bdv.viewer.SourceAndConverter +import cleargl.GLVector +import com.formdev.flatlaf.FlatLightLaf +import com.intellij.ui.tabs.JBTabsPosition +import com.intellij.ui.tabs.TabInfo +import com.intellij.ui.tabs.impl.JBEditorTabs +import graphics.scenery.* +import graphics.scenery.Box +import graphics.scenery.Scene.RaycastResult +import graphics.scenery.backends.Renderer +import graphics.scenery.backends.opengl.OpenGLRenderer +import graphics.scenery.backends.vulkan.VulkanRenderer +import graphics.scenery.controls.InputHandler +import graphics.scenery.controls.OpenVRHMD +import graphics.scenery.controls.TrackerInput +import graphics.scenery.controls.behaviours.ArcballCameraControl +import graphics.scenery.controls.behaviours.FPSCameraControl +import graphics.scenery.controls.behaviours.MovementCommand +import graphics.scenery.controls.behaviours.SelectCommand +import graphics.scenery.utils.* +import graphics.scenery.utils.ExtractsNatives.Companion.getPlatform +import graphics.scenery.volumes.Colormap +import graphics.scenery.volumes.RAIVolume +import graphics.scenery.volumes.Volume +import graphics.scenery.volumes.Volume.Companion.fromXML +import graphics.scenery.volumes.Volume.Companion.setupId +import graphics.scenery.volumes.Volume.VolumeDataSource.RAISource +import io.scif.SCIFIOService +import net.imagej.Dataset +import net.imagej.ImageJService +import net.imagej.axis.CalibratedAxis +import net.imagej.axis.DefaultAxisType +import net.imagej.axis.DefaultLinearAxis +import net.imagej.interval.CalibratedRealInterval +import net.imagej.lut.LUTService +import net.imagej.mesh.Mesh +import net.imagej.units.UnitService +import net.imglib2.* +import net.imglib2.display.ColorTable +import net.imglib2.img.Img +import net.imglib2.realtransform.AffineTransform3D +import net.imglib2.type.numeric.ARGBType +import net.imglib2.type.numeric.NumericType +import net.imglib2.type.numeric.RealType +import net.imglib2.type.numeric.integer.UnsignedByteType +import net.imglib2.view.Views +import org.joml.Quaternionf +import org.joml.Vector2f +import org.joml.Vector3f +import org.lwjgl.system.Platform +import org.scijava.Context +import org.scijava.`object`.ObjectService +import org.scijava.command.CommandService +import org.scijava.display.Display +import org.scijava.event.EventHandler +import org.scijava.event.EventService +import org.scijava.io.IOService +import org.scijava.log.LogLevel +import org.scijava.log.LogService +import org.scijava.menu.MenuService +import org.scijava.plugin.Parameter +import org.scijava.service.SciJavaService +import org.scijava.thread.ThreadService +import org.scijava.ui.behaviour.Behaviour +import org.scijava.ui.behaviour.ClickBehaviour +import org.scijava.ui.swing.menu.SwingJMenuBarCreator +import org.scijava.util.ColorRGB +import org.scijava.util.Colors +import org.scijava.util.VersionUtils +import sc.iview.commands.help.Help +import sc.iview.commands.view.NodePropertyEditor +import sc.iview.controls.behaviours.* +import sc.iview.event.NodeActivatedEvent +import sc.iview.event.NodeAddedEvent +import sc.iview.event.NodeChangedEvent +import sc.iview.event.NodeRemovedEvent +import sc.iview.process.MeshConverter +import sc.iview.ui.ContextPopUpNodeChooser +import sc.iview.ui.ProgressPie +import sc.iview.ui.REPLPane +import sc.iview.ui.TaskManager +import tpietzsch.example2.VolumeViewerOptions +import java.awt.* +import java.awt.event.* +import java.awt.image.BufferedImage +import java.awt.image.DataBufferByte +import java.io.* +import java.net.URL +import java.nio.ByteBuffer +import java.nio.FloatBuffer +import java.util.* +import java.util.concurrent.Future +import java.util.function.Consumer +import java.util.function.Function +import java.util.function.Predicate +import java.util.function.Supplier +import java.util.stream.Collectors +import javax.imageio.ImageIO +import javax.script.ScriptException +import javax.swing.* +import kotlin.math.acos +import kotlin.math.cos +import kotlin.math.sin + +/** + * Main SciView class. + * + * @author Kyle Harrington + */ +// we suppress unused warnings here because @Parameter-annotated fields +// get updated automatically by SciJava. +class SciView : SceneryBase, CalibratedRealInterval { + private val sceneryPanel = arrayOf(null) + + /** + * Mouse controls for FPS movement and Arcball rotation + */ + lateinit var targetArcball: SciView.AnimatedCenteringBeforeArcBallControl + protected var fpsControl: FPSCameraControl? = null + /* + * Return the default floor object + *//* + * Set the default floor object + */ + /** + * The floor that orients the user in the scene + */ + var floor: Node? = null + protected var vrActive = false + + /** + * The primary camera/observer in the scene + */ + var camera: Camera? = null + set(value) { + field = value + setActiveObserver(field) + } + + /** + * Geometry/Image information of scene + */ + private lateinit var axes: Array + + @Parameter + private val log: LogService? = null + + @Parameter + private val menus: MenuService? = null + + @Parameter + private val io: IOService? = null + + @Parameter + private val eventService: EventService? = null + + @Parameter + private val lutService: LUTService? = null + + @Parameter + private val threadService: ThreadService? = null + + @Parameter + private val objectService: ObjectService? = null + + @Parameter + private val unitService: UnitService? = null + + /** + * Queue keeps track of the currently running animations + */ + private var animations: Queue>? = null + + /** + * Animation pause tracking + */ + private var animating = false + + /** + * This tracks the actively selected Node in the scene + */ + var activeNode: Node? = null + private set + + /** + * Speeds for input controls + */ + var controlsParameters: ControlsParameters = ControlsParameters() + /* + * Return the SciJava Display that contains SciView + *//* + * Set the SciJava Display + */ var display: Display<*>? = null + private var splashLabel: SplashLabel? = null + + /** + * Return the current SceneryJPanel. This is necessary for custom context menus + * @return panel the current SceneryJPanel + */ + var sceneryJPanel: SceneryJPanel? = null + private set + private var mainSplitPane: JSplitPane? = null + private var inspector: JSplitPane? = null + private var interpreterPane: REPLPane? = null + private var nodePropertyEditor: NodePropertyEditor? = null + var lights: ArrayList? = null + private set + private var controlStack: Stack>? = null + private var frame: JFrame? = null + private val notAbstractNode: Predicate = Predicate { node: Node -> !(node is Camera || node is Light || node === floor) } + var isClosed = false + private set + private val notAbstractBranchingFunction = Function { node: Node -> node.children.stream().filter(notAbstractNode).collect(Collectors.toList()) } + + val taskManager = TaskManager() + + // If true, then when a new node is added to the scene, the camera will refocus on this node by default + var centerOnNewNodes = false + + // If true, then when a new node is added the thread will block until the node is added to the scene. This is required for + // centerOnNewNodes + var blockOnNewNodes = false + private var headlight: PointLight? = null + + constructor(context: Context) : super("SciView", 1280, 720, false, context) { + context.inject(this) + } + + constructor(applicationName: String?, windowWidth: Int, windowHeight: Int) : super(applicationName!!, windowWidth, windowHeight, false) {} + + fun publicGetInputHandler(): InputHandler { + return inputHandler!! + } + + /** + * Toggle video recording with scenery's video recording mechanism + * Note: this video recording may skip frames because it is asynchronous + */ + fun toggleRecordVideo() { + if (renderer is OpenGLRenderer) (renderer as OpenGLRenderer).recordMovie() else (renderer as VulkanRenderer).recordMovie() + } + + /** + * Toggle video recording with scenery's video recording mechanism + * Note: this video recording may skip frames because it is asynchronous + * + * @param filename destination for saving video + * @param overwrite should the file be replaced, otherwise a unique incrementing counter will be appended + */ + fun toggleRecordVideo(filename: String?, overwrite: Boolean) { + if (renderer is OpenGLRenderer) (renderer as OpenGLRenderer).recordMovie(filename!!, overwrite) else (renderer as VulkanRenderer).recordMovie(filename!!, overwrite) + } + + /** + * This pushes the current input setup onto a stack that allows them to be restored with restoreControls + * This pushes the current input setup onto a stack that allows them to be restored with restoreControls. + * It stacks in particular: all keybindings, all Behaviours, and all step sizes and mouse sensitivities + * (which are held together in [ControlsParameters]). + * + * *Word of warning:* The stashing memorizes *references only* on currently used controls + * (such as, e.g., [MovementCommand], [FPSCameraControl] or [NodeTranslateControl]), + * it *does not* create an extra copy of any control. That said, if you modify any control + * object despite it was already stashed with this method, the change will be visible in the "stored" + * control and will not go away after the restore... To be on the safe side for now at least, *create + * new and modified* controls rather than directly changing them. + */ + fun stashControls() { + val controlState = HashMap() + val handler = inputHandler + if (handler == null) { + logger.error("InputHandler is null, cannot store controls") + return + } + + //behaviours: + for (actionName in handler.getAllBehaviours()) { + controlState[STASH_BEHAVIOUR_KEY + actionName] = handler.getBehaviour(actionName) as Any + } + + //bindings: + for (actionName in handler.getAllBehaviours()) { + for (trigger in handler.getKeyBindings(actionName)) { + controlState[STASH_BINDING_KEY + actionName] = trigger.toString() + } + } + + //finally, stash the control parameters + controlState[STASH_CONTROLSPARAMS_KEY] = controlsParameters + + //...and stash it! + controlStack!!.push(controlState) + } + + /** + * This pops/restores the previously stashed controls. Emits a warning if there are no stashed controls. + * It first clears all controls, and then resets in particular: all keybindings, all Behaviours, + * and all step sizes and mouse sensitivities (which are held together in [ControlsParameters]). + * + * *Some recent changes may not be removed* with this restore -- + * see discussion in the docs of [SciView.stashControls] for more details. + */ + fun restoreControls() { + if (controlStack!!.empty()) { + logger.warn("Not restoring controls, the controls stash stack is empty!") + return + } + val inputHandler = inputHandler + if (inputHandler == null) { + logger.error("InputHandler is null, cannot restore controls") + return + } + + //clear the input handler entirely + for (actionName in inputHandler.getAllBehaviours()) { + inputHandler.removeKeyBinding(actionName) + inputHandler.removeBehaviour(actionName) + } + + //retrieve the most recent stash with controls + val controlState = controlStack!!.pop() + for (control in controlState.entries) { + var key: String + when { + control.key.startsWith(STASH_BEHAVIOUR_KEY) -> { + //processing behaviour + key = control.key.substring(STASH_BEHAVIOUR_KEY.length) + inputHandler.addBehaviour(key, control.value as Behaviour) + } + control.key.startsWith(STASH_BINDING_KEY) -> { + //processing key binding + key = control.key.substring(STASH_BINDING_KEY.length) + inputHandler.addKeyBinding(key, (control.value as String)) + } + control.key.startsWith(STASH_CONTROLSPARAMS_KEY) -> { + //processing mouse sensitivities and step sizes... + controlsParameters = control.value as ControlsParameters + } + } + } + } + + private val STASH_BEHAVIOUR_KEY = "behaviour:" + private val STASH_BINDING_KEY = "binding:" + private val STASH_CONTROLSPARAMS_KEY = "parameters:" + + /** + * Reset the scene to initial conditions + */ + fun reset() { + // Initialize the 3D axes + axes = arrayOf( + DefaultLinearAxis(DefaultAxisType("X", true), "um", 1.0), + DefaultLinearAxis(DefaultAxisType("Y", true), "um", 1.0), + DefaultLinearAxis(DefaultAxisType("Z", true), "um", 1.0) + ) + + // Remove everything except camera + val toRemove = getSceneNodes { n: Node? -> n !is Camera } + for (n in toRemove) { + deleteNode(n, false) + } + + // Setup camera + if (camera == null) { + camera = DetachedHeadCamera() + (camera as DetachedHeadCamera).position = Vector3f(0.0f, 1.65f, 0.0f) + scene.addChild(camera as DetachedHeadCamera) + } + camera!!.position = Vector3f(0.0f, 1.65f, 5.0f) + camera!!.perspectiveCamera(50.0f, windowWidth, windowHeight, 0.1f, 1000.0f) + + // Setup lights + val tetrahedron = arrayOfNulls(4) + tetrahedron[0] = Vector3f(1.0f, 0f, -1.0f / Math.sqrt(2.0).toFloat()) + tetrahedron[1] = Vector3f(-1.0f, 0f, -1.0f / Math.sqrt(2.0).toFloat()) + tetrahedron[2] = Vector3f(0.0f, 1.0f, 1.0f / Math.sqrt(2.0).toFloat()) + tetrahedron[3] = Vector3f(0.0f, -1.0f, 1.0f / Math.sqrt(2.0).toFloat()) + lights = ArrayList() + for (i in 0..3) { // TODO allow # initial lights to be customizable? + val light = PointLight(150.0f) + light.position = tetrahedron[i]!!.mul(25.0f) + light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) + light.intensity = 1.0f + lights!!.add(light) + //camera.addChild( light ); + scene.addChild(light) + } + + // Make a headlight for the camera + headlight = PointLight(150.0f) + headlight!!.position = Vector3f(0f, 0f, -1f).mul(25.0f) + headlight!!.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) + headlight!!.intensity = 0.5f + headlight!!.name = "headlight" + val lightSphere = Icosphere(1.0f, 2) + headlight!!.addChild(lightSphere) + lightSphere.material.diffuse = headlight!!.emissionColor + lightSphere.material.specular = headlight!!.emissionColor + lightSphere.material.ambient = headlight!!.emissionColor + lightSphere.material.wireframe = true + lightSphere.visible = false + //lights.add( light ); + camera!!.nearPlaneDistance = 0.01f + camera!!.farPlaneDistance = 1000.0f + camera!!.addChild(headlight!!) + floor = InfinitePlane() //new Box( new Vector3f( 500f, 0.2f, 500f ) ); + (floor as InfinitePlane).type = InfinitePlane.Type.Grid + (floor as Node).name = "Floor" + scene.addChild(floor as Node) + } + + /** + * Initialization of SWING and scenery. Also triggers an initial population of lights/camera in the scene + */ + override fun init() { + + // Darcula dependency went missing from maven repo, factor it out +// if(Boolean.parseBoolean(System.getProperty("sciview.useDarcula", "false"))) { +// try { +// BasicLookAndFeel darcula = new DarculaLaf(); +// UIManager.setLookAndFeel(darcula); +// } catch (Exception e) { +// getLogger().info("Could not load Darcula Look and Feel"); +// } +// } + val logLevel = System.getProperty("scenery.LogLevel", "info") + log!!.level = LogLevel.value(logLevel) + LogbackUtils.setLogLevel(null, logLevel) + System.getProperties().stringPropertyNames().forEach(Consumer { name: String -> + if (name.startsWith("scenery.LogLevel")) { + LogbackUtils.setLogLevel("", System.getProperty(name, "info")) + } + }) + + // determine imagej-launcher version and to disable Vulkan if XInitThreads() fix + // is not deployed + try { + val launcherClass = Class.forName("net.imagej.launcher.ClassLauncher") + var versionString = VersionUtils.getVersion(launcherClass) + if (versionString != null && getPlatform() == ExtractsNatives.Platform.LINUX) { + versionString = versionString.substring(0, 5) + val launcherVersion = Version(versionString) + val nonWorkingVersion = Version("4.0.5") + if (launcherVersion.compareTo(nonWorkingVersion) <= 0 + && !java.lang.Boolean.parseBoolean(System.getProperty("sciview.DisableLauncherVersionCheck", "false"))) { + logger.info("imagej-launcher version smaller or equal to non-working version ($versionString vs. 4.0.5), disabling Vulkan as rendering backend. Disable check by setting 'scenery.DisableLauncherVersionCheck' system property to 'true'.") + System.setProperty("scenery.Renderer", "OpenGLRenderer") + } else { + logger.info("imagej-launcher version bigger that non-working version ($versionString vs. 4.0.5), all good.") + } + } + } catch (cnfe: ClassNotFoundException) { + // Didn't find the launcher, so we're probably good. + logger.info("imagej-launcher not found, not touching renderer preferences.") + } + + // TODO: check for jdk 8 v. jdk 11 on linux and choose renderer accordingly + if (Platform.get() === Platform.LINUX) { + var version = System.getProperty("java.version") + if (version.startsWith("1.")) { + version = version.substring(2, 3) + } else { + val dot = version.indexOf(".") + if (dot != -1) { + version = version.substring(0, dot) + } + } + + // If Linux and JDK 8, then use OpenGLRenderer + if (version == "8") System.setProperty("scenery.Renderer", "OpenGLRenderer") + } + var x: Int + var y: Int + try { + val screenSize = Toolkit.getDefaultToolkit().screenSize + x = screenSize.width / 2 - windowWidth / 2 + y = screenSize.height / 2 - windowHeight / 2 + } catch (e: HeadlessException) { + x = 10 + y = 10 + } + frame = JFrame("SciView") + frame!!.layout = BorderLayout(0, 0) + frame!!.setSize(windowWidth, windowHeight) + frame!!.setLocation(x, y) + frame!!.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE + nodePropertyEditor = NodePropertyEditor(this) + val p = JPanel(BorderLayout(0, 0)) + sceneryJPanel = SceneryJPanel() + JPopupMenu.setDefaultLightWeightPopupEnabled(false) + val swingMenuBar = JMenuBar() + SwingJMenuBarCreator().createMenus(menus!!.getMenu("SciView"), swingMenuBar) + val bar = ProgressPie() +// progress.add(bar) + bar.value = 0.0 + bar.minimumSize = Dimension(30, 30) + bar.maximumSize = Dimension(30, 30) + bar.preferredSize = Dimension(30, 30) + val progressLabel = JLabel("") + progressLabel.horizontalAlignment = SwingConstants.RIGHT + swingMenuBar.add(javax.swing.Box.createHorizontalGlue()) + swingMenuBar.add(progressLabel) + swingMenuBar.add(bar) + frame!!.jMenuBar = swingMenuBar + p.layout = OverlayLayout(p) + p.background = Color(50, 48, 47) + p.add(sceneryJPanel, BorderLayout.CENTER) + + taskManager.update = { current -> + if(current != null) { + progressLabel.text = "${current.source}: ${current.status} " + bar.value = current.completion.toDouble() + } else { + progressLabel.text = "" + bar.value = 0.0 + } + + bar.repaint() + } + sceneryJPanel!!.isVisible = true + nodePropertyEditor!!.component // Initialize node property panel + val inspectorTree = nodePropertyEditor!!.tree + inspectorTree.toggleClickCount = 0 // This disables expanding menus on double click + val inspectorProperties = nodePropertyEditor!!.props + val tp = JBEditorTabs(null) + tp.tabsPosition = JBTabsPosition.right + tp.isSideComponentVertical = true + inspector = JSplitPane(JSplitPane.VERTICAL_SPLIT, // + JScrollPane(inspectorTree), + JScrollPane(inspectorProperties)) + inspector!!.dividerLocation = windowHeight / 3 + inspector!!.isContinuousLayout = true + inspector!!.border = BorderFactory.createEmptyBorder() + inspector!!.dividerSize = 1 + val inspectorIcon = getScaledImageIcon(this.javaClass.getResource("toolbox.png"), 16, 16) + val tiInspector = TabInfo(inspector, inspectorIcon) + tiInspector.text = "" + tp.addTab(tiInspector) + + // We need to get the surface scale here before initialising scenery's renderer, as + // the information is needed already at initialisation time. + val dt = frame!!.graphicsConfiguration.defaultTransform + val surfaceScale = Vector2f(dt.scaleX.toFloat(), dt.scaleY.toFloat()) + settings.set("Renderer.SurfaceScale", surfaceScale) + interpreterPane = REPLPane(scijavaContext) + interpreterPane!!.component.border = BorderFactory.createEmptyBorder() + val interpreterIcon = getScaledImageIcon(this.javaClass.getResource("terminal.png"), 16, 16) + val tiREPL = TabInfo(interpreterPane!!.component, interpreterIcon) + tiREPL.text = "" + tp.addTab(tiREPL) + tp.addTabMouseListener(object : MouseListener { + override fun mouseClicked(e: MouseEvent) { + if (e.clickCount == 2) { + toggleSidebar() + } + } + + override fun mousePressed(e: MouseEvent) {} + override fun mouseReleased(e: MouseEvent) {} + override fun mouseEntered(e: MouseEvent) {} + override fun mouseExited(e: MouseEvent) {} + }) + initializeInterpreter() + mainSplitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, // + p, + tp.component + ) + mainSplitPane!!.dividerLocation = frame!!.size.width - 36 + mainSplitPane!!.border = BorderFactory.createEmptyBorder() + mainSplitPane!!.dividerSize = 1 + mainSplitPane!!.resizeWeight = 0.9 + sidebarHidden = true + + //frame.add(mainSplitPane, BorderLayout.CENTER); + frame!!.add(mainSplitPane, BorderLayout.CENTER) + val sciView = this + frame!!.addWindowListener(object : WindowAdapter() { + override fun windowClosing(e: WindowEvent) { + logger.debug("Closing SciView window.") + close() + scijavaContext!!.service(SciViewService::class.java).close(sciView) + isClosed = true + } + }) + splashLabel = SplashLabel() + frame!!.glassPane = splashLabel + frame!!.glassPane.isVisible = true + frame!!.glassPane.requestFocusInWindow() + // frame.getGlassPane().setBackground(new java.awt.Color(50, 48, 47, 255)); + frame!!.isVisible = true + sceneryPanel[0] = sceneryJPanel + renderer = Renderer.createRenderer(hub, applicationName, scene, + windowWidth, windowHeight, + sceneryPanel[0]) + hub.add(SceneryElement.Renderer, renderer!!) + reset() + animations = LinkedList() + controlStack = Stack() + SwingUtilities.invokeLater { + try { + while (!getSceneryRenderer()!!.firstImageReady) { + logger.debug("Waiting for renderer initialisation") + Thread.sleep(300) + } + Thread.sleep(200) + } catch (e: InterruptedException) { + logger.error("Renderer construction interrupted.") + } + nodePropertyEditor!!.rebuildTree() + logger.info("Done initializing SciView") + + // subscribe to Node{Added, Removed, Changed} events, happens automatically +// eventService!!.subscribe(this) + frame!!.glassPane.isVisible = false + sceneryJPanel!!.isVisible = true + + // install hook to keep inspector updated on external changes (scripting, etc) + scene.onNodePropertiesChanged["updateInspector"] = { node: Node -> + if (node === nodePropertyEditor!!.currentNode) { + nodePropertyEditor!!.updateProperties(node) + } + } + + // Enable push rendering by default + renderer!!.pushMode = true + sciView.camera!!.setPosition(1.65, 1) + } + } + + private var sidebarHidden = false + private var previousSidebarPosition = 0 + fun toggleSidebar(): Boolean { + if (!sidebarHidden) { + previousSidebarPosition = mainSplitPane!!.dividerLocation + // TODO: remove hard-coded tab width + mainSplitPane!!.dividerLocation = frame!!.size.width - 36 + sidebarHidden = true + } else { + if (previousSidebarPosition == 0) { + previousSidebarPosition = windowWidth / 3 * 2 + } + mainSplitPane!!.dividerLocation = previousSidebarPosition + sidebarHidden = false + } + return sidebarHidden + } + + private fun getScaledImageIcon(resource: URL, width: Int, height: Int): ImageIcon { + val first = ImageIcon(resource) + val resizedImg = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + val g2 = resizedImg.createGraphics() + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR) + g2.drawImage(first.image, 0, 0, width, height, null) + g2.dispose() + return ImageIcon(resizedImg) + } + + private fun initializeInterpreter() { + val startupCode = Scanner(SciView::class.java.getResourceAsStream("startup.py"), "UTF-8").useDelimiter("\\A").next() + interpreterPane!!.repl.interpreter.bindings["sciView"] = this + try { + interpreterPane!!.repl.interpreter.eval(startupCode) + } catch (e: ScriptException) { + e.printStackTrace() + } + } + + /* + * Completely close the SciView window + cleanup + */ + fun closeWindow() { + frame!!.dispose() + } + + /* + * Return true if the scene has been initialized + */ + val isInitialized: Boolean + get() = sceneInitialized() + + /** + * Place the scene into the center of camera view, and zoom in/out such + * that the whole scene is in the view (everything would be visible if it + * would not be potentially occluded). + */ + fun fitCameraToScene() { + centerOnNode(scene) + //TODO: smooth zoom in/out VLADO vlado Vlado + } + + /** + * Place the scene into the center of camera view. + */ + fun centerOnScene() { + centerOnNode(scene) + } + /* + * Get the InputHandler that is managing mouse, input, VR controls, etc. + */ + val sceneryInputHandler: InputHandler + get() = inputHandler!! + + /* + * Return a bounding box around a subgraph of the scenegraph + */ + fun getSubgraphBoundingBox(n: Node): OrientedBoundingBox? { + val predicate = Function> { node: Node -> node.children } + return getSubgraphBoundingBox(n, predicate) + } + + /* + * Return a bounding box around a subgraph of the scenegraph + */ + fun getSubgraphBoundingBox(n: Node, branchFunction: Function>): OrientedBoundingBox? { + if (n.boundingBox == null && n.children.size != 0) { + return n.getMaximumBoundingBox().asWorld() + } + val branches = branchFunction.apply(n) + if (branches.size == 0) { + return if (n.boundingBox == null) null else n.boundingBox!!.asWorld() + } + var bb = n.getMaximumBoundingBox() + for (c in branches) { + val cBB = getSubgraphBoundingBox(c, branchFunction) + if (cBB != null) bb = bb.expand(bb, cBB) + } + return bb + } + + /** + * Place the active node into the center of camera view. + */ + fun centerOnActiveNode() { + if (activeNode == null) return + centerOnNode(activeNode) + } + + /** + * Place the specified node into the center of camera view. + */ + fun centerOnNode(currentNode: Node?) { + if (currentNode == null) { + log!!.info("Cannot center on null node.") + return + } + + //center the on the same spot as ArcBall does + centerOnPosition(currentNode.getMaximumBoundingBox().getBoundingSphere().origin) + } + + /** + * Center the camera on the specified Node + */ + fun centerOnPosition(currentPos: Vector3f?) { + //desired view direction in world coords + val worldDirVec = Vector3f(currentPos).sub(camera!!.position) + if (worldDirVec.lengthSquared() < 0.01) { + //ill defined task, happens typically when cam is inside the node which we want center on + log!!.info("Camera is on the spot you want to look at. Please, move the camera away first.") + return + } + val camForwardXZ = Vector2f(camera!!.forward.x, camera!!.forward.z) + val wantLookAtXZ = Vector2f(worldDirVec.x, worldDirVec.z) + var totalYawAng = camForwardXZ.normalize().dot(wantLookAtXZ.normalize()).toDouble() + //while mathematically impossible, cumulated numerical inaccuracies have different opinion + totalYawAng = if (totalYawAng > 1) { + 0.0 + } else { + acos(totalYawAng) + } + + //switch direction? + camForwardXZ[camForwardXZ.y] = -camForwardXZ.x + if (wantLookAtXZ.dot(camForwardXZ) > 0) totalYawAng *= -1.0 + val camForwardYed = Vector3f(camera!!.forward) + Quaternionf().rotateXYZ(0f, (-totalYawAng).toFloat(), 0f).normalize().transform(camForwardYed) + var totalPitchAng = camForwardYed.normalize().dot(worldDirVec.normalize()).toDouble() + totalPitchAng = if (totalPitchAng > 1) { + 0.0 + } else { + acos(totalPitchAng) + } + + //switch direction? + if (camera!!.forward.y > worldDirVec.y) totalPitchAng *= -1.0 + if (camera!!.up.y < 0) totalPitchAng *= -1.0 + + //animation options: control delay between animation frames -- fluency + val rotPausePerStep: Long = 30 //miliseconds + + //animation options: control max number of steps -- upper limit on total time for animation + val rotMaxSteps = 999999 //effectively disabled.... + + //animation options: the hardcoded 5 deg (0.087 rad) -- smoothness + //how many steps when max update/move is 5 deg + val totalDeltaAng = Math.max(Math.abs(totalPitchAng), Math.abs(totalYawAng)).toFloat() + var rotSteps = Math.ceil(totalDeltaAng / 0.087).toInt() + if (rotSteps > rotMaxSteps) rotSteps = rotMaxSteps + + /* + log.info("centering over "+rotSteps+" steps the pitch " + 180*totalPitchAng/Math.PI + + " and the yaw " + 180*totalYawAng/Math.PI); + */ + + //angular progress aux variables + var donePitchAng = 0.0 + var doneYawAng = 0.0 + var deltaAng: Float + camera!!.targeted = false + var i = 1 + while (i <= rotSteps) { + + //this emulates ease-in ease-out animation, both vars are in [0:1] + var timeProgress = i.toFloat() / rotSteps + val angProgress = (if (2.let { timeProgress *= it; timeProgress } <= 1) //two cubics connected smoothly into S-shape curve from [0,0] to [1,1] + timeProgress * timeProgress * timeProgress else 2.let { timeProgress -= it; timeProgress } * timeProgress * timeProgress + 2) / 2 + + //rotate now by this ang: "where should I be by now" minus "where I got last time" + deltaAng = (angProgress * totalPitchAng - donePitchAng).toFloat() + val pitchQ = Quaternionf().rotateXYZ(-deltaAng, 0f, 0f).normalize() + deltaAng = (angProgress * totalYawAng - doneYawAng).toFloat() + val yawQ = Quaternionf().rotateXYZ(0f, deltaAng, 0f).normalize() + camera!!.rotation = pitchQ.mul(camera!!.rotation).mul(yawQ).normalize() + donePitchAng = angProgress * totalPitchAng + doneYawAng = angProgress * totalYawAng + try { + Thread.sleep(rotPausePerStep) + } catch (e: InterruptedException) { + i = rotSteps + } + ++i + } + } + + /** + * Activate the node, and center the view on it. + * @param n + * @return the currently active node + */ + fun setActiveCenteredNode(n: Node?): Node? { + //activate... + val ret = setActiveNode(n) + //...and center it + ret?.let { centerOnNode(it) } + return ret + } + + //a couple of shortcut methods to readout controls params + fun getFPSSpeedSlow(): Float { + return controlsParameters.getFpsSpeedSlow() + } + + fun getFPSSpeedFast(): Float { + return controlsParameters.getFpsSpeedFast() + } + + fun getFPSSpeedVeryFast(): Float { + return controlsParameters.getFpsSpeedVeryFast() + } + + fun getMouseSpeed(): Float { + return controlsParameters.getMouseSpeedMult() + } + + fun getMouseScrollSpeed(): Float { + return controlsParameters.getMouseScrollMult() + } + + //a couple of setters with scene sensible boundary checks + fun setFPSSpeedSlow(slowSpeed: Float) { + controlsParameters.setFpsSpeedSlow(paramWithinBounds(slowSpeed, FPSSPEED_MINBOUND_SLOW, FPSSPEED_MAXBOUND_SLOW)) + } + + fun setFPSSpeedFast(fastSpeed: Float) { + controlsParameters.setFpsSpeedFast(paramWithinBounds(fastSpeed, FPSSPEED_MINBOUND_FAST, FPSSPEED_MAXBOUND_FAST)) + } + + fun setFPSSpeedVeryFast(veryFastSpeed: Float) { + controlsParameters.setFpsSpeedVeryFast(paramWithinBounds(veryFastSpeed, FPSSPEED_MINBOUND_VERYFAST, FPSSPEED_MAXBOUND_VERYFAST)) + } + + fun setFPSSpeed(newBaseSpeed: Float) { + // we don't want to escape bounds checking + // (so we call "our" methods rather than directly the controlsParameters) + setFPSSpeedSlow(1f * newBaseSpeed) + setFPSSpeedFast(20f * newBaseSpeed) + setFPSSpeedVeryFast(500f * newBaseSpeed) + + //report what's been set in the end + log!!.debug("FPS speeds: slow=" + controlsParameters.getFpsSpeedSlow() + .toString() + ", fast=" + controlsParameters.getFpsSpeedFast() + .toString() + ", very fast=" + controlsParameters.getFpsSpeedVeryFast()) + } + + fun setMouseSpeed(newSpeed: Float) { + controlsParameters.setMouseSpeedMult(paramWithinBounds(newSpeed, MOUSESPEED_MINBOUND, MOUSESPEED_MAXBOUND)) + log!!.debug("Mouse movement speed: " + controlsParameters.getMouseSpeedMult()) + } + + fun setMouseScrollSpeed(newSpeed: Float) { + controlsParameters.setMouseScrollMult(paramWithinBounds(newSpeed, MOUSESCROLL_MINBOUND, MOUSESCROLL_MAXBOUND)) + log!!.debug("Mouse scroll speed: " + controlsParameters.getMouseScrollMult()) + } + + // TODO replace with coerce? + private fun paramWithinBounds(param: Float, minBound: Float, maxBound: Float): Float { + var newParam = param + if (newParam < minBound) newParam = minBound else if (newParam > maxBound) newParam = maxBound + return newParam + } + + + fun setObjectSelectionMode() { + val selectAction = { nearest: RaycastResult, x: Int, y: Int -> + if (!nearest.matches.isEmpty()) { + // copy reference on the last object picking result into "public domain" + // (this must happen before the ContextPopUpNodeChooser menu!) + objectSelectionLastResult = nearest + + // Setup the context menu for this picking + // (in the menu, the user will chose node herself) + ContextPopUpNodeChooser(this).show(sceneryJPanel, x, y) + } + } + setObjectSelectionMode(selectAction) + } + + var objectSelectionLastResult: RaycastResult? = null + + /* + * Set the action used during object selection + */ + fun setObjectSelectionMode(selectAction: Function3?) { + val h = inputHandler + val ignoredObjects = ArrayList>() + ignoredObjects.add(BoundingGrid::class.java) + ignoredObjects.add(Camera::class.java) //do not mess with "scene params", allow only "scene data" to be selected + ignoredObjects.add(DetachedHeadCamera::class.java) + ignoredObjects.add(DirectionalLight::class.java) + ignoredObjects.add(PointLight::class.java) + if (h == null) { + logger.error("InputHandler is null, cannot change object selection mode.") + return + } + h.addBehaviour("node: choose one from the view panel", + SelectCommand("objectSelector", renderer!!, scene, + { scene.findObserver() }, false, ignoredObjects, + selectAction!!)) + h.addKeyBinding("node: choose one from the view panel", "double-click button1") + } + + /* + * Initial configuration of the scenery InputHandler + * This is automatically called and should not be used directly + */ + override fun inputSetup() { + val h = inputHandler + if (h == null) { + logger.error("InputHandler is null, cannot run input setup.") + return + } + //when we get here, the Behaviours and key bindings from scenery are already in place + + //possibly, disable some (unused?) controls from scenery + /* + h.removeBehaviour( "gamepad_camera_control"); + h.removeKeyBinding("gamepad_camera_control"); + h.removeBehaviour( "gamepad_movement_control"); + h.removeKeyBinding("gamepad_movement_control"); + */ + + // node-selection and node-manipulation (translate & rotate) controls + setObjectSelectionMode() + val nodeTranslateControl = NodeTranslateControl(this) + h.addBehaviour("node: move selected one left, right, up, or down", nodeTranslateControl) + h.addKeyBinding("node: move selected one left, right, up, or down", "ctrl button1") + h.addBehaviour("node: move selected one closer or further away", nodeTranslateControl) + h.addKeyBinding("node: move selected one closer or further away", "ctrl scroll") + h.addBehaviour("node: rotate selected one", NodeRotateControl(this)) + h.addKeyBinding("node: rotate selected one", "ctrl shift button1") + + // within-scene navigation: ArcBall and FPS + enableArcBallControl() + enableFPSControl() + + // whole-scene rolling + h.addBehaviour("view: rotate (roll) clock-wise", SceneRollControl(this, +0.05f)) //2.8 deg + h.addKeyBinding("view: rotate (roll) clock-wise", "R") + h.addBehaviour("view: rotate (roll) counter clock-wise", SceneRollControl(this, -0.05f)) + h.addKeyBinding("view: rotate (roll) counter clock-wise", "shift R") + h.addBehaviour("view: rotate (roll) with mouse", h.getBehaviour("view: rotate (roll) clock-wise")!!) + h.addKeyBinding("view: rotate (roll) with mouse", "ctrl button3") + + // adjusters of various controls sensitivities + h.addBehaviour("moves: step size decrease", ClickBehaviour { _: Int, _: Int -> setFPSSpeed(getFPSSpeedSlow() - 0.01f) }) + h.addKeyBinding("moves: step size decrease", "MINUS") + h.addBehaviour("moves: step size increase", ClickBehaviour { _: Int, _: Int -> setFPSSpeed(getFPSSpeedSlow() + 0.01f) }) + h.addKeyBinding("moves: step size increase", "EQUALS") + h.addBehaviour("mouse: move sensitivity decrease", ClickBehaviour { _: Int, _: Int -> setMouseSpeed(getMouseSpeed() - 0.02f) }) + h.addKeyBinding("mouse: move sensitivity decrease", "M MINUS") + h.addBehaviour("mouse: move sensitivity increase", ClickBehaviour { _: Int, _: Int -> setMouseSpeed(getMouseSpeed() + 0.02f) }) + h.addKeyBinding("mouse: move sensitivity increase", "M EQUALS") + h.addBehaviour("mouse: scroll sensitivity decrease", ClickBehaviour { _: Int, _: Int -> setMouseScrollSpeed(getMouseScrollSpeed() - 0.3f) }) + h.addKeyBinding("mouse: scroll sensitivity decrease", "S MINUS") + h.addBehaviour("mouse: scroll sensitivity increase", ClickBehaviour { _: Int, _: Int -> setMouseScrollSpeed(getMouseScrollSpeed() + 0.3f) }) + h.addKeyBinding("mouse: scroll sensitivity increase", "S EQUALS") + + // help window + h.addBehaviour("show help", showHelpDisplay()) + h.addKeyBinding("show help", "F1") + } + + /* + * Change the control mode to circle around the active object in an arcball + */ + private fun enableArcBallControl() { + val h = inputHandler + if (h == null) { + logger.error("InputHandler is null, cannot setup arcball control.") + return + } + + val target: Vector3f = activeNode?.position ?: Vector3f(0.0f, 0.0f, 0.0f) + + //setup ArcballCameraControl from scenery, register it with SciView's controlsParameters + val cameraSupplier = Supplier { scene.findObserver() } + targetArcball = AnimatedCenteringBeforeArcBallControl("view: rotate it around selected node", cameraSupplier, + renderer!!.window.width, + renderer!!.window.height, target) + targetArcball.maximumDistance = Float.MAX_VALUE + controlsParameters.registerArcballCameraControl(targetArcball) + h.addBehaviour("view: rotate around selected node", targetArcball) + h.addKeyBinding("view: rotate around selected node", "shift button1") + h.addBehaviour("view: zoom outward or toward selected node", targetArcball) + h.addKeyBinding("view: zoom outward or toward selected node", "shift scroll") + } + + /* + * A wrapping class for the {@ArcballCameraControl} that calls {@link CenterOnPosition()} + * before the actual Arcball camera movement takes place. This way, the targeted node is + * first smoothly brought into the centre along which Arcball is revolving, preventing + * from sudden changes of view (and lost of focus from the user. + */ + inner class AnimatedCenteringBeforeArcBallControl : ArcballCameraControl { + //a bunch of necessary c'tors (originally defined in the ArcballCameraControl class) + constructor(name: String, n: Function0, w: Int, h: Int, target: Function0) : super(name, n, w, h, target) {} + constructor(name: String,n: Supplier, w: Int, h: Int, target: Supplier) : super(name, n, w, h, target) {} + constructor(name: String, n: Function0, w: Int, h: Int, target: Vector3f) : super(name, n, w, h, target) {} + constructor(name: String, n: Supplier, w: Int, h: Int, target: Vector3f) : super(name, n, w, h, target) {} + + override fun init(x: Int, y: Int) { + centerOnPosition(targetArcball.target.invoke()) + super.init(x, y) + } + + override fun scroll(wheelRotation: Double, isHorizontal: Boolean, x: Int, y: Int) { + centerOnPosition(targetArcball.target.invoke()) + super.scroll(wheelRotation, isHorizontal, x, y) + } + } + + /* + * Enable FPS style controls + */ + private fun enableFPSControl() { + val h = inputHandler + if (h == null) { + logger.error("InputHandler is null, cannot setup fps control.") + return + } + + // Mouse look around (Lclick) and move around (Rclick) + // + //setup FPSCameraControl from scenery, register it with SciView's controlsParameters + val cameraSupplier = Supplier { scene.findObserver() } + fpsControl = FPSCameraControl("view: freely look around", cameraSupplier, renderer!!.window.width, + renderer!!.window.height) + controlsParameters.registerFpsCameraControl(fpsControl) + h.addBehaviour("view: freely look around", fpsControl!!) + h.addKeyBinding("view: freely look around", "button1") + + //slow and fast camera motion + h.addBehaviour("move_withMouse_back/forward/left/right", CameraTranslateControl(this, 1f)) + h.addKeyBinding("move_withMouse_back/forward/left/right", "button3") + // + //fast and very fast camera motion + h.addBehaviour("move_withMouse_back/forward/left/right_fast", CameraTranslateControl(this, 10f)) + h.addKeyBinding("move_withMouse_back/forward/left/right_fast", "shift button3") + + // Keyboard move around (WASD keys) + // + //override 'WASD' from Scenery + var mcW: MovementCommand + var mcA: MovementCommand + var mcS: MovementCommand + var mcD: MovementCommand + mcW = MovementCommand("move_forward", "forward", { scene.findObserver() }, controlsParameters.getFpsSpeedSlow()) + mcS = MovementCommand("move_backward", "back", { scene.findObserver() }, controlsParameters.getFpsSpeedSlow()) + mcA = MovementCommand("move_left", "left", { scene.findObserver() }, controlsParameters.getFpsSpeedSlow()) + mcD = MovementCommand("move_right", "right", { scene.findObserver() }, controlsParameters.getFpsSpeedSlow()) + controlsParameters.registerSlowStepMover(mcW) + controlsParameters.registerSlowStepMover(mcS) + controlsParameters.registerSlowStepMover(mcA) + controlsParameters.registerSlowStepMover(mcD) + h.addBehaviour("move_forward", mcW) + h.addBehaviour("move_back", mcS) + h.addBehaviour("move_left", mcA) + h.addBehaviour("move_right", mcD) + // 'WASD' keys are registered already in scenery + + //override shift+'WASD' from Scenery + mcW = MovementCommand("move_forward_fast", "forward", { scene.findObserver() }, controlsParameters.getFpsSpeedFast()) + mcS = MovementCommand("move_backward_fast", "back", { scene.findObserver() }, controlsParameters.getFpsSpeedFast()) + mcA = MovementCommand("move_left_fast", "left", { scene.findObserver() }, controlsParameters.getFpsSpeedFast()) + mcD = MovementCommand("move_right_fast", "right", { scene.findObserver() }, controlsParameters.getFpsSpeedFast()) + controlsParameters.registerFastStepMover(mcW) + controlsParameters.registerFastStepMover(mcS) + controlsParameters.registerFastStepMover(mcA) + controlsParameters.registerFastStepMover(mcD) + h.addBehaviour("move_forward_fast", mcW) + h.addBehaviour("move_back_fast", mcS) + h.addBehaviour("move_left_fast", mcA) + h.addBehaviour("move_right_fast", mcD) + // shift+'WASD' keys are registered already in scenery + + //define additionally shift+ctrl+'WASD' + mcW = MovementCommand("move_forward_veryfast", "forward", { scene.findObserver() }, controlsParameters.getFpsSpeedVeryFast()) + mcS = MovementCommand("move_back_veryfast", "back", { scene.findObserver() }, controlsParameters.getFpsSpeedVeryFast()) + mcA = MovementCommand("move_left_veryfast", "left", { scene.findObserver() }, controlsParameters.getFpsSpeedVeryFast()) + mcD = MovementCommand("move_right_veryfast", "right", { scene.findObserver() }, controlsParameters.getFpsSpeedVeryFast()) + controlsParameters.registerVeryFastStepMover(mcW) + controlsParameters.registerVeryFastStepMover(mcS) + controlsParameters.registerVeryFastStepMover(mcA) + controlsParameters.registerVeryFastStepMover(mcD) + h.addBehaviour("move_forward_veryfast", mcW) + h.addBehaviour("move_back_veryfast", mcS) + h.addBehaviour("move_left_veryfast", mcA) + h.addBehaviour("move_right_veryfast", mcD) + h.addKeyBinding("move_forward_veryfast", "ctrl shift W") + h.addKeyBinding("move_back_veryfast", "ctrl shift S") + h.addKeyBinding("move_left_veryfast", "ctrl shift A") + h.addKeyBinding("move_right_veryfast", "ctrl shift D") + + // Keyboard only move up/down (XC keys) + // + //[[ctrl]+shift]+'XC' + mcW = MovementCommand("move_up", "up", { scene.findObserver() }, controlsParameters.getFpsSpeedSlow()) + mcS = MovementCommand("move_down", "down", { scene.findObserver() }, controlsParameters.getFpsSpeedSlow()) + controlsParameters.registerSlowStepMover(mcW) + controlsParameters.registerSlowStepMover(mcS) + h.addBehaviour("move_up", mcW) + h.addBehaviour("move_down", mcS) + h.addKeyBinding("move_up", "C") + h.addKeyBinding("move_down", "X") + mcW = MovementCommand("move_up_fast", "up", { scene.findObserver() }, controlsParameters.getFpsSpeedFast()) + mcS = MovementCommand("move_down_fast", "down", { scene.findObserver() }, controlsParameters.getFpsSpeedFast()) + controlsParameters.registerFastStepMover(mcW) + controlsParameters.registerFastStepMover(mcS) + h.addBehaviour("move_up_fast", mcW) + h.addBehaviour("move_down_fast", mcS) + h.addKeyBinding("move_up_fast", "shift C") + h.addKeyBinding("move_down_fast", "shift X") + mcW = MovementCommand("move_up_veryfast", "up", { scene.findObserver() }, controlsParameters.getFpsSpeedVeryFast()) + mcS = MovementCommand("move_down_veryfast", "down", { scene.findObserver() }, controlsParameters.getFpsSpeedVeryFast()) + controlsParameters.registerVeryFastStepMover(mcW) + controlsParameters.registerVeryFastStepMover(mcS) + h.addBehaviour("move_up_veryfast", mcW) + h.addBehaviour("move_down_veryfast", mcS) + h.addKeyBinding("move_up_veryfast", "ctrl shift C") + h.addKeyBinding("move_down_veryfast", "ctrl shift X") + } + + /** + * Add a box at the specified position with specified size, color, and normals on the inside/outside + * @param position position to put the box + * @param size size of the box + * @param color color of the box + * @param inside are normals inside the box? + * @return the Node corresponding to the box + */ + @JvmOverloads + fun addBox(position: Vector3f? = Vector3f(0.0f, 0.0f, 0.0f), size: Vector3f? = Vector3f(1.0f, 1.0f, 1.0f), color: ColorRGB? = DEFAULT_COLOR, + inside: Boolean = false): Node? { + // TODO: use a material from the current palate by default + val boxmaterial = Material() + boxmaterial.ambient = Vector3f(1.0f, 0.0f, 0.0f) + boxmaterial.diffuse = Utils.convertToVector3f(color) + boxmaterial.specular = Vector3f(1.0f, 1.0f, 1.0f) + val box = size?.let { Box(it, inside) } + box?.material = boxmaterial + if (position != null) { + box?.position = position + } + return addNode(box) + } + /** + * Add a sphere at the specified positoin with a given radius and color + * @param position position to put the sphere + * @param radius radius the sphere + * @param color color of the sphere + * @return the Node corresponding to the sphere + */ + /** + * Add a sphere at the specified position with a given radius + * @param position position to put the sphere + * @param radius radius of the sphere + * @return the Node corresponding to the sphere + */ + /** + * Add a unit sphere at the origin + * @return the Node corresponding to the sphere + */ + @JvmOverloads + fun addSphere(position: Vector3f? = Vector3f(0.0f, 0.0f, 0.0f), radius: Float = 1f, color: ColorRGB? = DEFAULT_COLOR): Node? { + val material = Material() + material.ambient = Vector3f(1.0f, 0.0f, 0.0f) + material.diffuse = Utils.convertToVector3f(color) + material.specular = Vector3f(1.0f, 1.0f, 1.0f) + val sphere = Sphere(radius, 20) + sphere.material = material + if (position != null) { + sphere.position = position + } + return addNode(sphere) + } + + /** + * Add a Cylinder at the given position with radius, height, and number of faces/segments + * @param position position of the cylinder + * @param radius radius of the cylinder + * @param height height of the cylinder + * @param num_segments number of segments to represent the cylinder + * @return the Node corresponding to the cylinder + */ + fun addCylinder(position: Vector3f?, radius: Float, height: Float, num_segments: Int): Node? { + val cyl = Cylinder(radius, height, num_segments) + if (position != null) { + cyl.position = position + } + return addNode(cyl) + } + + /** + * Add a Cone at the given position with radius, height, and number of faces/segments + * @param position position to put the cone + * @param radius radius of the cone + * @param height height of the cone + * @param num_segments number of segments used to represent cone + * @return the Node corresponding to the cone + */ + fun addCone(position: Vector3f?, radius: Float, height: Float, num_segments: Int): Node? { + val cone = Cone(radius, height, num_segments, Vector3f(0.0f, 0.0f, 1.0f)) + if (position != null) { + cone.position = position + } + return addNode(cone) + } + + /** + * Add a line from start to stop with the given color + * @param start start position of line + * @param stop stop position of line + * @param color color of line + * @return the Node corresponding to the line + */ + @JvmOverloads + fun addLine(start: Vector3f = Vector3f(0.0f, 0.0f, 0.0f), stop: Vector3f = Vector3f(1.0f, 1.0f, 1.0f), color: ColorRGB? = DEFAULT_COLOR): Node? { + return addLine(arrayOf(start, stop), color, 0.1) + } + + /** + * Add a multi-segment line that goes through the supplied points with a single color and edge width + * @param points points along line including first and terminal points + * @param color color of line + * @param edgeWidth width of line segments + * @return the Node corresponding to the line + */ + fun addLine(points: Array, color: ColorRGB?, edgeWidth: Double): Node? { + val material = Material() + material.ambient = Vector3f(1.0f, 1.0f, 1.0f) + material.diffuse = Utils.convertToVector3f(color) + material.specular = Vector3f(1.0f, 1.0f, 1.0f) + val line = Line(points.size) + for (pt in points) { + line.addPoint(pt) + } + line.edgeWidth = edgeWidth.toFloat() + line.material = material + line.position = points[0] + return addNode(line) + } + + /** + * Add a PointLight source at the origin + * @return a Node corresponding to the PointLight + */ + fun addPointLight(): Node? { + val material = Material() + material.ambient = Vector3f(1.0f, 0.0f, 0.0f) + material.diffuse = Vector3f(0.0f, 1.0f, 0.0f) + material.specular = Vector3f(1.0f, 1.0f, 1.0f) + val light = PointLight(5.0f) + light.material = material + light.position = Vector3f(0.0f, 0.0f, 0.0f) + lights!!.add(light) + return addNode(light) + } + + /** + * Position all lights that were initialized by default around the scene in a circle at Y=0 + */ + fun surroundLighting() { + val bb = getSubgraphBoundingBox(scene, notAbstractBranchingFunction) + val (c, r) = bb!!.getBoundingSphere() + // Choose a good y-position, then place lights around the cross-section through this plane + val y = 0f + for (k in lights!!.indices) { + val light = lights!![k] + val x = (c.x() + r * cos(if (k == 0) 0.0 else Math.PI * 2 * (k.toFloat() / lights!!.size.toFloat()))).toFloat() + val z = (c.y() + r * sin(if (k == 0) 0.0 else Math.PI * 2 * (k.toFloat() / lights!!.size.toFloat()))).toFloat() + light.lightRadius = 2 * r + light.position = Vector3f(x, y, z) + } + } + + /** + * Write a scenery mesh as an stl to the given file + * @param filename filename of the stl + * @param scMesh mesh to save + */ + fun writeSCMesh(filename: String?, scMesh: graphics.scenery.Mesh) { + val f = File(filename) + val out: BufferedOutputStream + try { + out = BufferedOutputStream(FileOutputStream(f)) + out.write("solid STL generated by FIJI\n".toByteArray()) + val normalsFB = scMesh.normals + val verticesFB = scMesh.vertices + while (verticesFB.hasRemaining() && normalsFB.hasRemaining()) { + out.write("""facet normal ${normalsFB.get()} ${normalsFB.get()} ${normalsFB.get()} +""".toByteArray()) + out.write("outer loop\n".toByteArray()) + for (v in 0..2) { + out.write("""vertex ${verticesFB.get()} ${verticesFB.get()} ${verticesFB.get()} +""".toByteArray()) + } + out.write("endloop\n".toByteArray()) + out.write("endfacet\n".toByteArray()) + } + out.write("endsolid vcg\n".toByteArray()) + out.close() + } catch (e: FileNotFoundException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + } + + /** + * Return the default point size to use for point clouds + * @return default point size used for point clouds + */ + private val defaultPointSize: Float + get() = 0.025f + + /** + * Create an array of normal vectors from a set of vertices corresponding to triangles + * + * @param verts vertices to use for computing normals, assumed to be ordered as triangles + * @return array of normals + */ + fun makeNormalsFromVertices(verts: ArrayList): FloatArray { + val normals = FloatArray(verts.size) // div3 * 3coords + var k = 0 + while (k < verts.size) { + val v1 = Vector3f(verts[k].getFloatPosition(0), // + verts[k].getFloatPosition(1), // + verts[k].getFloatPosition(2)) + val v2 = Vector3f(verts[k + 1].getFloatPosition(0), + verts[k + 1].getFloatPosition(1), + verts[k + 1].getFloatPosition(2)) + val v3 = Vector3f(verts[k + 2].getFloatPosition(0), + verts[k + 2].getFloatPosition(1), + verts[k + 2].getFloatPosition(2)) + val a = v2.sub(v1) + val b = v3.sub(v1) + val n = a.cross(b).normalize() + normals[k / 3] = n[0] + normals[k / 3 + 1] = n[1] + normals[k / 3 + 2] = n[2] + k += 3 + } + return normals + } + + /** + * Open a file specified by the source path. The file can be anything that SciView knows about: mesh, volume, point cloud + * @param source string of a data source + * @throws IOException + */ + @Suppress("UNCHECKED_CAST") + @Throws(IOException::class) + fun open(source: String) { + if (source.endsWith(".xml")) { + addNode(fromXML(source, hub, VolumeViewerOptions())) + return + } + val data = io!!.open(source) + if (data is Mesh) + addMesh(data) + else if (data is graphics.scenery.Mesh) + addMesh(data) + else if (data is PointCloud) + addPointCloud(data) + else if (data is Dataset) + addVolume(data) + else if (data is RandomAccessibleInterval<*>) + addVolume(data as RandomAccessibleInterval>, source) + else if (data is List<*>) { + val list = data + require(!list.isEmpty()) { "Data source '$source' appears empty." } + val element = list[0] + if (element is RealLocalizable) { + // NB: For now, we assume all elements will be RealLocalizable. + // Highly likely to be the case, barring antagonistic importers. + val points = list as List + addPointCloud(points, source) + } else { + val type = if (element == null) "" else element.javaClass.name + throw IllegalArgumentException("Data source '" + source + // + "' contains elements of unknown type '" + type + "'") + } + } else { + val type = if (data == null) "" else data.javaClass.name + throw IllegalArgumentException("Data source '" + source + // + "' contains data of unknown type '" + type + "'") + } + } + + /** + * Add the given points to the scene as a PointCloud with a given name + * @param points points to use in a PointCloud + * @param name name of the PointCloud + * @return + */ + @JvmOverloads + fun addPointCloud(points: Collection, + name: String? = "PointCloud"): Node? { + val flatVerts = FloatArray(points.size * 3) + var k = 0 + for (point in points) { + flatVerts[k * 3] = point.getFloatPosition(0) + flatVerts[k * 3 + 1] = point.getFloatPosition(1) + flatVerts[k * 3 + 2] = point.getFloatPosition(2) + k++ + } + val pointCloud = PointCloud(defaultPointSize, name!!) + val material = Material() + val vBuffer: FloatBuffer = BufferUtils.allocateFloat(flatVerts.size * 4) + val nBuffer: FloatBuffer = BufferUtils.allocateFloat(0) + vBuffer.put(flatVerts) + vBuffer.flip() + pointCloud.vertices = vBuffer + pointCloud.normals = nBuffer + pointCloud.indices = BufferUtils.allocateInt(0) + pointCloud.setupPointCloud() + material.ambient = Vector3f(1.0f, 1.0f, 1.0f) + material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) + material.specular = Vector3f(1.0f, 1.0f, 1.0f) + pointCloud.material = material + pointCloud.position = Vector3f(0f, 0f, 0f) + return addNode(pointCloud) + } + + /** + * Add a PointCloud to the scene + * @param pointCloud existing PointCloud to add to scene + * @return a Node corresponding to the PointCloud + */ + fun addPointCloud(pointCloud: PointCloud): Node? { + pointCloud.setupPointCloud() + pointCloud.material.ambient = Vector3f(1.0f, 1.0f, 1.0f) + pointCloud.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) + pointCloud.material.specular = Vector3f(1.0f, 1.0f, 1.0f) + pointCloud.position = Vector3f(0f, 0f, 0f) + return addNode(pointCloud) + } + + /** + * Add Node n to the scene and set it as the active node/publish it to the event service if activePublish is true + * @param n node to add to scene + * @param activePublish flag to specify whether the node becomes active *and* is published in the inspector/services + * @return a Node corresponding to the Node + */ + @JvmOverloads + fun addNode(n: Node?, activePublish: Boolean = true): Node? { + n?.let { + scene.addChild(it) + objectService?.addObject(n) + if (blockOnNewNodes) { + blockWhile({ sciView: SciView -> sciView.find(n.name) == null }, 20) + //System.out.println("find(name) " + find(n.getName()) ); + } + // Set new node as active and centered? + setActiveNode(n); + if (centerOnNewNodes) { + centerOnNode(n) + } + if (activePublish) { + eventService?.publish(NodeAddedEvent(n)); + } + } + return n; + } + + /** + * Add a scenery Mesh to the scene + * @param scMesh scenery mesh to add to scene + * @return a Node corresponding to the mesh + */ + fun addMesh(scMesh: graphics.scenery.Mesh): Node? { + val material = Material() + material.ambient = Vector3f(1.0f, 0.0f, 0.0f) + material.diffuse = Vector3f(0.0f, 1.0f, 0.0f) + material.specular = Vector3f(1.0f, 1.0f, 1.0f) + scMesh.material = material + scMesh.position = Vector3f(0.0f, 0.0f, 0.0f) + objectService?.addObject(scMesh) + return addNode(scMesh) + } + + /** + * Add an ImageJ mesh to the scene + * @param mesh net.imagej.mesh to add to scene + * @return a Node corresponding to the mesh + */ + fun addMesh(mesh: Mesh): Node? { + val scMesh = MeshConverter.toScenery(mesh) + return addMesh(scMesh) + } + + /** + * [Deprecated: use deleteNode] + * Remove a Mesh from the scene + * @param scMesh mesh to remove from scene + */ + fun removeMesh(scMesh: graphics.scenery.Mesh?) { + scene.removeChild(scMesh!!) + } + + /** + * Set the currently active node + * @param n existing node that should become active focus of this SciView + * @return the currently active node + */ + fun setActiveNode(n: Node?): Node? { + if (activeNode === n) return activeNode + activeNode = n + targetArcball.target = { n?.getMaximumBoundingBox()?.getBoundingSphere()?.origin ?: Vector3f(0.0f, 0.0f, 0.0f) } + nodePropertyEditor?.trySelectNode(activeNode); + eventService!!.publish(NodeActivatedEvent(activeNode)) + return activeNode + } + + @Suppress("UNUSED_PARAMETER") + @EventHandler + protected fun onNodeAdded(event: NodeAddedEvent?) { + nodePropertyEditor!!.rebuildTree() + } + + @Suppress("UNUSED_PARAMETER") + @EventHandler + protected fun onNodeRemoved(event: NodeRemovedEvent?) { + nodePropertyEditor!!.rebuildTree() + } + + @Suppress("UNUSED_PARAMETER") + @EventHandler + protected fun onNodeChanged(event: NodeChangedEvent?) { + nodePropertyEditor!!.rebuildTree() + } + + @Suppress("UNUSED_PARAMETER") + @EventHandler + protected fun onNodeActivated(event: NodeActivatedEvent?) { + // TODO: add listener code for node activation, if necessary + // NOTE: do not update property window here, this will lead to a loop. + } + + fun toggleInspectorWindow() { + toggleSidebar() + } + + @Suppress("UNUSED_PARAMETER") + fun setInspectorWindowVisibility(visible: Boolean) { +// inspector.setVisible(visible); +// if( visible ) +// mainSplitPane.setDividerLocation(getWindowWidth()/4 * 3); +// else +// mainSplitPane.setDividerLocation(getWindowWidth()); + } + + @Suppress("UNUSED_PARAMETER") + fun setInterpreterWindowVisibility(visible: Boolean) { +// interpreterPane.getComponent().setVisible(visible); +// if( visible ) +// interpreterSplitPane.setDividerLocation(getWindowHeight()/10 * 6); +// else +// interpreterSplitPane.setDividerLocation(getWindowHeight()); + } + + /** + * Create an animation thread with the given fps speed and the specified action + * @param fps frames per second at which this action should be run + * @param action Runnable that contains code to run fps times per second + * @return a Future corresponding to the thread + */ + @Synchronized + fun animate(fps: Int, action: Runnable): Future<*> { + // TODO: Make animation speed less laggy and more accurate. + val delay = 1000 / fps + val thread = threadService!!.run { + while (animating) { + action.run() + try { + Thread.sleep(delay.toLong()) + } catch (e: InterruptedException) { + break + } + } + } + animations!!.add(thread) + animating = true + return thread + } + + /** + * Stop all animations + */ + @Synchronized + fun stopAnimation() { + animating = false + while (!animations!!.isEmpty()) { + animations!!.peek().cancel(true) + animations!!.remove() + } + } + + /** + * Take a screenshot and save it to the default scenery location + */ + fun takeScreenshot() { + renderer!!.screenshot() + } + + /** + * Take a screenshot and save it to the specified path + * @param path path for saving the screenshot + */ + fun takeScreenshot(path: String?) { + renderer!!.screenshot(path!!, false) + } + + /** + * Take a screenshot and return it as an Img + * @return an Img of type UnsignedByteType + */ + val screenshot: Img? + get() { + val screenshot = getSceneryRenderer()!!.requestScreenshot() + val image = BufferedImage(screenshot.width, screenshot.height, BufferedImage.TYPE_4BYTE_ABGR) + val imgData = (image.raster.dataBuffer as DataBufferByte).data + System.arraycopy(screenshot.data, 0, imgData, 0, screenshot.data!!.size) + var img: Img? = null + try { + val tmpFile = File.createTempFile("sciview-", "-tmp.png") + ImageIO.write(image, "png", tmpFile) + @Suppress("UNCHECKED_CAST") + img = io!!.open(tmpFile.absolutePath) as Img + tmpFile.delete() + } catch (e: IOException) { + e.printStackTrace() + } + return img + } + + /** + * Take a screenshot and return it as an Img + * @return an Img of type UnsignedByteType + */ + val aRGBScreenshot: Img + get() { + val screenshot = screenshot + return Utils.convertToARGB(screenshot) + } + + /** + * @param name The name of the node to find. + * @return the node object or null, if the node has not been found. + */ + fun find(name: String): Node? { + val n = scene.find(name) + if (n == null) { + logger.warn("Node with name $name not found.") + } + return n + } + + /** + * @return an array of all nodes in the scene except Cameras and PointLights + */ + val sceneNodes: Array + get() = getSceneNodes { n: Node? -> n !is Camera && n !is PointLight } + + /** + * Get a list of nodes filtered by filter predicate + * @param filter, a predicate that filters the candidate nodes + * @return all nodes that match the predicate + */ + fun getSceneNodes(filter: Predicate): Array { + return scene.children.filter{ filter.test(it) }.toTypedArray() + } + + /** + * @return an array of all Node's in the scene + */ + val allSceneNodes: Array + get() = getSceneNodes { _: Node? -> true } + + /** + * Delete the current active node + */ + fun deleteActiveNode() { + deleteNode(activeNode) + } + /** + * Delete a specified node and control whether the event is published + * @param node node to delete from scene + * @param activePublish whether the deletion should be published + */ + /** + * Delete the specified node, this event is published + * @param node node to delete from scene + */ + @JvmOverloads + fun deleteNode(node: Node?, activePublish: Boolean = true) { + for (child in node!!.children) { + deleteNode(child, activePublish) + } + objectService?.removeObject(node); + node.parent?.removeChild(node); + if (activeNode == node) { + setActiveNode(null) + } + //maintain consistency + if( activePublish ) { + eventService?.publish(NodeRemovedEvent(node)) + }; + } + + /** + * Dispose the current scenery renderer, hub, and other scenery things + */ + fun dispose() { + val objs: List = objectService!!.getObjects(Node::class.java) + for (obj in objs) { + objectService.removeObject(obj) + } + scijavaContext!!.service(SciViewService::class.java).close(this) + close() + } + + override fun close() { + super.close() + frame!!.dispose() + } + + /** + * Move the current active camera to the specified position + * @param position position to move the camera to + */ + fun moveCamera(position: FloatArray) { + camera!!.position = Vector3f(position[0], position[1], position[2]) + } + + /** + * Move the current active camera to the specified position + * @param position position to move the camera to + */ + fun moveCamera(position: DoubleArray) { + camera!!.position = Vector3f(position[0].toFloat(), position[1].toFloat(), position[2].toFloat()) + } + + /** + * Get the current application name + * @return a String of the application name + */ + fun getName(): String { + return applicationName + } + + /** + * Add a child to the scene. you probably want addNode + * @param node node to add as a child to the scene + */ + fun addChild(node: Node?) { + scene.addChild(node!!) + } + + /** + * Add a Dataset to the scene as a volume. Voxel resolution and name are extracted from the Dataset itself + * @param image image to add as a volume + * @return a Node corresponding to the Volume + */ + fun addVolume(image: Dataset): Node? { + val voxelDims = FloatArray(image.numDimensions()) + for (d in voxelDims.indices) { + val inValue = image.axis(d).averageScale(0.0, 1.0) + if (image.axis(d).unit() == null) voxelDims[d] = inValue.toFloat() else voxelDims[d] = unitService!!.value(inValue, image.axis(d).unit(), axis(d)!!.unit()).toFloat() + } + return addVolume(image, voxelDims) + } + + /** + * Add a Dataset as a Volume with the specified voxel dimensions + * @param image image to add as a volume + * @param voxelDimensions dimensions of voxels in volume + * @return a Node corresponding to the Volume + */ + @Suppress("UNCHECKED_CAST") + fun addVolume(image: Dataset, voxelDimensions: FloatArray): Node? { + return addVolume>(image.imgPlus as RandomAccessibleInterval>, image.name, + *voxelDimensions) + } + +// /** +// * Add a RandomAccessibleInterval to the image +// * @param image image to add as a volume +// * @param name name of image +// * @param extra, kludge argument to prevent matching issues +// * @param pixel type of image +// * @return a Node corresponding to the volume +// */ +// fun ?> addVolume(image: RandomAccessibleInterval, name: String?, extra: String?): Node { +// return addVolume(image, name, 1f, 1f, 1f) +// } + + /** + * Add a RandomAccessibleInterval to the image + * @param image image to add as a volume + * @param pixel type of image + * @return a Node corresponding to the volume + */ + fun > addVolume(image: RandomAccessibleInterval, name: String?): Node? { + return addVolume(image, name, 1f, 1f, 1f) + } + + /** + * Add a RandomAccessibleInterval to the image + * @param image image to add as a volume + * @param pixel type of image + * @return a Node corresponding to the volume + */ + fun > addVolume(image: RandomAccessibleInterval, voxelDimensions: FloatArray): Node? { + return addVolume(image, "volume", *voxelDimensions) + } + + /** + * Add an IterableInterval as a Volume + * @param image + * @param + * @return a Node corresponding to the Volume + */ + @Suppress("UNCHECKED_CAST") + @Throws(Exception::class) + fun ?> addVolume(image: IterableInterval): Node? { + return if (image is RandomAccessibleInterval<*>) { + addVolume(image as RandomAccessibleInterval>, "Volume") + } else { + throw Exception("Unsupported Volume type:$image") + } + } + + /** + * Add an IterableInterval as a Volume + * @param image image to add as a volume + * @param name name of image + * @param pixel type of image + * @return a Node corresponding to the Volume + */ + @Suppress("UNCHECKED_CAST") + @Throws(Exception::class) + fun ?> addVolume(image: IterableInterval, name: String?): Node? { + return if (image is RandomAccessibleInterval<*>) { + addVolume(image as RandomAccessibleInterval>, name, 1f, 1f, 1f) + } else { + throw Exception("Unsupported Volume type:$image") + } + } + + /** + * Set the colormap using an ImageJ LUT name + * @param n node to apply colormap to + * @param lutName name of LUT according to imagej LUTService + */ + fun setColormap(n: Node, lutName: String?) { + try { + setColormap(n, lutService!!.loadLUT(lutService.findLUTs()[lutName])) + } catch (e: IOException) { + e.printStackTrace() + } + } + + /** + * Set the ColorMap of node n to the supplied colorTable + * @param n node to apply colortable to + * @param colorTable ColorTable to use + */ + fun setColormap(n: Node, colorTable: ColorTable) { + val copies = 16 + val byteBuffer = ByteBuffer.allocateDirect( + 4 * colorTable.length * copies) // Num bytes * num components * color map length * height of color map texture + val tmp = ByteArray(4 * colorTable.length) + for (k in 0 until colorTable.length) { + for (c in 0 until colorTable.componentCount) { + // TODO this assumes numBits is 8, could be 16 + tmp[4 * k + c] = colorTable[c, k].toByte() + } + if (colorTable.componentCount == 3) { + tmp[4 * k + 3] = 255.toByte() + } + } + for (i in 0 until copies) { + byteBuffer.put(tmp) + } + byteBuffer.flip() + n.metadata["sciviewColormap"] = colorTable + if (n is Volume) { + n.colormap = Colormap.fromColorTable(colorTable) + n.dirty = true + n.needsUpdate = true + } + } + + /** + * Adss a SourceAndConverter to the scene. + * + * @param sac The SourceAndConverter to add + * @param name Name of the dataset + * @param voxelDimensions Array with voxel dimensions. + * @param Type of the dataset. + * @return THe node corresponding to the volume just added. + */ + fun > addVolume(sac: SourceAndConverter, + numTimepoints: Int, + name: String?, + vararg voxelDimensions: Float): Node? { + val sources: MutableList> = ArrayList() + sources.add(sac) + return addVolume(sources, numTimepoints, name, *voxelDimensions) + } + + /** + * Add an IterableInterval to the image with the specified voxelDimensions and name + * This version of addVolume does most of the work + * @param image image to add as a volume + * @param name name of image + * @param voxelDimensions dimensions of voxel in volume + * @param pixel type of image + * @return a Node corresponding to the Volume + */ + fun > addVolume(image: RandomAccessibleInterval, name: String?, + vararg voxelDimensions: Float): Node? { + //log.debug( "Add Volume " + name + " image: " + image ); + val dimensions = LongArray(image.numDimensions()) + image.dimensions(dimensions) + val minPt = LongArray(image.numDimensions()) + + // Get type at min point + val imageRA = image.randomAccess() + image.min(minPt) + imageRA.setPosition(minPt) + val voxelType = imageRA.get()!!.createVariable() + val converterSetups: ArrayList = ArrayList() + val stacks = AxisOrder.splitInputStackIntoSourceStacks(image, AxisOrder.getAxisOrder(AxisOrder.DEFAULT, image, false)) + val sourceTransform = AffineTransform3D() + val sources: ArrayList> = ArrayList>() + var numTimepoints = 1 + for (stack in stacks) { + var s: Source + if (stack.numDimensions() > 3) { + numTimepoints = (stack.max(3) + 1).toInt() + s = RandomAccessibleIntervalSource4D(stack, voxelType, sourceTransform, name) + } else { + s = RandomAccessibleIntervalSource(stack, voxelType, sourceTransform, name) + } + val source = BigDataViewer.wrapWithTransformedSource( + SourceAndConverter(s, BigDataViewer.createConverterToARGB(voxelType))) + converterSetups.add(BigDataViewer.createConverterSetup(source, setupId.getAndIncrement())) + sources.add(source) + } + val v = addVolume(sources, numTimepoints, name, *voxelDimensions) + v?.metadata?.set("RandomAccessibleInterval", image) + return v + } + + /** + * Adds a SourceAndConverter to the scene. + * + * This method actually instantiates the volume. + * + * @param sources The list of SourceAndConverter to add + * @param name Name of the dataset + * @param voxelDimensions Array with voxel dimensions. + * @param Type of the dataset. + * @return THe node corresponding to the volume just added. + */ + @Suppress("UNCHECKED_CAST") + fun > addVolume(sources: List>, + converterSetups: ArrayList?, + numTimepoints: Int, + name: String?, + vararg voxelDimensions: Float): Node? { + var timepoints = numTimepoints + var cacheControl: CacheControl? = null + +// RandomAccessibleInterval image = +// ((RandomAccessibleIntervalSource4D) sources.get(0).getSpimSource()). +// .getSource(0, 0); + val image = sources[0].spimSource.getSource(0, 0) + if (image is VolatileView<*, *>) { + val viewData = (image as VolatileView?>).volatileViewData + cacheControl = viewData.cacheControl + } + val dimensions = LongArray(image.numDimensions()) + image.dimensions(dimensions) + val minPt = LongArray(image.numDimensions()) + + // Get type at min point + val imageRA = image.randomAccess() + image.min(minPt) + imageRA.setPosition(minPt) + val voxelType = imageRA.get()!!.createVariable() as T + println("addVolume " + image.numDimensions() + " interval " + image as Interval) + + //int numTimepoints = 1; + if (image.numDimensions() > 3) { + timepoints = image.dimension(3).toInt() + } + val ds = RAISource(voxelType, sources, converterSetups!!, timepoints, cacheControl) + val options = VolumeViewerOptions() + val v: Volume = RAIVolume(ds, options, hub) + v.name = name!! + v.metadata["sources"] = sources + v.metadata["VoxelDimensions"] = voxelDimensions + val tf = v.transferFunction + val rampMin = 0f + val rampMax = 0.1f + tf.clear() + tf.addControlPoint(0.0f, 0.0f) + tf.addControlPoint(rampMin, 0.0f) + tf.addControlPoint(1.0f, rampMax) + val bg = BoundingGrid() + bg.node = v + return addNode(v) + } + + /** + * Block while predicate is true + * + * @param predicate predicate function that returns true as long as this function should block + * @param waitTime wait time before predicate re-evaluation + */ + private fun blockWhile(predicate: Function, waitTime: Int) { + while (predicate.apply(this)) { + try { + Thread.sleep(waitTime.toLong()) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + /** + * Adds a SourceAndConverter to the scene. + * + * @param sources The list of SourceAndConverter to add + * @param name Name of the dataset + * @param voxelDimensions Array with voxel dimensions. + * @param Type of the dataset. + * @return THe node corresponding to the volume just added. + */ + fun > addVolume(sources: List>, + numTimepoints: Int, + name: String?, + vararg voxelDimensions: Float): Node? { + var setupId = 0 + val converterSetups = ArrayList() + for (source in sources) { + converterSetups.add(BigDataViewer.createConverterSetup(source, setupId++)) + } + return addVolume(sources, converterSetups, numTimepoints, name, *voxelDimensions) + } + + /** + * Update a volume with the given IterableInterval. + * This method actually populates the volume + * @param image image to update into volume + * @param name name of image + * @param voxelDimensions dimensions of voxel in volume + * @param v existing volume to update + * @param pixel type of image + * @return a Node corresponding to the input volume + */ + @Suppress("UNCHECKED_CAST") + fun > updateVolume(image: IterableInterval, name: String, + voxelDimensions: FloatArray, v: Volume): Node { + val sacs = v.metadata["sources"] as List>? + val source = sacs!![0].spimSource.getSource(0, 0) // hard coded to timepoint and mipmap 0 + val sCur = Views.iterable(source).cursor() + val iCur = image.cursor() + while (sCur.hasNext()) { + sCur.fwd() + iCur.fwd() + sCur.get()!!.set(iCur.get()) + } + v.name = name + v.metadata["VoxelDimensions"] = voxelDimensions + v.volumeManager.notifyUpdate(v) + v.volumeManager.requestRepaint() + //v.getCacheControls().clear(); + //v.setDirty( true ); + v.needsUpdate = true + //v.setNeedsUpdateWorld( true ); + return v + } + + /** + * + * @return whether PushMode is currently active + */ + fun getPushMode(): Boolean { + return renderer!!.pushMode + } + + /** + * Set the status of PushMode, which only updates the render panel when there is a change in the scene + * @param push true if push mode should be used + * @return current PushMode status + */ + fun setPushMode(push: Boolean): Boolean { + renderer!!.pushMode = push + return renderer!!.pushMode + } + + protected fun finalize() { + stopAnimation() + } + + fun getScenerySettings(): Settings { + return settings + } + + fun getSceneryStatistics(): Statistics { + return stats + } + + fun getSceneryRenderer(): Renderer? { + return renderer + } + + /** + * Enable VR rendering + */ + fun toggleVRRendering() { + vrActive = !vrActive + val cam = scene.activeObserver as? DetachedHeadCamera ?: return + var ti: TrackerInput? = null + var hmdAdded = false + if (!hub.has(SceneryElement.HMDInput)) { + try { + val hmd = OpenVRHMD(false, true) + if (hmd.initializedAndWorking()) { + hub.add(SceneryElement.HMDInput, hmd) + ti = hmd + } else { + logger.warn("Could not initialise VR headset, just activating stereo rendering.") + } + hmdAdded = true + } catch (e: Exception) { + logger.error("Could not add OpenVRHMD: $e") + } + } else { + ti = hub.getWorkingHMD() + } + if (vrActive && ti != null) { + cam.tracker = ti + } else { + cam.tracker = null + } + renderer!!.pushMode = false + // we need to force reloading the renderer as the HMD might require device or instance extensions + if (renderer is VulkanRenderer && hmdAdded) { + replaceRenderer((renderer as VulkanRenderer).javaClass.simpleName, true, true) + (renderer as VulkanRenderer).toggleVR() + while (!(renderer as VulkanRenderer).initialized /* || !getRenderer().getFirstImageReady()*/) { + logger.debug("Waiting for renderer reinitialisation") + try { + Thread.sleep(200) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } else { + renderer!!.toggleVR() + } + } + + /** + * Set the rotation of Node N by generating a quaternion from the supplied arguments + * @param n node to set rotation for + * @param x x coord of rotation quat + * @param y y coord of rotation quat + * @param z z coord of rotation quat + * @param w w coord of rotation quat + */ + fun setRotation(n: Node, x: Float, y: Float, z: Float, w: Float) { + n.rotation = Quaternionf(x, y, z, w) + } + + fun setScale(n: Node, x: Float, y: Float, z: Float) { + n.scale = Vector3f(x, y, z) + } + + @Suppress("UNUSED_PARAMETER") + fun setColor(n: Node, x: Float, y: Float, z: Float, w: Float) { + val col = Vector3f(x, y, z) + n.material.ambient = col + n.material.diffuse = col + n.material.specular = col + } + + fun setPosition(n: Node, x: Float, y: Float, z: Float) { + n.position = Vector3f(x, y, z) + } + + fun addWindowListener(wl: WindowListener?) { + frame!!.addWindowListener(wl) + } + + override fun axis(i: Int): CalibratedAxis? { + return axes[i] + } + + override fun axes(calibratedAxes: Array) { + axes = calibratedAxes + } + + override fun setAxis(calibratedAxis: CalibratedAxis, i: Int) { + axes[i] = calibratedAxis + } + + override fun realMin(i: Int): Double { + return Double.NEGATIVE_INFINITY + } + + override fun realMin(doubles: DoubleArray) { + for (i in doubles.indices) { + doubles[i] = Double.NEGATIVE_INFINITY + } + } + + override fun realMin(realPositionable: RealPositionable) { + for (i in 0 until realPositionable.numDimensions()) { + realPositionable.move(Double.NEGATIVE_INFINITY, i) + } + } + + override fun realMax(i: Int): Double { + return Double.POSITIVE_INFINITY + } + + override fun realMax(doubles: DoubleArray) { + for (i in doubles.indices) { + doubles[i] = Double.POSITIVE_INFINITY + } + } + + override fun realMax(realPositionable: RealPositionable) { + for (i in 0 until realPositionable.numDimensions()) { + realPositionable.move(Double.POSITIVE_INFINITY, i) + } + } + + override fun numDimensions(): Int { + return axes.size + } + + fun setActiveObserver(screenshotCam: Camera?) { + scene.activeObserver = screenshotCam + } + + fun getActiveObserver(): Camera? { + return scene.activeObserver + } + + inner class TransparentSlider : JSlider() { + override fun paintComponent(g: Graphics) { + val g2d = g.create() as Graphics2D + g2d.color = background + g2d.composite = AlphaComposite.SrcOver.derive(0.9f) + g2d.fillRect(0, 0, width, height) + g2d.dispose() + super.paintComponent(g) + } + + init { + // Important, we taking over the filling of the + // component... + isOpaque = false + background = Color.DARK_GRAY + foreground = Color.LIGHT_GRAY + } + } + + internal inner class showHelpDisplay : ClickBehaviour { + override fun click(x: Int, y: Int) { + scijavaContext?.getService(CommandService::class.java)?.run(Help::class.java, true) + } + } + + /** + * Return a list of all nodes that match a given predicate function + * @param nodeMatchPredicate, returns true if a node is a match + * @return list of nodes that match the predicate + */ + fun findNodes(nodeMatchPredicate: Function1): List { + return scene.discover(scene, nodeMatchPredicate, false) + } + + /* + * Convenience function for getting a string of info about a Node + */ + fun nodeInfoString(n: Node): String { + return "Node name: " + n.name + " Node type: " + n.nodeType + " To String: " + n + } + + companion object { + //bounds for the controls + @JvmField + val FPSSPEED_MINBOUND_SLOW = 0.01f + @JvmField + val FPSSPEED_MAXBOUND_SLOW = 30.0f + @JvmField + val FPSSPEED_MINBOUND_FAST = 0.2f + @JvmField + val FPSSPEED_MAXBOUND_FAST = 600f + @JvmField + val FPSSPEED_MINBOUND_VERYFAST = 10f + @JvmField + val FPSSPEED_MAXBOUND_VERYFAST = 2000f + + @JvmField + val MOUSESPEED_MINBOUND = 0.1f + @JvmField + val MOUSESPEED_MAXBOUND = 3.0f + @JvmField + val MOUSESCROLL_MINBOUND = 0.3f + @JvmField + val MOUSESCROLL_MAXBOUND = 10.0f + + @JvmField + val DEFAULT_COLOR = Colors.LIGHTGRAY + + /** + * Utility function to generate GLVector in cases like usage from Python + * @param x x coord + * @param y y coord + * @param z z coord + * @return a GLVector of x,y,z + */ + fun getGLVector(x: Float, y: Float, z: Float): GLVector { + return GLVector(x, y, z) + } + + /** + * Static launching method + * + * @return a newly created SciView + */ + @JvmStatic + @Throws(Exception::class) + fun create(): SciView { + xinitThreads() + FlatLightLaf.install() + try { + UIManager.setLookAndFeel(FlatLightLaf()) + } catch (ex: Exception) { + System.err.println("Failed to initialize Flat Light LaF, falling back to Swing default.") + } + System.setProperty("scijava.log.level:sc.iview", "debug") + val context = Context(ImageJService::class.java, SciJavaService::class.java, SCIFIOService::class.java, ThreadService::class.java) + val sciViewService = context.service(SciViewService::class.java) + return sciViewService.orCreateActiveSciView + } + + /** + * Static launching method + * DEPRECATED use SciView.create() instead + * + * @return a newly created SciView + */ + @Deprecated("") + @Throws(Exception::class) + fun createSciView(): SciView { + return create() + } + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/LaunchViewer.java b/src/main/java/sc/iview/commands/LaunchViewer.java deleted file mode 100644 index bbbab351..00000000 --- a/src/main/java/sc/iview/commands/LaunchViewer.java +++ /dev/null @@ -1,80 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.commands; - -import org.scijava.ItemIO; -import org.scijava.command.Command; -import org.scijava.display.DisplayService; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import org.scijava.ui.UIService; - -import sc.iview.SciView; -import sc.iview.SciViewService; -import sc.iview.display.SciViewDisplay; - -/** - * Command to launch SciView - * - * @author Kyle Harrington - * - */ -@Plugin(type = Command.class, menuPath = "Plugins>SciView") -public class LaunchViewer implements Command { - - @Parameter - private DisplayService displayService; - - @Parameter - private SciViewService sciViewService; - - @Parameter(required = false) - private UIService uiService; - - @Parameter(type = ItemIO.OUTPUT) - private SciView sciView = null; - - @Override - public void run() { - final SciViewDisplay display = displayService.getActiveDisplay(SciViewDisplay.class); - try { - if (display == null) { - sciView = sciViewService.getOrCreateActiveSciView(); - } - else - sciViewService.createSciView(); - } catch (Exception e) { - e.printStackTrace(); - } - -// else if (uiService != null) -// uiService.showDialog( "The SciView window is already open. For now, only one SciView window is supported.", "SciView" ); - } - -} diff --git a/src/main/java/sc/iview/commands/LaunchViewer.kt b/src/main/java/sc/iview/commands/LaunchViewer.kt new file mode 100644 index 00000000..142f3ff4 --- /dev/null +++ b/src/main/java/sc/iview/commands/LaunchViewer.kt @@ -0,0 +1,67 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands + +import org.scijava.ItemIO +import org.scijava.command.Command +import org.scijava.display.DisplayService +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import org.scijava.ui.UIService +import sc.iview.SciView +import sc.iview.SciViewService +import sc.iview.display.SciViewDisplay + +/** + * Command to launch SciView + * + * @author Kyle Harrington + */ +@Plugin(type = Command::class, menuPath = "Plugins>SciView") +class LaunchViewer : Command { + @Parameter + private lateinit var displayService: DisplayService + + @Parameter + private lateinit var sciViewService: SciViewService + + @Parameter(type = ItemIO.OUTPUT) + private var sciView: SciView? = null + override fun run() { + val display = displayService.getActiveDisplay(SciViewDisplay::class.java) + try { + if (display == null) { + sciView = sciViewService.orCreateActiveSciView + } else sciViewService.createSciView() + } catch (e: Exception) { + e.printStackTrace() + } + } + +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/MenuWeights.java b/src/main/java/sc/iview/commands/MenuWeights.java deleted file mode 100644 index e737709b..00000000 --- a/src/main/java/sc/iview/commands/MenuWeights.java +++ /dev/null @@ -1,99 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.commands; - -/** - * Constants for coherent menu ordering. - * - * @author Curtis Rueden - * @author Kyle Harrington - */ -public final class MenuWeights { - private MenuWeights() { - // NB: Prevent instantiation of utility class. - } - - public static final double FILE = 0; - public static final double EDIT = 1; - public static final double PROCESS = 2; - public static final double VIEW = 3; - public static final double DEMO = 4; - public static final double HELP = 4; - - public static final double FILE_OPEN = 0; - public static final double FILE_EXPORT_STL = 100; - - public static final double EDIT_ADD_BOX = 0; - public static final double EDIT_ADD_SPHERE = 1; - public static final double EDIT_ADD_LINE = 2; - public static final double EDIT_ADD_POINT_LIGHT = 3; - public static final double EDIT_ADD_LABEL_IMAGE = 4; - public static final double EDIT_ADD_VOLUME = 5; - public static final double EDIT_ADD_CAMERA = 6; - public static final double EDIT_ADD_COMPASS = 49; - public static final double EDIT_TOGGLE_FLOOR = 50; - public static final double EDIT_DELETE_OBJECT = 100; - public static final double EDIT_SCIVIEW_SETTINGS = 200; - - public static final double PROCESS_ISOSURFACE = 0; - public static final double PROCESS_CONVEX_HULL = 1; - public static final double PROCESS_MESH_TO_IMAGE = 2; - public static final double PROCESS_INTERACTIVE_CONVEX_MESH = 3; - public static final double PROCESS_DRAW_LINES = 4; - - public static final double VIEW_ROTATE = 0; - public static final double VIEW_STOP_ANIMATION = 1; - public static final double VIEW_TOGGLE_UNLIMITED_FRAMERATE = 2; - public static final double VIEW_SET_SUPERSAMPLING_FACTOR = 3; - public static final double VIEW_SET_FAR_PLANE = 4; - public static final double VIEW_START_RECORDING_VIDEO = 98; - public static final double VIEW_STOP_RECORDING_VIDEO = 99; - public static final double VIEW_SCREENSHOT = 100; - public static final double VIEW_SET_LUT = 101; - public static final double VIEW_TOGGLE_BOUNDING_GRID = 102; - public static final double VIEW_CENTER_ON_ACTIVE_NODE = 103; - public static final double VIEW_RESET_CAMERA_ROTATION = 202; - public static final double VIEW_RESET_CAMERA_POSITION = 203; - public static final double VIEW_SAVE_CAMERA_CONFIGURATION = 204; - public static final double VIEW_TOGGLE_INSPECTOR = 302; - public static final double VIEW_RENDER_TO_OPENVR = 303; - public static final double VIEW_SET_TRANSFER_FUNCTION = 400; - - - public static final double DEMO_LINES = 0; - public static final double DEMO_MESH = 1; - public static final double DEMO_MESH_TEXTURE = 2; - public static final double DEMO_VOLUME_RENDER = 3; - public static final double DEMO_GAME_OF_LIFE = 4; - public static final double DEMO_TEXT = 5; - public static final double DEMO_EMBRYO = 6; - - public static final double HELP_HELP = 0; - public static final double HELP_ABOUT = 200; -} diff --git a/src/main/java/sc/iview/commands/MenuWeights.kt b/src/main/java/sc/iview/commands/MenuWeights.kt new file mode 100644 index 00000000..bcbfd18b --- /dev/null +++ b/src/main/java/sc/iview/commands/MenuWeights.kt @@ -0,0 +1,88 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands + +/** + * Constants for coherent menu ordering. + * + * @author Curtis Rueden + * @author Kyle Harrington + */ +object MenuWeights { + const val FILE = 0.0 + const val EDIT = 1.0 + const val PROCESS = 2.0 + const val VIEW = 3.0 + const val DEMO = 4.0 + const val HELP = 4.0 + const val FILE_OPEN = 0.0 + const val FILE_EXPORT_STL = 100.0 + const val EDIT_ADD_BOX = 0.0 + const val EDIT_ADD_SPHERE = 1.0 + const val EDIT_ADD_LINE = 2.0 + const val EDIT_ADD_POINT_LIGHT = 3.0 + const val EDIT_ADD_LABEL_IMAGE = 4.0 + const val EDIT_ADD_VOLUME = 5.0 + const val EDIT_ADD_CAMERA = 6.0 + const val EDIT_ADD_COMPASS = 49.0 + const val EDIT_TOGGLE_FLOOR = 50.0 + const val EDIT_DELETE_OBJECT = 100.0 + const val EDIT_SCIVIEW_SETTINGS = 200.0 + const val PROCESS_ISOSURFACE = 0.0 + const val PROCESS_CONVEX_HULL = 1.0 + const val PROCESS_MESH_TO_IMAGE = 2.0 + const val PROCESS_INTERACTIVE_CONVEX_MESH = 3.0 + const val PROCESS_DRAW_LINES = 4.0 + const val VIEW_ROTATE = 0.0 + const val VIEW_STOP_ANIMATION = 1.0 + const val VIEW_TOGGLE_UNLIMITED_FRAMERATE = 2.0 + const val VIEW_SET_SUPERSAMPLING_FACTOR = 3.0 + const val VIEW_SET_FAR_PLANE = 4.0 + const val VIEW_START_RECORDING_VIDEO = 98.0 + const val VIEW_STOP_RECORDING_VIDEO = 99.0 + const val VIEW_SCREENSHOT = 100.0 + const val VIEW_SET_LUT = 101.0 + const val VIEW_TOGGLE_BOUNDING_GRID = 102.0 + const val VIEW_CENTER_ON_ACTIVE_NODE = 103.0 + const val VIEW_RESET_CAMERA_ROTATION = 202.0 + const val VIEW_RESET_CAMERA_POSITION = 203.0 + const val VIEW_SAVE_CAMERA_CONFIGURATION = 204.0 + const val VIEW_TOGGLE_INSPECTOR = 302.0 + const val VIEW_RENDER_TO_OPENVR = 303.0 + const val VIEW_SET_TRANSFER_FUNCTION = 400.0 + const val DEMO_LINES = 0.0 + const val DEMO_MESH = 1.0 + const val DEMO_MESH_TEXTURE = 2.0 + const val DEMO_VOLUME_RENDER = 3.0 + const val DEMO_GAME_OF_LIFE = 4.0 + const val DEMO_TEXT = 5.0 + const val DEMO_EMBRYO = 6.0 + const val HELP_HELP = 0.0 + const val HELP_ABOUT = 200.0 +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/demo/GameOfLife3D.java b/src/main/java/sc/iview/commands/demo/GameOfLife3D.java deleted file mode 100644 index 83589e7e..00000000 --- a/src/main/java/sc/iview/commands/demo/GameOfLife3D.java +++ /dev/null @@ -1,380 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package sc.iview.commands.demo; - -import bdv.BigDataViewer; -import bdv.util.RandomAccessibleIntervalSource; -import bdv.viewer.SourceAndConverter; -import cleargl.GLVector; -import graphics.scenery.BoundingGrid; -import graphics.scenery.volumes.Volume; -import ij.gui.GenericDialog; -import ij.gui.NonBlockingGenericDialog; -import net.imglib2.Cursor; -import net.imglib2.RandomAccess; -import net.imglib2.RandomAccessibleInterval; -import net.imglib2.Sampler; -import net.imglib2.img.Img; -import net.imglib2.img.array.ArrayImgs; -import net.imglib2.type.numeric.integer.UnsignedByteType; -import org.joml.Vector3f; -import org.scijava.command.Command; -import org.scijava.command.CommandService; -import org.scijava.command.InteractiveCommand; -import org.scijava.event.EventHandler; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import org.scijava.widget.Button; -import org.scijava.widget.NumberWidget; -import sc.iview.SciView; -import sc.iview.event.NodeRemovedEvent; - -import javax.swing.*; - -import java.util.HashMap; - -import static sc.iview.commands.MenuWeights.DEMO; -import static sc.iview.commands.MenuWeights.DEMO_GAME_OF_LIFE; - -/** - * Conway's Game of Life—in 3D! - * - * @author Curtis Rueden - * @author Kyle Harrington - */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = { @Menu(label = "Demo", weight = DEMO), // - @Menu(label = "Game of Life 3D", weight = DEMO_GAME_OF_LIFE) }) -public class GameOfLife3D implements Command { - - private static final int ALIVE = 255; - private static final int DEAD = 16; - - private static final String SIX = "6-connected"; - private static final String EIGHTEEN = "18-connected"; - private static final String TWENTY_SIX = "26-connected"; - - @Parameter - private SciView sciView; - - @Parameter(label = "Starvation threshold", min = "0", max = "26", persist = false) - private int starvation = 5; - - @Parameter(label = "Birth threshold", min = "0", max = "26", persist = false) - private int birth = 6; - - @Parameter(label = "Suffocation threshold", min = "0", max = "26", persist = false) - private int suffocation = 9; - - @Parameter(choices = { SIX, EIGHTEEN, TWENTY_SIX }, persist = false) - private String connectedness = TWENTY_SIX; - - @Parameter(label = "Initial saturation % when randomizing", min = "1", max = "99", style = NumberWidget.SCROLL_BAR_STYLE, persist = false) - private int saturation = 10; - -// @Parameter(label = "Play speed", min = "1", max="100", style = NumberWidget.SCROLL_BAR_STYLE, persist = false) - private int playSpeed = 10; -// - @Parameter(callback = "iterate") - private Button iterate; - - @Parameter(callback = "randomize") - private Button randomize; - - @Parameter(callback = "play") - private Button play; - - @Parameter(callback = "pause") - private Button pause; - - private int w = 64, h = 64, d = 64; - private Img field; - private String name; - private float[] voxelDims; - private Volume volume; - - /** Temporary buffer for use while recomputing the image. */ - private boolean[] bits = new boolean[w * h * d]; - private GenericDialog dialog; - - /** Repeatedly iterates the simulation until stopped **/ - public void play() { - sciView.animate( playSpeed, this::iterate); - } - - /** Stops the simulation **/ - public void pause() { - sciView.stopAnimation(); - } - - /** Randomizes a new bit field. */ - public void randomize() { - final Cursor cursor = field.localizingCursor(); - final double chance = saturation / 100d; - while( cursor.hasNext() ) { - final boolean alive = Math.random() <= chance; - cursor.next().set( alive ? ALIVE : DEAD ); - } - updateVolume(); - } - - /** Performs one iteration of the game. */ - public void iterate() { - final int connected; - switch( connectedness ) { - case SIX: connected = 6; break; - case EIGHTEEN: connected = 18; break; - default: connected = 26; break; - } - - // compute the new image field - final RandomAccess access = field.randomAccess(); - - //RandomAccess access = ((SourceAndConverter) ((Volume.VolumeDataSource.RAIISource) volume.getDataSource()).getSources().get(0)).getSpimSource().getSource(0, 0).randomAccess(); - - for( int z = 0; z < d; z++ ) { - for( int y = 0; y < h; y++ ) { - for( int x = 0; x < w; x++ ) { - final int i = z * w * h + y * w + x; - final int n = neighbors( access, x, y, z, connected ); - access.setPosition( x, 0 ); - access.setPosition( y, 1 ); - access.setPosition( y, 2 ); - if( alive( access ) ) { - // Living cell stays alive within (starvation, suffocation). - bits[i] = n > starvation && n < suffocation; - } else { - // New cell forms within [birth, suffocation). - bits[i] = n >= birth && n < suffocation; - } - } - } - } - - // write the new bit field into the image - final Cursor cursor = field.localizingCursor(); - while( cursor.hasNext() ) { - cursor.fwd(); - final int x = cursor.getIntPosition( 0 ); - final int y = cursor.getIntPosition( 1 ); - final int z = cursor.getIntPosition( 2 ); - final boolean alive = bits[z * w * h + y * w + x]; - cursor.get().set( alive ? ALIVE : DEAD ); - } - -// for( int z = 0; z < d; z++ ) { -// for( int y = 0; y < h; y++ ) { -// for( int x = 0; x < w; x++ ) { -// access.setPosition( x, 0 ); -// access.setPosition( y, 1 ); -// access.setPosition( y, 2 ); -// final boolean alive = bits[z * w * h + y * w + x]; -// access.get().set( alive ? ALIVE : DEAD ); -// } -// } -// } - - updateVolume(); - } - - @Override - public void run() { - field = ArrayImgs.unsignedBytes( w, h, d ); - randomize(); - - dialog = new GenericDialog("Game of Life 3D"); - dialog.addNumericField("Starvation threshold", starvation, 0); - dialog.addNumericField("Birth threshold", birth, 0); - dialog.addNumericField("Suffocation threshold", suffocation, 0); - dialog.addNumericField("Initial saturation % when randomizing", saturation, 0); - dialog.showDialog(); - - if( dialog.wasCanceled() ) return; - - starvation = (int) dialog.getNextNumber(); - birth = (int) dialog.getNextNumber(); - suffocation = (int) dialog.getNextNumber(); - saturation = (int) dialog.getNextNumber(); - - randomize(); - play(); - -// -// @Parameter(callback = "iterate") -// private Button iterate; -// -// @Parameter(callback = "randomize") -// private Button randomize; -// -// @Parameter(callback = "play") -// private Button play; -// -// @Parameter(callback = "pause") -// private Button pause; - - //play(); - - //eventService.subscribe(this); - } - - - // -- Helper methods -- - - private int neighbors( RandomAccess access, int x, int y, int z, int connected ) { - int n = 0; - // six-connected - n += val( access, x - 1, y, z ); - n += val( access, x + 1, y, z ); - n += val( access, x, y - 1, z ); - n += val( access, x, y + 1, z ); - n += val( access, x, y, z - 1 ); - n += val( access, x, y, z + 1 ); - // eighteen-connected - if( connected >= 18 ) { - n += val( access, x - 1, y - 1, z ); - n += val( access, x + 1, y - 1, z ); - n += val( access, x - 1, y + 1, z ); - n += val( access, x + 1, y + 1, z ); - n += val( access, x - 1, y, z - 1 ); - n += val( access, x + 1, y, z - 1 ); - n += val( access, x - 1, y, z + 1 ); - n += val( access, x + 1, y, z + 1 ); - n += val( access, x, y - 1, z - 1 ); - n += val( access, x, y + 1, z - 1 ); - n += val( access, x, y - 1, z + 1 ); - n += val( access, x, y + 1, z + 1 ); - } - // twenty-six-connected - if( connected == 26 ) { - n += val( access, x - 1, y - 1, z - 1 ); - n += val( access, x + 1, y - 1, z - 1 ); - n += val( access, x - 1, y + 1, z - 1 ); - n += val( access, x + 1, y + 1, z - 1 ); - n += val( access, x - 1, y - 1, z + 1 ); - n += val( access, x + 1, y - 1, z + 1 ); - n += val( access, x - 1, y + 1, z + 1 ); - n += val( access, x + 1, y + 1, z + 1 ); - } - return n; - } - - - - private int val( RandomAccess access, int x, int y, int z ) { - if( x < 0 || x >= w || y < 0 || y >= h || z < 0 || z >= d ) return 0; - access.setPosition( x, 0 ); - access.setPosition( y, 1 ); - access.setPosition( z, 2 ); - return alive( access ) ? 1 : 0; - } - - private boolean alive( final Sampler access ) { - return access.get().get() == ALIVE; - } - - private long tick; - - private void updateVolume() { - if( volume == null ) { - name = "Life Simulation"; - voxelDims = new float[] { 1, 1, 1 }; - volume = ( Volume ) sciView.addVolume( field, name, voxelDims ); - - BoundingGrid bg = new BoundingGrid(); - bg.setNode( volume ); - -// volume.setVoxelSizeX(10.0f); -// volume.setVoxelSizeY(10.0f); -// volume.setVoxelSizeZ(10.0f); - - volume.putAbove(new Vector3f(0.0f, 0.0f, 0.0f)); -// volume.setRenderingMethod(2); - volume.getTransferFunction().addControlPoint(0.0f, 0.0f); - volume.getTransferFunction().addControlPoint(0.4f, 0.3f); - - volume.setName( "Game of Life 3D" ); - - sciView.centerOnNode(volume); - } else { - // NB: Name must be unique each time. - sciView.updateVolume( field, name + "-" + ++tick, voxelDims, volume ); - -// RandomAccessibleIntervalSource newSource = new RandomAccessibleIntervalSource(field, new UnsignedByteType(), name + "-" + ++tick); -// -// SourceAndConverter sourceAndConverter = BigDataViewer.wrapWithTransformedSource( -// new SourceAndConverter<>(newSource, BigDataViewer.createConverterToARGB(new UnsignedByteType()))); -// -// ((Volume.VolumeDataSource.RAISource) volume.getDataSource()).getSources().set(0, sourceAndConverter); -// -// volume.getVolumeManager().notifyUpdate(volume); -// -// volume.setDirty(true); -// volume.setNeedsUpdate(true); -// volume.getVolumeManager().requestRepaint(); - //volume.getCacheControl().prepareNextFrame(); - } - } - - /** - * Stops the animation when the volume node is removed. - * @param event - */ - @EventHandler - private void onNodeRemoved(NodeRemovedEvent event) { - if(event.getNode() == volume) { - sciView.stopAnimation(); - } - } - - /** - * Returns the current Img - */ - public Img getImg() { - return field; - } - - /** - * Returns the scenery volume node. - */ - public Volume getVolume() { - return volume; - } - - public static void main(String... args) throws Exception { - SciView sv = SciView.create(); - - CommandService command = sv.getScijavaContext().getService(CommandService.class); - - HashMap argmap = new HashMap<>(); - - command.run(GameOfLife3D.class, true, argmap); - } -} diff --git a/src/main/java/sc/iview/commands/demo/GameOfLife3D.kt b/src/main/java/sc/iview/commands/demo/GameOfLife3D.kt new file mode 100644 index 00000000..72650e12 --- /dev/null +++ b/src/main/java/sc/iview/commands/demo/GameOfLife3D.kt @@ -0,0 +1,346 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands.demo + +import graphics.scenery.BoundingGrid +import graphics.scenery.volumes.Volume +import ij.gui.GenericDialog +import net.imglib2.IterableInterval +import net.imglib2.RandomAccess +import net.imglib2.RandomAccessibleInterval +import net.imglib2.Sampler +import net.imglib2.img.Img +import net.imglib2.img.array.ArrayImgs +import net.imglib2.type.numeric.integer.UnsignedByteType +import org.joml.Vector3f +import org.scijava.command.Command +import org.scijava.command.CommandService +import org.scijava.event.EventHandler +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import org.scijava.widget.Button +import org.scijava.widget.NumberWidget +import sc.iview.SciView +import sc.iview.commands.MenuWeights +import sc.iview.event.NodeRemovedEvent +import java.util.* + +/** + * Conway's Game of Lifein 3D! + * + * @author Curtis Rueden + * @author Kyle Harrington + * @author Ulrik Guenther + */ +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "Demo", weight = MenuWeights.DEMO), Menu(label = "Game of Life 3D", weight = MenuWeights.DEMO_GAME_OF_LIFE)]) +class GameOfLife3D : Command { + @Parameter + private lateinit var sciView: SciView + + @Parameter(label = "Starvation threshold", min = "0", max = "26", persist = false) + private var starvation = 5 + + @Parameter(label = "Birth threshold", min = "0", max = "26", persist = false) + private var birth = 6 + + @Parameter(label = "Suffocation threshold", min = "0", max = "26", persist = false) + private var suffocation = 9 + + @Parameter(choices = [SIX, EIGHTEEN, TWENTY_SIX], persist = false) + private var connectedness = TWENTY_SIX + + @Parameter(label = "Initial saturation % when randomizing", min = "1", max = "99", style = NumberWidget.SCROLL_BAR_STYLE, persist = false) + private var saturation = 10 + + // @Parameter(label = "Play speed", min = "1", max="100", style = NumberWidget.SCROLL_BAR_STYLE, persist = false) + private val playSpeed = 10 + + // + @Parameter(callback = "iterate") + private lateinit var iterate: Button + + @Parameter(callback = "randomize") + private lateinit var randomize: Button + + @Parameter(callback = "play") + private lateinit var play: Button + + @Parameter(callback = "pause") + private lateinit var pause: Button + private val w = 64 + private val h = 64 + private val d = 64 + + /** + * Returns the current Img + */ + var img: Img? = null + private set + private var name: String? = null + private var voxelDims: FloatArray = floatArrayOf(1.0f, 1.0f, 1.0f) + + /** + * Returns the scenery volume node. + */ + var volume: Volume? = null + private set + + /** Temporary buffer for use while recomputing the image. */ + private val bits = BooleanArray(w * h * d) + private var dialog: GenericDialog? = null + + /** Repeatedly iterates the simulation until stopped */ + fun play() { + sciView.animate(playSpeed) { iterate() } + } + + /** Stops the simulation */ + fun pause() { + sciView.stopAnimation() + } + + /** Randomizes a new bit field. */ + fun randomize() { + val cursor = img!!.localizingCursor() + val chance = saturation / 100.0 + while (cursor.hasNext()) { + val alive = Math.random() <= chance + cursor.next().set(if (alive) ALIVE else DEAD) + } + updateVolume() + } + + /** Performs one iteration of the game. */ + fun iterate() { + val connected = when (connectedness) { + SIX -> 6 + EIGHTEEN -> 18 + else -> 26 + } + + // compute the new image field + val access = img!!.randomAccess() + + //RandomAccess access = ((SourceAndConverter) ((Volume.VolumeDataSource.RAIISource) volume.getDataSource()).getSources().get(0)).getSpimSource().getSource(0, 0).randomAccess(); + for (z in 0 until d) { + for (y in 0 until h) { + for (x in 0 until w) { + val i = z * w * h + y * w + x + val n = neighbors(access, x, y, z, connected) + access.setPosition(x, 0) + access.setPosition(y, 1) + access.setPosition(y, 2) + if (alive(access)) { + // Living cell stays alive within (starvation, suffocation). + bits[i] = n > starvation && n < suffocation + } else { + // New cell forms within [birth, suffocation). + bits[i] = n >= birth && n < suffocation + } + } + } + } + + // write the new bit field into the image + val cursor = img!!.localizingCursor() + while (cursor.hasNext()) { + cursor.fwd() + val x = cursor.getIntPosition(0) + val y = cursor.getIntPosition(1) + val z = cursor.getIntPosition(2) + val alive = bits[z * w * h + y * w + x] + cursor.get().set(if (alive) ALIVE else DEAD) + } + +// for( int z = 0; z < d; z++ ) { +// for( int y = 0; y < h; y++ ) { +// for( int x = 0; x < w; x++ ) { +// access.setPosition( x, 0 ); +// access.setPosition( y, 1 ); +// access.setPosition( y, 2 ); +// final boolean alive = bits[z * w * h + y * w + x]; +// access.get().set( alive ? ALIVE : DEAD ); +// } +// } +// } + updateVolume() + } + + override fun run() { + img = ArrayImgs.unsignedBytes(w.toLong(), h.toLong(), d.toLong()) + randomize() + dialog = GenericDialog("Game of Life 3D") + dialog!!.addNumericField("Starvation threshold", starvation.toDouble(), 0) + dialog!!.addNumericField("Birth threshold", birth.toDouble(), 0) + dialog!!.addNumericField("Suffocation threshold", suffocation.toDouble(), 0) + dialog!!.addNumericField("Initial saturation % when randomizing", saturation.toDouble(), 0) + dialog!!.showDialog() + if (dialog!!.wasCanceled()) return + starvation = dialog!!.nextNumber.toInt() + birth = dialog!!.nextNumber.toInt() + suffocation = dialog!!.nextNumber.toInt() + saturation = dialog!!.nextNumber.toInt() + randomize() + play() + +// +// @Parameter(callback = "iterate") +// private Button iterate; +// +// @Parameter(callback = "randomize") +// private Button randomize; +// +// @Parameter(callback = "play") +// private Button play; +// +// @Parameter(callback = "pause") +// private Button pause; + + //play(); + + //eventService.subscribe(this); + } + + // -- Helper methods -- + private fun neighbors(access: RandomAccess, x: Int, y: Int, z: Int, connected: Int): Int { + var n = 0 + // six-connected + n += value(access, x - 1, y, z) + n += value(access, x + 1, y, z) + n += value(access, x, y - 1, z) + n += value(access, x, y + 1, z) + n += value(access, x, y, z - 1) + n += value(access, x, y, z + 1) + // eighteen-connected + if (connected >= 18) { + n += value(access, x - 1, y - 1, z) + n += value(access, x + 1, y - 1, z) + n += value(access, x - 1, y + 1, z) + n += value(access, x + 1, y + 1, z) + n += value(access, x - 1, y, z - 1) + n += value(access, x + 1, y, z - 1) + n += value(access, x - 1, y, z + 1) + n += value(access, x + 1, y, z + 1) + n += value(access, x, y - 1, z - 1) + n += value(access, x, y + 1, z - 1) + n += value(access, x, y - 1, z + 1) + n += value(access, x, y + 1, z + 1) + } + // twenty-six-connected + if (connected == 26) { + n += value(access, x - 1, y - 1, z - 1) + n += value(access, x + 1, y - 1, z - 1) + n += value(access, x - 1, y + 1, z - 1) + n += value(access, x + 1, y + 1, z - 1) + n += value(access, x - 1, y - 1, z + 1) + n += value(access, x + 1, y - 1, z + 1) + n += value(access, x - 1, y + 1, z + 1) + n += value(access, x + 1, y + 1, z + 1) + } + return n + } + + private fun value(access: RandomAccess, x: Int, y: Int, z: Int): Int { + if (x < 0 || x >= w || y < 0 || y >= h || z < 0 || z >= d) return 0 + access.setPosition(x, 0) + access.setPosition(y, 1) + access.setPosition(z, 2) + return if (alive(access)) 1 else 0 + } + + private fun alive(access: Sampler): Boolean { + return access.get().get() == ALIVE + } + + private var tick: Long = 0 + private fun updateVolume() { + if (volume == null) { + name = "Life Simulation" + voxelDims = floatArrayOf(1f, 1f, 1f) + volume = sciView.addVolume(img as RandomAccessibleInterval, name, *voxelDims) as Volume? + val bg = BoundingGrid() + bg.node = volume + +// volume.setVoxelSizeX(10.0f); +// volume.setVoxelSizeY(10.0f); +// volume.setVoxelSizeZ(10.0f); + volume!!.putAbove(Vector3f(0.0f, 0.0f, 0.0f)) + // volume.setRenderingMethod(2); + volume!!.transferFunction.addControlPoint(0.0f, 0.0f) + volume!!.transferFunction.addControlPoint(0.4f, 0.3f) + volume!!.name = "Game of Life 3D" + sciView.centerOnNode(volume) + } else { + // NB: Name must be unique each time. + sciView.updateVolume(img as IterableInterval, name + "-" + ++tick, voxelDims, volume!!) + +// RandomAccessibleIntervalSource newSource = new RandomAccessibleIntervalSource(field, new UnsignedByteType(), name + "-" + ++tick); +// +// SourceAndConverter sourceAndConverter = BigDataViewer.wrapWithTransformedSource( +// new SourceAndConverter<>(newSource, BigDataViewer.createConverterToARGB(new UnsignedByteType()))); +// +// ((Volume.VolumeDataSource.RAISource) volume.getDataSource()).getSources().set(0, sourceAndConverter); +// +// volume.getVolumeManager().notifyUpdate(volume); +// +// volume.setDirty(true); +// volume.setNeedsUpdate(true); +// volume.getVolumeManager().requestRepaint(); + //volume.getCacheControl().prepareNextFrame(); + } + } + + /** + * Stops the animation when the volume node is removed. + * @param event + */ + @EventHandler + private fun onNodeRemoved(event: NodeRemovedEvent) { + if (event.node === volume) { + sciView.stopAnimation() + } + } + + companion object { + private const val ALIVE = 255 + private const val DEAD = 16 + private const val SIX = "6-connected" + private const val EIGHTEEN = "18-connected" + private const val TWENTY_SIX = "26-connected" + @Throws(Exception::class) + @JvmStatic + fun main(args: Array) { + val sv = SciView.create() + val command = sv.scijavaContext!!.getService(CommandService::class.java) + val argmap = HashMap() + command.run(GameOfLife3D::class.java, true, argmap) + } + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/demo/Line3DDemo.java b/src/main/java/sc/iview/commands/demo/Line3DDemo.java index 931e4330..7c35024f 100644 --- a/src/main/java/sc/iview/commands/demo/Line3DDemo.java +++ b/src/main/java/sc/iview/commands/demo/Line3DDemo.java @@ -28,6 +28,7 @@ */ package sc.iview.commands.demo; +import org.joml.Vector3f; import org.scijava.command.Command; import org.scijava.command.CommandService; import org.scijava.plugin.Menu; @@ -36,8 +37,6 @@ import org.scijava.util.ColorRGB; import sc.iview.SciView; import sc.iview.node.Line3D; -import sc.iview.vector.JOMLVector3; -import sc.iview.vector.Vector3; import java.util.ArrayList; import java.util.HashMap; @@ -62,11 +61,11 @@ public class Line3DDemo implements Command { @Override public void run() { int numPoints = 25; - List points = new ArrayList<>(); + List points = new ArrayList<>(); List colors = new ArrayList<>(); for( int k = 0; k < numPoints; k++ ) { - points.add( new JOMLVector3( ( float ) ( 10.0f * Math.random() - 5.0f ), // + points.add( new Vector3f( ( float ) ( 10.0f * Math.random() - 5.0f ), // ( float ) ( 10.0f * Math.random() - 5.0f ), // ( float ) ( 10.0f * Math.random() - 5.0f ) ) ); colors.add(new ColorRGB((int) (Math.random()*255), (int) (Math.random()*255), (int) (Math.random()*255))); diff --git a/src/main/java/sc/iview/commands/demo/LineDemo.java b/src/main/java/sc/iview/commands/demo/LineDemo.java index b601e72f..89802452 100644 --- a/src/main/java/sc/iview/commands/demo/LineDemo.java +++ b/src/main/java/sc/iview/commands/demo/LineDemo.java @@ -31,6 +31,7 @@ import static sc.iview.commands.MenuWeights.DEMO; import static sc.iview.commands.MenuWeights.DEMO_LINES; +import org.joml.Vector3f; import org.scijava.command.Command; import org.scijava.command.CommandService; import org.scijava.plugin.Menu; @@ -39,9 +40,6 @@ import org.scijava.util.Colors; import sc.iview.SciView; -import sc.iview.vector.JOMLVector3; -import sc.iview.vector.Vector3; - import java.util.HashMap; /** @@ -61,12 +59,12 @@ public class LineDemo implements Command { @Override public void run() { int numPoints = 25; - Vector3[] points = new Vector3[numPoints]; + Vector3f[] points = new Vector3f[numPoints]; for( int k = 0; k < numPoints; k++ ) { - points[k] = new JOMLVector3( ( float ) ( 10.0f * Math.random() - 5.0f ), // - ( float ) ( 10.0f * Math.random() - 5.0f ), // - ( float ) ( 10.0f * Math.random() - 5.0f ) ); + points[k] = new Vector3f( ( float ) ( 10.0f * Math.random() - 5.0f ), // + ( float ) ( 10.0f * Math.random() - 5.0f ), // + ( float ) ( 10.0f * Math.random() - 5.0f ) ); } double edgeWidth = 0.1; diff --git a/src/main/java/sc/iview/commands/demo/LoadCremiDatasetAndNeurons.kt b/src/main/java/sc/iview/commands/demo/LoadCremiDatasetAndNeurons.kt index ce118ffa..818e7ebb 100644 --- a/src/main/java/sc/iview/commands/demo/LoadCremiDatasetAndNeurons.kt +++ b/src/main/java/sc/iview/commands/demo/LoadCremiDatasetAndNeurons.kt @@ -5,14 +5,13 @@ import bdv.viewer.Interpolation import ch.systemsx.cisd.hdf5.HDF5Factory import graphics.scenery.Material import graphics.scenery.Origin -import graphics.scenery.numerics.Random import graphics.scenery.utils.extensions.xyz import graphics.scenery.volumes.Colormap -import graphics.scenery.volumes.Colormap.Companion.fromColorTable import graphics.scenery.volumes.TransferFunction import graphics.scenery.volumes.Volume import net.imagej.lut.LUTService import net.imagej.mesh.Mesh +import net.imagej.mesh.Meshes import net.imagej.ops.OpService import net.imagej.ops.geom.geom3d.mesh.BitTypeVertexInterpolator import net.imglib2.RandomAccessibleInterval @@ -35,7 +34,6 @@ import org.scijava.plugin.Plugin import org.scijava.ui.UIService import org.scijava.widget.FileWidget import sc.iview.SciView -import sc.iview.SciViewService import sc.iview.commands.MenuWeights import sc.iview.process.MeshConverter import java.io.FileFilter @@ -87,6 +85,7 @@ class LoadCremiDatasetAndNeurons: Command { * @see Thread.run */ override fun run() { + val task = sciview.taskManager.newTask("Cremi", "Loading dataset") val filter = FileFilter { file -> val extension = file.name.substringAfterLast(".").toLowerCase() @@ -112,6 +111,8 @@ class LoadCremiDatasetAndNeurons: Command { volume?.scale = Vector3f(0.04f, 0.04f, 2.5f) volume?.transferFunction = TransferFunction.ramp(0.3f, 0.1f, 0.1f) // min 20, max 180, color map fire + + volume?.transferFunction?.addControlPoint(0.3f, 0.5f) volume?.transferFunction?.addControlPoint(0.8f, 0.01f) volume?.converterSetups?.get(0)?.setDisplayRange(20.0, 220.0) val colormap = lut.loadLUT(lut.findLUTs().get("Grays.lut")) @@ -120,6 +121,8 @@ class LoadCremiDatasetAndNeurons: Command { volume?.colormap = Colormap.fromColorTable(colormap) + task.status = "Creating labeling" + task.completion = 10.0f val rai = nai.second log.info("Got ${nai.first.size} labels") @@ -138,9 +141,11 @@ class LoadCremiDatasetAndNeurons: Command { regions.filter { largestNeuronLabels.contains(it.label.toLong() + 1L) }.forEachIndexed { i, region -> log.info("Meshing neuron ${i + 1}/${largestNeuronLabels.size} with label ${region.label}...") + task.status = "Meshing neuron ${i+1}/${largestNeuronLabels.size}" + // ui.show(region) // Generate the mesh with imagej-ops - val m: Mesh = ops.geom().marchingCubes(region, 1.0, BitTypeVertexInterpolator()) + val m: Mesh = Meshes.marchingCubes(region); log.info("Converting neuron ${i + 1}/${largestNeuronLabels.size} to scenery format...") // Convert the mesh into a scenery mesh for visualization @@ -149,11 +154,13 @@ class LoadCremiDatasetAndNeurons: Command { mesh.material.diffuse = colormapNeurons.lookupARGB(0.0, 255.0, kotlin.random.Random.nextDouble(0.0, 255.0)).toRGBColor().xyz() mesh.material.roughness = 0.0f - // marching cubes produces CW meshes, not CCW as expected by default - mesh.material.cullingMode = Material.CullingMode.Front mesh.name = "Neuron $i" sciview.addNode(mesh) + val completion = 10.0f + ((i+1)/largestNeuronLabels.size.toFloat())*90.0f + task.completion = completion } + + task.completion = 100.0f } fun readCremiHDF5(path: String, scale: Double = 1.0): NeuronsAndImage? { diff --git a/src/main/java/sc/iview/commands/demo/VolumeRenderDemo.java b/src/main/java/sc/iview/commands/demo/VolumeRenderDemo.java deleted file mode 100644 index 618ac57c..00000000 --- a/src/main/java/sc/iview/commands/demo/VolumeRenderDemo.java +++ /dev/null @@ -1,131 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.commands.demo; - -import graphics.scenery.volumes.Volume; -import io.scif.services.DatasetIOService; -import net.imagej.Dataset; -import net.imagej.mesh.Mesh; -import net.imagej.ops.OpService; -import net.imagej.ops.geom.geom3d.mesh.BitTypeVertexInterpolator; -import net.imglib2.img.Img; -import net.imglib2.type.logic.BitType; -import net.imglib2.type.numeric.integer.UnsignedByteType; -import org.scijava.command.Command; -import org.scijava.command.CommandService; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; -import sc.iview.process.MeshConverter; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; - -import static sc.iview.commands.MenuWeights.DEMO; -import static sc.iview.commands.MenuWeights.DEMO_VOLUME_RENDER; - -/** - * A demo of volume rendering. - * - * @author Kyle Harrington - * @author Curtis Rueden - */ -@Plugin(type = Command.class, label = "Volume Render/Isosurface Demo", menuRoot = "SciView", // - menu = { @Menu(label = "Demo", weight = DEMO), // - @Menu(label = "Volume Render/Isosurface", weight = DEMO_VOLUME_RENDER) }) -public class VolumeRenderDemo implements Command { - - @Parameter - private DatasetIOService datasetIO; - - @Parameter - private LogService log; - - @Parameter - private OpService ops; - - @Parameter - private SciView sciView; - - @Parameter(label = "Show isosurface") - private boolean iso = true; - - @Override - public void run() { - final Dataset cube; - try { - File cubeFile = ResourceLoader.createFile( getClass(), "/cored_cube_var2_8bit.tif" ); - - cube = datasetIO.open( cubeFile.getAbsolutePath() ); - } - catch (IOException exc) { - log.error( exc ); - return; - } - - Volume v = (Volume) sciView.addVolume( cube, new float[] { 1, 1, 1 } ); - v.setPixelToWorldRatio(0.05f); - v.setName( "Volume Render Demo" ); - v.setDirty(true); - v.setNeedsUpdate(true); - - if (iso) { - int isoLevel = 1; - - @SuppressWarnings("unchecked") - Img cubeImg = ( Img ) cube.getImgPlus().getImg(); - - Img bitImg = ( Img ) ops.threshold().apply( cubeImg, new UnsignedByteType( isoLevel ) ); - - Mesh m = ops.geom().marchingCubes( bitImg, isoLevel, new BitTypeVertexInterpolator() ); - - graphics.scenery.Mesh isoSurfaceMesh = MeshConverter.toScenery(m,false); - v.addChild(isoSurfaceMesh); - - isoSurfaceMesh.setName( "Volume Render Demo Isosurface" ); - } - - sciView.setActiveNode(v); - sciView.centerOnNode( sciView.getActiveNode() ); - } - - public static void main(String... args) throws Exception { - SciView sv = SciView.create(); - - CommandService command = sv.getScijavaContext().getService(CommandService.class); - - HashMap argmap = new HashMap(); - argmap.put("iso",true); - - command.run(VolumeRenderDemo.class, true, argmap); - } -} diff --git a/src/main/java/sc/iview/commands/demo/VolumeRenderDemo.kt b/src/main/java/sc/iview/commands/demo/VolumeRenderDemo.kt new file mode 100644 index 00000000..d5c07c42 --- /dev/null +++ b/src/main/java/sc/iview/commands/demo/VolumeRenderDemo.kt @@ -0,0 +1,115 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands.demo + +import graphics.scenery.volumes.Volume +import io.scif.services.DatasetIOService +import net.imagej.Dataset +import net.imagej.ops.OpService +import net.imagej.ops.geom.geom3d.mesh.BitTypeVertexInterpolator +import net.imglib2.img.Img +import net.imglib2.type.logic.BitType +import net.imglib2.type.numeric.integer.UnsignedByteType +import org.scijava.command.Command +import org.scijava.command.CommandService +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.DEMO +import sc.iview.commands.MenuWeights.DEMO_VOLUME_RENDER +import sc.iview.process.MeshConverter +import java.io.IOException +import java.util.* + +/** + * A demo of volume rendering. + * + * @author Kyle Harrington + * @author Curtis Rueden + */ +@Plugin(type = Command::class, label = "Volume Render/Isosurface Demo", menuRoot = "SciView", menu = [Menu(label = "Demo", weight = DEMO), Menu(label = "Volume Render/Isosurface", weight = DEMO_VOLUME_RENDER)]) +class VolumeRenderDemo : Command { + @Parameter + private lateinit var datasetIO: DatasetIOService + + @Parameter + private lateinit var log: LogService + + @Parameter + private lateinit var ops: OpService + + @Parameter + private lateinit var sciView: SciView + + @Parameter(label = "Show isosurface") + private var iso: Boolean = true + + override fun run() { + val cube: Dataset + cube = try { + val cubeFile = ResourceLoader.createFile(javaClass, "/cored_cube_var2_8bit.tif") + datasetIO.open(cubeFile.absolutePath) + } catch (exc: IOException) { + log.error(exc) + return + } + val v = sciView.addVolume(cube, floatArrayOf(1f, 1f, 1f)) as Volume + v.pixelToWorldRatio = 0.05f + v.name = "Volume Render Demo" + v.dirty = true + v.needsUpdate = true + if (iso) { + val isoLevel = 1 + + @Suppress("UNCHECKED_CAST") + val cubeImg = cube.imgPlus.img as Img + val bitImg = ops.threshold().apply(cubeImg, UnsignedByteType(isoLevel)) as Img + val m = ops.geom().marchingCubes(bitImg, isoLevel.toDouble(), BitTypeVertexInterpolator()) + val isoSurfaceMesh = MeshConverter.toScenery(m, false) + v.addChild(isoSurfaceMesh) + isoSurfaceMesh.name = "Volume Render Demo Isosurface" + } + sciView.setActiveNode(v) + sciView.centerOnNode(sciView.activeNode) + } + + companion object { + @Throws(Exception::class) + @JvmStatic + fun main(args: Array) { + val sv = SciView.create() + val command = sv.scijavaContext!!.getService(CommandService::class.java) + val argmap = HashMap() + argmap["iso"] = true + command.run(VolumeRenderDemo::class.java, true, argmap) + } + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/edit/AddBox.java b/src/main/java/sc/iview/commands/edit/AddBox.java index 31558031..47ab4542 100644 --- a/src/main/java/sc/iview/commands/edit/AddBox.java +++ b/src/main/java/sc/iview/commands/edit/AddBox.java @@ -31,6 +31,7 @@ import static sc.iview.commands.MenuWeights.EDIT; import static sc.iview.commands.MenuWeights.EDIT_ADD_BOX; +import org.joml.Vector3f; import org.scijava.command.Command; import org.scijava.display.DisplayService; import org.scijava.plugin.Menu; @@ -39,8 +40,6 @@ import org.scijava.util.ColorRGB; import sc.iview.SciView; -import sc.iview.vector.JOMLVector3; -import sc.iview.vector.Vector3; /** * Command to add a box to the scene @@ -75,8 +74,8 @@ public class AddBox implements Command { @Override public void run() { //final Vector3 pos = ClearGLVector3.parse( position ); - final Vector3 pos = new JOMLVector3(0, 0, 0); - final Vector3 vSize = new JOMLVector3( size, size, size ); + final Vector3f pos = new Vector3f(0f, 0f, 0f); + final Vector3f vSize = new Vector3f( size, size, size ); sciView.addBox( pos, vSize, color, inside ); } } diff --git a/src/main/java/sc/iview/commands/edit/AddCamera.java b/src/main/java/sc/iview/commands/edit/AddCamera.java index 666c77a8..d5738492 100644 --- a/src/main/java/sc/iview/commands/edit/AddCamera.java +++ b/src/main/java/sc/iview/commands/edit/AddCamera.java @@ -29,14 +29,13 @@ package sc.iview.commands.edit; import graphics.scenery.DetachedHeadCamera; +import org.joml.Vector3f; import org.scijava.command.Command; import org.scijava.display.DisplayService; import org.scijava.plugin.Menu; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import sc.iview.SciView; -import sc.iview.vector.JOMLVector3; -import sc.iview.vector.Vector3; import static sc.iview.commands.MenuWeights.EDIT; import static sc.iview.commands.MenuWeights.EDIT_ADD_CAMERA; @@ -74,7 +73,7 @@ public class AddCamera implements Command { @Override public void run() { //final Vector3 pos = ClearGLVector3.parse( position ); - final Vector3 pos = new JOMLVector3(0, 0, 0); + final Vector3f pos = new Vector3f(0, 0, 0); final DetachedHeadCamera cam = new DetachedHeadCamera(); cam.perspectiveCamera( fov, sciView.getWindowWidth(), sciView.getWindowHeight(), Math.min(nearPlane, farPlane), Math.max(nearPlane, farPlane) ); cam.setPosition( pos ); diff --git a/src/main/java/sc/iview/commands/edit/AddLine.java b/src/main/java/sc/iview/commands/edit/AddLine.java index 5288e327..e1386b37 100644 --- a/src/main/java/sc/iview/commands/edit/AddLine.java +++ b/src/main/java/sc/iview/commands/edit/AddLine.java @@ -31,6 +31,7 @@ import static sc.iview.commands.MenuWeights.EDIT; import static sc.iview.commands.MenuWeights.EDIT_ADD_LINE; +import org.joml.Vector3f; import org.scijava.command.Command; import org.scijava.plugin.Menu; import org.scijava.plugin.Parameter; @@ -38,8 +39,6 @@ import org.scijava.util.ColorRGB; import sc.iview.SciView; -import sc.iview.vector.JOMLVector3; -import sc.iview.vector.Vector3; /** * Command to add a line in the scene @@ -71,7 +70,7 @@ public class AddLine implements Command { @Override public void run() { //Vector3[] endpoints = { JOMLVector3.parse( start ), JOMLVector3.parse( stop ) }; - Vector3[] endpoints = { new JOMLVector3( 0, 0, 0 ), new JOMLVector3( 1, 1, 1 ) }; + Vector3f[] endpoints = { new Vector3f( 0, 0, 0 ), new Vector3f( 1, 1, 1 ) }; sciView.addLine( endpoints, color, edgeWidth ); } } diff --git a/src/main/java/sc/iview/commands/edit/AddOrientationCompass.java b/src/main/java/sc/iview/commands/edit/AddOrientationCompass.java index c9da9995..2df81396 100644 --- a/src/main/java/sc/iview/commands/edit/AddOrientationCompass.java +++ b/src/main/java/sc/iview/commands/edit/AddOrientationCompass.java @@ -121,10 +121,9 @@ public void run() { root.getUpdate().add(() -> { final Camera cam = sciView.getCamera(); root.setPosition(cam.viewportToView(new Vector2f(-0.9f, 0.7f))); - root.setRotation(new Quaternionf(sciView.getCamera().getRotation()).conjugate()); + root.setRotation(new Quaternionf(sciView.getCamera().getRotation()).conjugate().normalize()); return null; }); - } public static void main(String... args) throws Exception { diff --git a/src/main/java/sc/iview/commands/edit/AddSphere.java b/src/main/java/sc/iview/commands/edit/AddSphere.java index 23f9a05d..69fbd560 100644 --- a/src/main/java/sc/iview/commands/edit/AddSphere.java +++ b/src/main/java/sc/iview/commands/edit/AddSphere.java @@ -31,6 +31,7 @@ import static sc.iview.commands.MenuWeights.EDIT; import static sc.iview.commands.MenuWeights.EDIT_ADD_SPHERE; +import org.joml.Vector3f; import org.scijava.command.Command; import org.scijava.plugin.Menu; import org.scijava.plugin.Parameter; @@ -38,8 +39,6 @@ import org.scijava.util.ColorRGB; import sc.iview.SciView; -import sc.iview.vector.JOMLVector3; -import sc.iview.vector.Vector3; /** * Command to add a sphere in the scene @@ -67,7 +66,7 @@ public class AddSphere implements Command { @Override public void run() { //final Vector3 pos = ClearGLVector3.parse( position ); - final Vector3 pos = new JOMLVector3(0, 0, 0); + final Vector3f pos = new Vector3f(0, 0, 0); sciView.addSphere( pos, radius, color ); } } diff --git a/src/main/java/sc/iview/commands/edit/NavigationControlsSettings.java b/src/main/java/sc/iview/commands/edit/NavigationControlsSettings.java index 9c9ea1a4..93f020b7 100644 --- a/src/main/java/sc/iview/commands/edit/NavigationControlsSettings.java +++ b/src/main/java/sc/iview/commands/edit/NavigationControlsSettings.java @@ -100,11 +100,11 @@ private void setupBoundsFromSciView() //backup the current state of SciView before we eventually override it //so that there is something to return to with the "first toggle" - orig_fpsSlowSpeed = sciView.controlsParameters.getFpsSpeedSlow(); - orig_fpsFastSpeed = sciView.controlsParameters.getFpsSpeedFast(); - orig_fpsVeryFastSpeed = sciView.controlsParameters.getFpsSpeedVeryFast(); - orig_mouseMoveSensitivity = sciView.controlsParameters.getMouseSpeedMult(); - orig_mouseScrollSensitivity = sciView.controlsParameters.getMouseScrollMult(); + orig_fpsSlowSpeed = sciView.getControlsParameters().getFpsSpeedSlow(); + orig_fpsFastSpeed = sciView.getControlsParameters().getFpsSpeedFast(); + orig_fpsVeryFastSpeed = sciView.getControlsParameters().getFpsSpeedVeryFast(); + orig_mouseMoveSensitivity = sciView.getControlsParameters().getMouseSpeedMult(); + orig_mouseScrollSensitivity = sciView.getControlsParameters().getMouseScrollMult(); //try to retrieve stored dialog state and push it to SciView //so that the SciView and dialog states match @@ -120,11 +120,11 @@ private void setupBoundsFromSciView() //updates GUI with fresh values private void updateDialogSpeedsAndMouseParams() { - fpsSlowSpeed = sciView.controlsParameters.getFpsSpeedSlow(); - fpsFastSpeed = sciView.controlsParameters.getFpsSpeedFast(); - fpsVeryFastSpeed = sciView.controlsParameters.getFpsSpeedVeryFast(); - mouseMoveSensitivity = sciView.controlsParameters.getMouseSpeedMult(); - mouseScrollSensitivity = sciView.controlsParameters.getMouseScrollMult(); + fpsSlowSpeed = sciView.getControlsParameters().getFpsSpeedSlow(); + fpsFastSpeed = sciView.getControlsParameters().getFpsSpeedFast(); + fpsVeryFastSpeed = sciView.getControlsParameters().getFpsSpeedVeryFast(); + mouseMoveSensitivity = sciView.getControlsParameters().getMouseSpeedMult(); + mouseScrollSensitivity = sciView.getControlsParameters().getMouseScrollMult(); } diff --git a/src/main/java/sc/iview/commands/process/DrawLines.java b/src/main/java/sc/iview/commands/process/DrawLines.java index 2fbfd7f4..2a760d11 100644 --- a/src/main/java/sc/iview/commands/process/DrawLines.java +++ b/src/main/java/sc/iview/commands/process/DrawLines.java @@ -41,7 +41,6 @@ import sc.iview.SciView; import sc.iview.node.Line3D; import sc.iview.process.ControlPoints; -import sc.iview.vector.Vector3; import java.util.ArrayList; @@ -84,8 +83,8 @@ public void createLine() { //Line line = new Line(); ArrayList points = new ArrayList<>(); - for( Vector3 v : controlPoints.getVertices() ) { - points.add(new Vector3f(v.xf(), v.yf(), v.zf())); + for( Vector3f v : controlPoints.getVertices() ) { + points.add(new Vector3f(v.x(), v.y(), v.z())); } float r = 0.1f; diff --git a/src/main/java/sc/iview/commands/process/InteractiveConvexMesh.java b/src/main/java/sc/iview/commands/process/InteractiveConvexMesh.java index f7029431..408c6d9c 100644 --- a/src/main/java/sc/iview/commands/process/InteractiveConvexMesh.java +++ b/src/main/java/sc/iview/commands/process/InteractiveConvexMesh.java @@ -33,6 +33,7 @@ import net.imagej.mesh.naive.NaiveDoubleMesh; import net.imagej.ops.OpService; import net.imagej.ops.geom.geom3d.DefaultConvexHull3D; +import org.joml.Vector3f; import org.scijava.command.Command; import org.scijava.command.InteractiveCommand; import org.scijava.plugin.Menu; @@ -41,7 +42,6 @@ import org.scijava.widget.Button; import sc.iview.SciView; import sc.iview.process.ControlPoints; -import sc.iview.vector.Vector3; import java.util.List; @@ -83,8 +83,8 @@ public void run() { public void createMesh() { Mesh mesh = new NaiveDoubleMesh(); - for( Vector3 v : controlPoints.getVertices() ) { - mesh.vertices().add(v.xf(), v.yf(), v.zf()); + for( Vector3f v : controlPoints.getVertices() ) { + mesh.vertices().add(v.x(), v.y(), v.z()); } final List result = (List) opService.run(DefaultConvexHull3D.class, mesh ); diff --git a/src/main/java/sc/iview/commands/view/CenterOnActiveNode.java b/src/main/java/sc/iview/commands/view/CenterOnActiveNode.java deleted file mode 100644 index 5924164e..00000000 --- a/src/main/java/sc/iview/commands/view/CenterOnActiveNode.java +++ /dev/null @@ -1,71 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.commands.view; - -import graphics.scenery.Mesh; -import graphics.scenery.Node; -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_CENTER_ON_ACTIVE_NODE; - -/** - * Command to center the camera on the currently active Node - * - * @author Kyle Harrington - * - */ -@Plugin(type = Command.class, menuRoot = "SciView", // -menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Center On Active Node", weight = VIEW_CENTER_ON_ACTIVE_NODE)}) -public class CenterOnActiveNode implements Command { - - @Parameter - private LogService logService; - - @Parameter - private SciView sciView; - - @Override - public void run() { - if( sciView.getActiveNode() instanceof Mesh ) { - Node currentNode = sciView.getActiveNode(); - - sciView.centerOnNode( currentNode ); - - } - - } - -} diff --git a/src/main/java/sc/iview/commands/view/CenterOnActiveNode.kt b/src/main/java/sc/iview/commands/view/CenterOnActiveNode.kt new file mode 100644 index 00000000..bc4fc3f9 --- /dev/null +++ b/src/main/java/sc/iview/commands/view/CenterOnActiveNode.kt @@ -0,0 +1,57 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands.view + +import graphics.scenery.Mesh +import org.scijava.command.Command +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_CENTER_ON_ACTIVE_NODE + +/** + * Command to center the camera on the currently active Node + * + * @author Kyle Harrington + */ +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Center On Active Node", weight = VIEW_CENTER_ON_ACTIVE_NODE)]) +class CenterOnActiveNode : Command { + @Parameter + private lateinit var sciView: SciView + + override fun run() { + if (sciView.activeNode is Mesh) { + val currentNode = sciView.activeNode + sciView.centerOnNode(currentNode) + } + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/DisplayVertices.java b/src/main/java/sc/iview/commands/view/DisplayVertices.kt similarity index 50% rename from src/main/java/sc/iview/commands/view/DisplayVertices.java rename to src/main/java/sc/iview/commands/view/DisplayVertices.kt index 9102ff6a..df2206a9 100644 --- a/src/main/java/sc/iview/commands/view/DisplayVertices.java +++ b/src/main/java/sc/iview/commands/view/DisplayVertices.kt @@ -26,72 +26,62 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import graphics.scenery.Mesh; -import net.imagej.mesh.Vertex; -import org.scijava.ItemIO; -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import org.scijava.table.*; -import sc.iview.SciView; -import sc.iview.process.MeshConverter; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_SET_TRANSFER_FUNCTION; +import graphics.scenery.Mesh +import org.scijava.ItemIO +import org.scijava.command.Command +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import org.scijava.table.DefaultGenericTable +import org.scijava.table.DoubleColumn +import org.scijava.table.GenericColumn +import org.scijava.table.Table +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_SET_TRANSFER_FUNCTION +import sc.iview.process.MeshConverter /** * Command to display the vertices of the currently active Node as a table. * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Display Vertices", weight = VIEW_SET_TRANSFER_FUNCTION)}) -public class DisplayVertices implements Command { - - @Parameter - private LogService logService; - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Display Vertices", weight = VIEW_SET_TRANSFER_FUNCTION)]) +class DisplayVertices : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView // TODO: this should be the way to do this instead of using sciView.activeNode() -// @Parameter -// private Mesh mesh; + // @Parameter + // private Mesh mesh; @Parameter(type = ItemIO.OUTPUT) - private Table table; - - @Override - public void run() { - if( sciView.getActiveNode() instanceof Mesh ) { - Mesh scMesh = (Mesh) sciView.getActiveNode(); - net.imagej.mesh.Mesh mesh = MeshConverter.toImageJ(scMesh); + private lateinit var table: Table<*, *> - table = new DefaultGenericTable(); + override fun run() { + if (sciView.activeNode is Mesh) { + val scMesh = sciView.activeNode as Mesh + val mesh = MeshConverter.toImageJ(scMesh) + table = DefaultGenericTable() // we create two columns - GenericColumn idColumn = new GenericColumn("ID"); - DoubleColumn xColumn = new DoubleColumn("X"); - DoubleColumn yColumn = new DoubleColumn("Y"); - DoubleColumn zColumn = new DoubleColumn("Z"); - - for (Vertex v : mesh.vertices()) { - idColumn.add(v.index()); - xColumn.add(v.x()); - yColumn.add(v.y()); - zColumn.add(v.z()); + val idColumn = GenericColumn("ID") + val xColumn = DoubleColumn("X") + val yColumn = DoubleColumn("Y") + val zColumn = DoubleColumn("Z") + for (v in mesh.vertices()) { + idColumn.add(v.index()) + xColumn.add(v.x()) + yColumn.add(v.y()) + zColumn.add(v.z()) } - - table.add(idColumn); - table.add(xColumn); - table.add(yColumn); - table.add(zColumn); + (table as DefaultGenericTable).add(idColumn) + (table as DefaultGenericTable).add(xColumn) + (table as DefaultGenericTable).add(yColumn) + (table as DefaultGenericTable).add(zColumn) } } -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/RenderToOpenVRHMD.java b/src/main/java/sc/iview/commands/view/RenderToOpenVRHMD.kt similarity index 70% rename from src/main/java/sc/iview/commands/view/RenderToOpenVRHMD.java rename to src/main/java/sc/iview/commands/view/RenderToOpenVRHMD.kt index fe251778..58270614 100644 --- a/src/main/java/sc/iview/commands/view/RenderToOpenVRHMD.java +++ b/src/main/java/sc/iview/commands/view/RenderToOpenVRHMD.kt @@ -26,32 +26,27 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import org.scijava.command.Command; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; - -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.*; +import org.scijava.command.Command +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_RENDER_TO_OPENVR /** * Activates rendering to an OpenVR headset * * @author Ulrik Guenther */ -@Plugin(type = Command.class, initializer = "initValues", menuRoot = "SciView", selectable = true, - menu = { @Menu(label = "View", weight = VIEW), - @Menu(label = "Render to OpenVR Headset", weight = VIEW_RENDER_TO_OPENVR) }) -public class RenderToOpenVRHMD implements Command { - +@Plugin(type = Command::class, initializer = "initValues", menuRoot = "SciView", selectable = true, menu = [Menu(label = "View", weight = VIEW), Menu(label = "Render to OpenVR Headset", weight = VIEW_RENDER_TO_OPENVR)]) +class RenderToOpenVRHMD : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView - @Override - public void run() { - sciView.toggleVRRendering(); + override fun run() { + sciView.toggleVRRendering() } -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/ResetCameraPosition.java b/src/main/java/sc/iview/commands/view/ResetCameraPosition.kt similarity index 66% rename from src/main/java/sc/iview/commands/view/ResetCameraPosition.java rename to src/main/java/sc/iview/commands/view/ResetCameraPosition.kt index f710cbb1..4ff62b25 100644 --- a/src/main/java/sc/iview/commands/view/ResetCameraPosition.java +++ b/src/main/java/sc/iview/commands/view/ResetCameraPosition.kt @@ -26,39 +26,32 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import org.joml.Vector3f; -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_RESET_CAMERA_POSITION; +import org.joml.Vector3f +import org.scijava.command.Command +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_RESET_CAMERA_POSITION /** * Command to set the camera position to the default position * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // -menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Reset Camera Position", weight = VIEW_RESET_CAMERA_POSITION)}) -public class ResetCameraPosition implements Command { - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Reset Camera Position", weight = VIEW_RESET_CAMERA_POSITION)]) +class ResetCameraPosition : Command { @Parameter - private LogService logService; + private lateinit var logService: LogService @Parameter - private SciView sciView; + private lateinit var sciView: SciView - @Override - public void run() { - sciView.getCamera().setPosition( new Vector3f(0,5,5) ); + override fun run() { + sciView.camera?.position = Vector3f(0.0f, 1.65f, 5f) } - -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/ResetCameraRotation.java b/src/main/java/sc/iview/commands/view/ResetCameraRotation.kt similarity index 66% rename from src/main/java/sc/iview/commands/view/ResetCameraRotation.java rename to src/main/java/sc/iview/commands/view/ResetCameraRotation.kt index 88782bfb..d89faae4 100644 --- a/src/main/java/sc/iview/commands/view/ResetCameraRotation.java +++ b/src/main/java/sc/iview/commands/view/ResetCameraRotation.kt @@ -26,39 +26,32 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import org.joml.Quaternionf; -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_RESET_CAMERA_ROTATION; +import org.joml.Quaternionf +import org.scijava.command.Command +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_RESET_CAMERA_ROTATION /** * Command to set the camera rotation to the default orientation. * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // -menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Reset Camera Rotation", weight = VIEW_RESET_CAMERA_ROTATION)}) -public class ResetCameraRotation implements Command { - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Reset Camera Rotation", weight = VIEW_RESET_CAMERA_ROTATION)]) +class ResetCameraRotation : Command { @Parameter - private LogService logService; + private lateinit var logService: LogService @Parameter - private SciView sciView; + private lateinit var sciView: SciView - @Override - public void run() { - sciView.getCamera().setRotation( new Quaternionf(0, 0, 0, 1) ); + override fun run() { + sciView.camera?.rotation = Quaternionf(0f, 0f, 0f, 1f) } - -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/RotateView.java b/src/main/java/sc/iview/commands/view/RotateView.kt similarity index 63% rename from src/main/java/sc/iview/commands/view/RotateView.java rename to src/main/java/sc/iview/commands/view/RotateView.kt index 922c071b..9335b24d 100644 --- a/src/main/java/sc/iview/commands/view/RotateView.java +++ b/src/main/java/sc/iview/commands/view/RotateView.kt @@ -26,45 +26,37 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_ROTATE; - -import org.scijava.command.Command; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; - -import sc.iview.SciView; +import org.scijava.command.Command +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_ROTATE /** * Command to circle the camera around the currently active Node * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = { @Menu(label = "View", weight = VIEW), // - @Menu(label = "Circle camera around current object", weight = VIEW_ROTATE) }) -public class RotateView implements Command { - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Circle camera around current object", weight = VIEW_ROTATE)]) +class RotateView : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView @Parameter - private int xSpeed = 3; + private var xSpeed = 3 @Parameter - private int ySpeed = 0; - - @Override - public void run() { - sciView.animate( 30, () -> { - sciView.getTargetArcball().init( 1, 1 ); - sciView.getTargetArcball().drag( 1+xSpeed, 1+ySpeed ); - sciView.getTargetArcball().end( 1+xSpeed, 1+ySpeed ); - } ); + private var ySpeed = 0 + + override fun run() { + sciView.animate(30) { + sciView.targetArcball.init(1, 1) + sciView.targetArcball.drag(1 + xSpeed, 1 + ySpeed) + sciView.targetArcball.end(1 + xSpeed, 1 + ySpeed) + } } - -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/SaveCameraConfiguration.java b/src/main/java/sc/iview/commands/view/SaveCameraConfiguration.java deleted file mode 100644 index b5115335..00000000 --- a/src/main/java/sc/iview/commands/view/SaveCameraConfiguration.java +++ /dev/null @@ -1,98 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.commands.view; - -import com.google.common.io.Files; -import org.joml.Quaternionf; -import org.joml.Vector3f; -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import org.scijava.script.ScriptService; -import sc.iview.SciView; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_SAVE_CAMERA_CONFIGURATION; - -/** - * Save the current camera configuration to file. - * - * @author Kyle Harrington - * - */ -@Plugin(type = Command.class, menuRoot = "SciView", // -menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Save Camera Configuration", weight = VIEW_SAVE_CAMERA_CONFIGURATION)}) -public class SaveCameraConfiguration implements Command { - - @Parameter - private LogService logService; - - @Parameter - private SciView sciView; - - @Parameter - private File saveFile; - - @Parameter - private ScriptService scriptService; - - @Override - public void run() { - try { - FileWriter fw = new FileWriter(saveFile); - BufferedWriter bw = new BufferedWriter(fw); - - if( !Files.getFileExtension(saveFile.getAbsolutePath()).equalsIgnoreCase("clj") ) - throw new IOException("File must be Clojure (extension = .clj)"); - - Vector3f pos = sciView.getCamera().getPosition(); - Quaternionf rot = sciView.getCamera().getRotation(); - - String scriptContents = "; @SciView sciView\n\n"; - scriptContents += "(.setPosition (.getCamera sciView) (cleargl.GLVector. (float-array [" + pos.x() + " " + pos.y() + " " + pos.z() + "])))\n"; - scriptContents += "(.setRotation (.getCamera sciView) (com.jogamp.opengl.math.Quaternion. " + rot.x() + " " + rot.y() + " " + rot.z() + " " + rot.w() + "))\n"; - - bw.write(scriptContents); - - bw.close(); - fw.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/src/main/java/sc/iview/commands/view/SaveCameraConfiguration.kt b/src/main/java/sc/iview/commands/view/SaveCameraConfiguration.kt new file mode 100644 index 00000000..4d726ea8 --- /dev/null +++ b/src/main/java/sc/iview/commands/view/SaveCameraConfiguration.kt @@ -0,0 +1,81 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands.view + +import com.google.common.io.Files +import org.scijava.command.Command +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import org.scijava.script.ScriptService +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_SAVE_CAMERA_CONFIGURATION +import java.io.BufferedWriter +import java.io.File +import java.io.FileWriter +import java.io.IOException + +/** + * Save the current camera configuration to file. + * + * @author Kyle Harrington + */ +@Suppress("UnstableApiUsage") +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Save Camera Configuration", weight = VIEW_SAVE_CAMERA_CONFIGURATION)]) +class SaveCameraConfiguration : Command { + @Parameter + private lateinit var sciView: SciView + + @Parameter + private lateinit var saveFile: File + + override fun run() { + try { + val cam = sciView.camera ?: return + + val fw = FileWriter(saveFile) + val bw = BufferedWriter(fw) + if (!Files.getFileExtension(saveFile.absolutePath).equals("clj", ignoreCase = true)) throw IOException("File must be Clojure (extension = .clj)") + val pos = cam.position + val rot = cam.rotation + var scriptContents = "; @SciView sciView\n\n" + scriptContents += """(.setPosition (.getCamera sciView) (cleargl.GLVector. (float-array [${pos.x()} ${pos.y()} ${pos.z()}]))) +""" + scriptContents += """(.setRotation (.getCamera sciView) (com.jogamp.opengl.math.Quaternion. ${rot.x()} ${rot.y()} ${rot.z()} ${rot.w()})) +""" + bw.write(scriptContents) + bw.close() + fw.close() + } catch (e: IOException) { + e.printStackTrace() + } + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/Screenshot.java b/src/main/java/sc/iview/commands/view/Screenshot.java deleted file mode 100644 index ac7ad65a..00000000 --- a/src/main/java/sc/iview/commands/view/Screenshot.java +++ /dev/null @@ -1,91 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.commands.view; - -import graphics.scenery.backends.RenderedImage; -import net.imagej.Dataset; -import net.imagej.ImgPlus; -import net.imagej.ops.OpService; -import net.imglib2.RandomAccessibleInterval; -import net.imglib2.img.Img; -import net.imglib2.img.display.imagej.ImageJFunctions; -import net.imglib2.type.numeric.ARGBType; -import org.scijava.ItemIO; -import org.scijava.command.Command; -import org.scijava.io.IOService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import org.scijava.ui.UIService; -import sc.iview.SciView; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; -import java.io.File; -import java.io.IOException; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_SCREENSHOT; - -/** - * Command to take a screenshot. The screenshot is opened in ImageJ. - * - * @author Kyle Harrington - * - */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = { @Menu(label = "View", weight = VIEW), // - @Menu(label = "Screenshot", weight = VIEW_SCREENSHOT) }) -public class Screenshot implements Command { - - @Parameter - private SciView sciView; - - @Parameter - private OpService opService; - - @Parameter - private IOService ioService; - - @Parameter - private UIService uiService; - - @Parameter(type = ItemIO.OUTPUT) - private ImgPlus img; - - @Override - public void run() { - Img screenshot = sciView.getARGBScreenshot(); - - img = new ImgPlus<>(screenshot); - - uiService.show(img); - } -} diff --git a/src/main/java/sc/iview/commands/view/Screenshot.kt b/src/main/java/sc/iview/commands/view/Screenshot.kt new file mode 100644 index 00000000..4b598f92 --- /dev/null +++ b/src/main/java/sc/iview/commands/view/Screenshot.kt @@ -0,0 +1,65 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands.view + +import net.imagej.ImgPlus +import net.imagej.ops.OpService +import org.scijava.ItemIO +import org.scijava.command.Command +import org.scijava.io.IOService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import org.scijava.ui.UIService +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_SCREENSHOT + +/** + * Command to take a screenshot. The screenshot is opened in ImageJ. + * + * @author Kyle Harrington + */ +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Screenshot", weight = VIEW_SCREENSHOT)]) +class Screenshot : Command { + @Parameter + private lateinit var sciView: SciView + + @Parameter + private lateinit var uiService: UIService + + @Parameter(type = ItemIO.OUTPUT) + private var img: ImgPlus<*>? = null + + override fun run() { + val screenshot = sciView.aRGBScreenshot + img = ImgPlus(screenshot) + uiService.show(img) + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/SetFarPlane.java b/src/main/java/sc/iview/commands/view/SetFarPlane.kt similarity index 68% rename from src/main/java/sc/iview/commands/view/SetFarPlane.java rename to src/main/java/sc/iview/commands/view/SetFarPlane.kt index 78ecf8f2..24c2b8ed 100644 --- a/src/main/java/sc/iview/commands/view/SetFarPlane.java +++ b/src/main/java/sc/iview/commands/view/SetFarPlane.kt @@ -26,42 +26,31 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.*; +import org.scijava.command.Command +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_SET_FAR_PLANE /** * Command to set the far plane for the renderer. Everything beyond this **will not** be rendered * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Set Far Plane", weight = VIEW_SET_FAR_PLANE)}) -public class SetFarPlane implements Command { - - @Parameter - private LogService logService; - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Set Far Plane", weight = VIEW_SET_FAR_PLANE)]) +class SetFarPlane : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView @Parameter - private float farPlane = 1000f; - - @Override - public void run() { - - sciView.getCamera().setFarPlaneDistance(farPlane); + private var farPlane = 1000f + override fun run() { + sciView.camera?.farPlaneDistance = farPlane } - -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/SetLUT.java b/src/main/java/sc/iview/commands/view/SetLUT.java deleted file mode 100644 index 954cae95..00000000 --- a/src/main/java/sc/iview/commands/view/SetLUT.java +++ /dev/null @@ -1,99 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.commands.view; - -import graphics.scenery.Node; -import net.imagej.lut.LUTService; -import net.imglib2.display.AbstractArrayColorTable; -import net.imglib2.display.ColorTable; -import org.scijava.command.Command; -import org.scijava.command.DynamicCommand; -import org.scijava.module.MutableModuleItem; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import java.io.IOException; -import java.util.ArrayList; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_SET_LUT; - -/** - * Command to set the currently used Look Up Table (LUT). This is a colormap for the volume. - * - * @author Kyle Harrington - * - */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = { @Menu(label = "View", weight = VIEW), // - @Menu(label = "Set LUT", weight = VIEW_SET_LUT) }) -public class SetLUT extends DynamicCommand { - - @Parameter - private SciView sciView; - - @Parameter - private LUTService lutService; - - @Parameter(label = "Node") - private Node node; - - @Parameter(label = "Selected LUT", choices = {}, callback = "lutNameChanged") - private String lutName; - - @Parameter(label = "LUT Selection") - private ColorTable colorTable; - - protected void lutNameChanged() { - final MutableModuleItem lutNameItem = getInfo().getMutableInput("lutName", String.class); - try { - colorTable = lutService.loadLUT( lutService.findLUTs().get( lutName ) ); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void initialize() { - try { - colorTable = lutService.loadLUT( lutService.findLUTs().get( "Red.lut" ) ); - } catch (IOException e) { - e.printStackTrace(); - } - final MutableModuleItem lutNameItem = getInfo().getMutableInput("lutName", String.class ); - lutNameItem.setChoices( new ArrayList( lutService.findLUTs().keySet() ) ); - } - - @Override - public void run() { - sciView.setColormap( node, (AbstractArrayColorTable) colorTable); - } -} diff --git a/src/main/java/sc/iview/commands/view/SetLUT.kt b/src/main/java/sc/iview/commands/view/SetLUT.kt new file mode 100644 index 00000000..9a8bc975 --- /dev/null +++ b/src/main/java/sc/iview/commands/view/SetLUT.kt @@ -0,0 +1,91 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2020 SciView developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package sc.iview.commands.view + +import graphics.scenery.Node +import net.imagej.lut.LUTService +import net.imglib2.display.AbstractArrayColorTable +import net.imglib2.display.ColorTable +import org.scijava.command.Command +import org.scijava.command.DynamicCommand +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_SET_LUT +import java.io.IOException +import java.util.* + +/** + * Command to set the currently used Look Up Table (LUT). This is a colormap for the volume. + * + * @author Kyle Harrington + */ +@Suppress("TYPE_INFERENCE_ONLY_INPUT_TYPES_WARNING") +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Set LUT", weight = VIEW_SET_LUT)]) +class SetLUT : DynamicCommand() { + @Parameter + private lateinit var sciView: SciView + + @Parameter + private lateinit var lutService: LUTService + + @Parameter(label = "Node") + private lateinit var node: Node + + @Parameter(label = "Selected LUT", choices = [], callback = "lutNameChanged") + private lateinit var lutName: String + + @Parameter(label = "LUT Selection") + private lateinit var colorTable: ColorTable + + protected fun lutNameChanged() { + val lutNameItem = info.getMutableInput("lutName", String::class.java) + try { + colorTable = lutService.loadLUT(lutService.findLUTs()[lutNameItem]) + } catch (e: IOException) { + e.printStackTrace() + } + } + + override fun initialize() { + try { + colorTable = lutService.loadLUT(lutService.findLUTs()["Red.lut"]) + } catch (e: IOException) { + e.printStackTrace() + } + val lutNameItem = info.getMutableInput("lutName", String::class.java) + lutNameItem.choices = ArrayList(lutService.findLUTs().keys) + } + + override fun run() { + sciView.setColormap(node, colorTable) + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/SetSupersamplingFactor.java b/src/main/java/sc/iview/commands/view/SetSupersamplingFactor.kt similarity index 65% rename from src/main/java/sc/iview/commands/view/SetSupersamplingFactor.java rename to src/main/java/sc/iview/commands/view/SetSupersamplingFactor.kt index ff95132a..b9699f75 100644 --- a/src/main/java/sc/iview/commands/view/SetSupersamplingFactor.java +++ b/src/main/java/sc/iview/commands/view/SetSupersamplingFactor.kt @@ -26,42 +26,31 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.*; +import org.scijava.command.Command +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_SET_SUPERSAMPLING_FACTOR /** * Command to set scenery's Supersampling Factor * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Set Supersampling Factor", weight = VIEW_SET_SUPERSAMPLING_FACTOR)}) -public class SetSupersamplingFactor implements Command { - - @Parameter - private LogService logService; - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Set Supersampling Factor", weight = VIEW_SET_SUPERSAMPLING_FACTOR)]) +class SetSupersamplingFactor : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView @Parameter - private double supersamplingFactor = 1.0; - - @Override - public void run() { - - sciView.getSceneryRenderer().getSettings().set("Renderer.SupersamplingFactor",supersamplingFactor); + private var supersamplingFactor = 1.0 + override fun run() { + sciView.getSceneryRenderer()?.settings?.set("Renderer.SupersamplingFactor", supersamplingFactor) } - -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/SetTransferFunction.java b/src/main/java/sc/iview/commands/view/SetTransferFunction.kt similarity index 57% rename from src/main/java/sc/iview/commands/view/SetTransferFunction.java rename to src/main/java/sc/iview/commands/view/SetTransferFunction.kt index 61a9ef6d..db8409ea 100644 --- a/src/main/java/sc/iview/commands/view/SetTransferFunction.java +++ b/src/main/java/sc/iview/commands/view/SetTransferFunction.kt @@ -26,74 +26,53 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import graphics.scenery.volumes.TransferFunction; -import graphics.scenery.volumes.Volume; -import org.scijava.command.Command; -import org.scijava.command.InteractiveCommand; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import org.scijava.widget.NumberWidget; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_SET_TRANSFER_FUNCTION; +import graphics.scenery.volumes.Volume +import org.scijava.command.Command +import org.scijava.command.InteractiveCommand +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import org.scijava.widget.NumberWidget +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_SET_TRANSFER_FUNCTION /** * Command to set the transfer function of a Volume * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Set Transfer Function", weight = VIEW_SET_TRANSFER_FUNCTION)}) -public class SetTransferFunction extends InteractiveCommand { - - @Parameter - private LogService logService; - - @Parameter - private SciView sciView; - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Set Transfer Function", weight = VIEW_SET_TRANSFER_FUNCTION)]) +class SetTransferFunction : InteractiveCommand() { @Parameter(label = "Target Volume") - private Volume volume; + private lateinit var volume: Volume - @Parameter(label = "TF Ramp Min", style = NumberWidget.SLIDER_STYLE, // - min = "0", max = "1.0", stepSize = "0.001", callback = "updateTransferFunction") - private float rampMin = 0; + @Parameter(label = "TF Ramp Min", style = NumberWidget.SLIDER_STYLE, min = "0", max = "1.0", stepSize = "0.001", callback = "updateTransferFunction") + private var rampMin = 0f - @Parameter(label = "TF Ramp Max", style = NumberWidget.SLIDER_STYLE, // - min = "0", max = "1.0", stepSize = "0.001", callback = "updateTransferFunction") - private float rampMax = 1.0f; + @Parameter(label = "TF Ramp Max", style = NumberWidget.SLIDER_STYLE, min = "0", max = "1.0", stepSize = "0.001", callback = "updateTransferFunction") + private var rampMax = 1.0f /** * Nothing happens here, as cancelling the dialog is not possible. */ - @Override - public void cancel() { - - } + override fun cancel() {} /** * Nothing is done here, as the refreshing of the objects properties works via * callback methods. */ - @Override - public void run() { - - } - - protected void updateTransferFunction() { - TransferFunction tf = volume.getTransferFunction(); + override fun run() {} + protected fun updateTransferFunction() { + val tf = volume.transferFunction //float currentOffset = tf.getControlPoint$scenery(1).getValue(); //float currentFactor = tf.getControlPoint$scenery(2).getFactor(); - tf.clear(); - tf.addControlPoint(0.0f, 0.0f); - tf.addControlPoint(rampMin, 0.0f); - tf.addControlPoint(1.0f, rampMax); + tf.clear() + tf.addControlPoint(0.0f, 0.0f) + tf.addControlPoint(rampMin, 0.0f) + tf.addControlPoint(1.0f, rampMax) } -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/StartRecordingVideo.java b/src/main/java/sc/iview/commands/view/StartRecordingVideo.kt similarity index 60% rename from src/main/java/sc/iview/commands/view/StartRecordingVideo.java rename to src/main/java/sc/iview/commands/view/StartRecordingVideo.kt index f76bac48..64d935c2 100644 --- a/src/main/java/sc/iview/commands/view/StartRecordingVideo.java +++ b/src/main/java/sc/iview/commands/view/StartRecordingVideo.kt @@ -26,40 +26,38 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import org.scijava.command.Command; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.*; +import org.scijava.command.Command +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_START_RECORDING_VIDEO +import kotlin.math.max /** * Command to start recording a video. Currently this will record to ~/Desktop * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = { @Menu(label = "View", weight = VIEW), // - @Menu(label = "Start recording video", weight = VIEW_START_RECORDING_VIDEO) }) -public class StartRecordingVideo implements Command { +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Start recording video", weight = VIEW_START_RECORDING_VIDEO)]) +class StartRecordingVideo : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView @Parameter - private int bitrate = 10000000;// 10 MBit + private var bitrate = 10000000 // 10 MBit - @Parameter(choices={"VeryLow", "Low", "Medium", "High", "Ultra", "Insane"}, style="listBox") - private String videoEncodingQuality;// listed as an enum here, cant access from java https://github.com/scenerygraphics/scenery/blob/1a451c2864e5a48e47622d9313fe1681e47d7958/src/main/kotlin/graphics/scenery/utils/H264Encoder.kt#L65 + @Parameter(choices = ["VeryLow", "Low", "Medium", "High", "Ultra", "Insane"], style = "listBox") + private lateinit var videoEncodingQuality // listed as an enum here, cant access from java https://github.com/scenerygraphics/scenery/blob/1a451c2864e5a48e47622d9313fe1681e47d7958/src/main/kotlin/graphics/scenery/utils/H264Encoder.kt#L65 + : String - @Override - public void run() { - bitrate = Math.max(0,bitrate); - sciView.getScenerySettings().set("VideoEncoder.Bitrate", bitrate); - sciView.getScenerySettings().set("VideoEncoder.Quality", videoEncodingQuality); - sciView.toggleRecordVideo(); + override fun run() { + bitrate = max(0, bitrate) + sciView.getScenerySettings().set("VideoEncoder.Bitrate", bitrate) + sciView.getScenerySettings().set("VideoEncoder.Quality", videoEncodingQuality) + sciView.toggleRecordVideo() } -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/StopAnimation.java b/src/main/java/sc/iview/commands/view/StopAnimation.kt similarity index 70% rename from src/main/java/sc/iview/commands/view/StopAnimation.java rename to src/main/java/sc/iview/commands/view/StopAnimation.kt index 38899f2e..cd0b1307 100644 --- a/src/main/java/sc/iview/commands/view/StopAnimation.java +++ b/src/main/java/sc/iview/commands/view/StopAnimation.kt @@ -26,34 +26,27 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_STOP_ANIMATION; - -import org.scijava.command.Command; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; - -import sc.iview.SciView; +import org.scijava.command.Command +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_STOP_ANIMATION /** * Command to stop all current animations * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = { @Menu(label = "View", weight = VIEW), // - @Menu(label = "Stop Animation", weight = VIEW_STOP_ANIMATION) }) -public class StopAnimation implements Command { - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Stop Animation", weight = VIEW_STOP_ANIMATION)]) +class StopAnimation : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView - @Override - public void run() { - sciView.stopAnimation(); + override fun run() { + sciView.stopAnimation() } -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/StopRecordingVideo.java b/src/main/java/sc/iview/commands/view/StopRecordingVideo.kt similarity index 69% rename from src/main/java/sc/iview/commands/view/StopRecordingVideo.java rename to src/main/java/sc/iview/commands/view/StopRecordingVideo.kt index db9b2f88..b28ec6fa 100644 --- a/src/main/java/sc/iview/commands/view/StopRecordingVideo.java +++ b/src/main/java/sc/iview/commands/view/StopRecordingVideo.kt @@ -26,33 +26,27 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import org.scijava.command.Command; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_STOP_RECORDING_VIDEO; +import org.scijava.command.Command +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_STOP_RECORDING_VIDEO /** * Command to stop recording the current video * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = { @Menu(label = "View", weight = VIEW), // - @Menu(label = "Stop recording video", weight = VIEW_STOP_RECORDING_VIDEO) }) -public class StopRecordingVideo implements Command { - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Stop recording video", weight = VIEW_STOP_RECORDING_VIDEO)]) +class StopRecordingVideo : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView - @Override - public void run() { - sciView.toggleRecordVideo(); + override fun run() { + sciView.toggleRecordVideo() } -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/ToggleBoundingGrid.java b/src/main/java/sc/iview/commands/view/ToggleBoundingGrid.kt similarity index 56% rename from src/main/java/sc/iview/commands/view/ToggleBoundingGrid.java rename to src/main/java/sc/iview/commands/view/ToggleBoundingGrid.kt index eb5a9ee7..12553719 100644 --- a/src/main/java/sc/iview/commands/view/ToggleBoundingGrid.java +++ b/src/main/java/sc/iview/commands/view/ToggleBoundingGrid.kt @@ -26,57 +26,48 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import graphics.scenery.BoundingGrid; -import graphics.scenery.Mesh; -import graphics.scenery.Node; -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.*; +import graphics.scenery.BoundingGrid +import graphics.scenery.Mesh +import graphics.scenery.Node +import org.scijava.command.Command +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_TOGGLE_BOUNDING_GRID /** * Command to toggle the bounding grid around a Node * * @author Kyle Harrington - * */ -@Plugin(type = Command.class, menuRoot = "SciView", // -menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Toggle Bounding Grid", weight = VIEW_TOGGLE_BOUNDING_GRID)}) -public class ToggleBoundingGrid implements Command { - +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Toggle Bounding Grid", weight = VIEW_TOGGLE_BOUNDING_GRID)]) +class ToggleBoundingGrid : Command { @Parameter - private LogService logService; + private lateinit var logService: LogService @Parameter - private SciView sciView; + private lateinit var sciView: SciView @Parameter - private Node node; - - @Override - public void run() { - if( node instanceof Mesh ) { + private lateinit var node: Node - if( node.getMetadata().containsKey("BoundingGrid") ) { - BoundingGrid bg = (BoundingGrid) node.getMetadata().get("BoundingGrid"); - bg.setNode( null ); - node.getMetadata().remove("BoundingGrid"); - bg.getScene().removeChild(bg); + override fun run() { + if (node is Mesh) { + if (node.metadata.containsKey("BoundingGrid")) { + val bg = node.metadata["BoundingGrid"] as BoundingGrid? + bg!!.node = null + node.metadata.remove("BoundingGrid") + bg.getScene()!!.removeChild(bg) } else { - BoundingGrid bg = new BoundingGrid(); - bg.setNode(node); - - node.getMetadata().put("BoundingGrid", bg); + val bg = BoundingGrid() + bg.node = node + node.metadata["BoundingGrid"] = bg } } - } - -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/ToggleInspector.java b/src/main/java/sc/iview/commands/view/ToggleInspector.kt similarity index 68% rename from src/main/java/sc/iview/commands/view/ToggleInspector.java rename to src/main/java/sc/iview/commands/view/ToggleInspector.kt index c277ef3e..6e80b439 100644 --- a/src/main/java/sc/iview/commands/view/ToggleInspector.java +++ b/src/main/java/sc/iview/commands/view/ToggleInspector.kt @@ -26,32 +26,27 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ -package sc.iview.commands.view; +package sc.iview.commands.view -import org.scijava.command.Command; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; - -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.*; +import org.scijava.command.Command +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights.VIEW +import sc.iview.commands.MenuWeights.VIEW_TOGGLE_INSPECTOR /** - * Command that displays a {@link NodePropertyEditor} window. + * Command that displays a [NodePropertyEditor] window. * * @author Curtis Rueden */ -@Plugin(type = Command.class, initializer = "initValues", menuRoot = "SciView", // - menu = { @Menu(label = "View", weight = VIEW), // - @Menu(label = "Toggle Inspector", weight = VIEW_TOGGLE_INSPECTOR) }) -public class ToggleInspector implements Command { - +@Plugin(type = Command::class, initializer = "initValues", menuRoot = "SciView", menu = [Menu(label = "View", weight = VIEW), Menu(label = "Toggle Inspector", weight = VIEW_TOGGLE_INSPECTOR)]) +class ToggleInspector : Command { @Parameter - private SciView sciView; + private lateinit var sciView: SciView - @Override - public void run() { - sciView.toggleInspectorWindow(); + override fun run() { + sciView.toggleInspectorWindow() } -} +} \ No newline at end of file diff --git a/src/main/java/sc/iview/commands/view/ToggleUnlimitedFramerate.java b/src/main/java/sc/iview/commands/view/ToggleUnlimitedFramerate.java deleted file mode 100644 index d3399138..00000000 --- a/src/main/java/sc/iview/commands/view/ToggleUnlimitedFramerate.java +++ /dev/null @@ -1,64 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.commands.view; - -import org.scijava.command.Command; -import org.scijava.log.LogService; -import org.scijava.plugin.Menu; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import sc.iview.SciView; - -import static sc.iview.commands.MenuWeights.VIEW; -import static sc.iview.commands.MenuWeights.VIEW_TOGGLE_UNLIMITED_FRAMERATE; - -/** - * Command to toggle scenery's PushMode. If this is true the scene only renders when it is changed, otherwise it is - * continuously rendered - * - * @author Kyle Harrington - * - */ -@Plugin(type = Command.class, menuRoot = "SciView", // - menu = {@Menu(label = "View", weight = VIEW), // - @Menu(label = "Toggle Unlimited Framerate", weight = VIEW_TOGGLE_UNLIMITED_FRAMERATE)}) -public class ToggleUnlimitedFramerate implements Command { - - @Parameter - private LogService logService; - - @Parameter - private SciView sciView; - - @Override - public void run() { - sciView.setPushMode(!sciView.getPushMode()); - } - -} diff --git a/src/main/java/sc/iview/commands/view/ToggleUnlimitedFramerate.kt b/src/main/java/sc/iview/commands/view/ToggleUnlimitedFramerate.kt new file mode 100644 index 00000000..494aecf1 --- /dev/null +++ b/src/main/java/sc/iview/commands/view/ToggleUnlimitedFramerate.kt @@ -0,0 +1,24 @@ +package sc.iview.commands.view + +import org.scijava.command.Command +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights + +/** + * Command to toggle scenery's PushMode. If this is true the scene only renders when it is changed, otherwise it is + * continuously rendered + * + * @author Kyle Harrington + */ +@Plugin(type = Command::class, menuRoot = "SciView", menu = [Menu(label = "View", weight = MenuWeights.VIEW), Menu(label = "Toggle Unlimited Framerate", weight = MenuWeights.VIEW_TOGGLE_UNLIMITED_FRAMERATE)]) +class ToggleUnlimitedFramerate : Command { + @Parameter + private lateinit var sciView: SciView + + override fun run() { + sciView.setPushMode(!sciView.getPushMode()) + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/controls/behaviours/CameraTranslateControl.kt b/src/main/java/sc/iview/controls/behaviours/CameraTranslateControl.kt index a2c99e88..4416f4ef 100644 --- a/src/main/java/sc/iview/controls/behaviours/CameraTranslateControl.kt +++ b/src/main/java/sc/iview/controls/behaviours/CameraTranslateControl.kt @@ -8,6 +8,7 @@ import sc.iview.SciView * Behavior for translating a camera * * @author Kyle Harrington + * @author Vladimir Ulman */ class CameraTranslateControl(protected val sciView: SciView, var dragSpeed: Float) : DragBehaviour { private var lastX = 0 diff --git a/src/main/java/sc/iview/controls/behaviours/NodeRotateControl.kt b/src/main/java/sc/iview/controls/behaviours/NodeRotateControl.kt index b3bfad83..3e461d20 100644 --- a/src/main/java/sc/iview/controls/behaviours/NodeRotateControl.kt +++ b/src/main/java/sc/iview/controls/behaviours/NodeRotateControl.kt @@ -29,15 +29,16 @@ class NodeRotateControl(protected val sciView: SciView) : DragBehaviour { override fun drag(x: Int, y: Int) { val targetedNode = sciView.activeNode + val cam = sciView.camera ?: return if (targetedNode == null || !targetedNode.lock.tryLock()) return - val frameYaw = sciView.mouseSpeed * (x - lastX) * 0.0174533f // 0.017 = PI/180 - val framePitch = sciView.mouseSpeed * (y - lastY) * 0.0174533f + val frameYaw = sciView.getMouseSpeed() * (x - lastX) * 0.0174533f // 0.017 = PI/180 + val framePitch = sciView.getMouseSpeed() * (y - lastY) * 0.0174533f - Quaternionf().rotateAxis(frameYaw, sciView.camera.up) + Quaternionf().rotateAxis(frameYaw, cam.up) .mul(targetedNode.rotation, targetedNode.rotation) .normalize() - Quaternionf().rotateAxis(framePitch, sciView.camera.right) + Quaternionf().rotateAxis(framePitch, cam.right) .mul(targetedNode.rotation, targetedNode.rotation) .normalize() targetedNode.needsUpdate = true diff --git a/src/main/java/sc/iview/controls/behaviours/NodeTranslateControl.kt b/src/main/java/sc/iview/controls/behaviours/NodeTranslateControl.kt index 2ff65c46..56f28679 100644 --- a/src/main/java/sc/iview/controls/behaviours/NodeTranslateControl.kt +++ b/src/main/java/sc/iview/controls/behaviours/NodeTranslateControl.kt @@ -58,11 +58,12 @@ class NodeTranslateControl(protected val sciView: SciView) : DragBehaviour, Scro override fun drag(x: Int, y: Int) { val targetedNode = sciView.activeNode; + val cam = sciView.camera ?: return if (targetedNode == null || !targetedNode.lock.tryLock()) return - sciView.camera.right.mul((x - lastX) * sciView.fpsSpeedSlow * sciView.mouseSpeed, dragPosUpdater ) + cam.right.mul((x - lastX) * sciView.getFPSSpeedSlow() * sciView.getMouseSpeed(), dragPosUpdater ) targetedNode.position.add( dragPosUpdater ) - sciView.camera.up.mul( (lastY - y) * sciView.fpsSpeedSlow * sciView.mouseSpeed, dragPosUpdater ) + cam.up.mul( (lastY - y) * sciView.getFPSSpeedSlow() * sciView.getMouseSpeed(), dragPosUpdater ) targetedNode.position.add( dragPosUpdater ) targetedNode.needsUpdate = true @@ -77,9 +78,10 @@ class NodeTranslateControl(protected val sciView: SciView) : DragBehaviour, Scro override fun scroll(wheelRotation: Double, isHorizontal: Boolean, x: Int, y: Int) { val targetedNode = sciView.activeNode; + val cam = sciView.camera ?: return if (targetedNode == null || !targetedNode.lock.tryLock()) return - sciView.camera.forward.mul( wheelRotation.toFloat() * sciView.fpsSpeedSlow * sciView.mouseScrollSpeed, scrollPosUpdater ) + cam.forward.mul( wheelRotation.toFloat() * sciView.getFPSSpeedSlow() * sciView.getMouseSpeed(), scrollPosUpdater ) targetedNode.position.add( scrollPosUpdater ); targetedNode.needsUpdate = true diff --git a/src/main/java/sc/iview/controls/behaviours/SceneRollControl.kt b/src/main/java/sc/iview/controls/behaviours/SceneRollControl.kt index a15d0145..446c81cb 100644 --- a/src/main/java/sc/iview/controls/behaviours/SceneRollControl.kt +++ b/src/main/java/sc/iview/controls/behaviours/SceneRollControl.kt @@ -15,7 +15,7 @@ class SceneRollControl(protected val sciView: SciView, protected val byFixedAngI private val rotQ_CCW: Quaternionf = Quaternionf().rotateAxis(-byFixedAngInRad, 0f, 0f, -1f); override fun click(x: Int, y: Int) { - val cam = sciView.camera + val cam = sciView.camera ?: return rotQ_CW.mul(cam.rotation, cam.rotation).normalize() } @@ -27,7 +27,7 @@ class SceneRollControl(protected val sciView: SciView, protected val byFixedAngI } override fun drag(x: Int, y: Int) { - val cam = sciView.camera + val cam = sciView.camera ?: return if (x > lastX + minMouseMovementDelta) rotQ_CW.mul(cam.rotation, cam.rotation).normalize() else if (x < lastX - minMouseMovementDelta) rotQ_CCW.mul(cam.rotation, cam.rotation).normalize() lastX = x diff --git a/src/main/java/sc/iview/node/Line3D.kt b/src/main/java/sc/iview/node/Line3D.kt index cef1dfad..2addae63 100644 --- a/src/main/java/sc/iview/node/Line3D.kt +++ b/src/main/java/sc/iview/node/Line3D.kt @@ -1,11 +1,10 @@ package sc.iview.node import graphics.scenery.* +import org.joml.Vector3f import org.scijava.util.ColorRGB import org.scijava.util.Colors import sc.iview.Utils -import sc.iview.vector.JOMLVector3 -import sc.iview.vector.Vector3 import java.util.ArrayList /** @@ -24,7 +23,7 @@ class Line3D : Node { edges = ArrayList() } - constructor(points: List, colorRGB: ColorRGB, edgeWidth: Double) { + constructor(points: List, colorRGB: ColorRGB, edgeWidth: Double) { defaultColor = colorRGB this.edgeWidth = edgeWidth edges = ArrayList() @@ -32,8 +31,8 @@ class Line3D : Node { for (k in points.indices) { if (k > 0) { val edge: Node = Cylinder.betweenPoints( - JOMLVector3.convert(points[k - 1]), - JOMLVector3.convert(points[k]), + points[k - 1], + points[k], edgeWidth.toFloat(), 1f, 15) @@ -41,14 +40,14 @@ class Line3D : Node { } if (sphereJoints) { val joint: Node = Sphere(edgeWidth.toFloat(), 15) - joint.position = JOMLVector3.convert(points[k]) + joint.position = points[k] joints!!.add(joint) addChild(joint) } } } - constructor(points: List, colors: List, edgeWidth: Double) { + constructor(points: List, colors: List, edgeWidth: Double) { this.edgeWidth = edgeWidth edges = ArrayList() if (sphereJoints) joints = ArrayList() @@ -60,8 +59,8 @@ class Line3D : Node { mat.specular = c if (k > 0) { val edge: Node = Cylinder.betweenPoints( - JOMLVector3.convert(points[k - 1]), - JOMLVector3.convert(points[k]), + points[k - 1], + points[k], edgeWidth.toFloat(), 1f, 15) @@ -71,7 +70,7 @@ class Line3D : Node { if (sphereJoints) { val joint: Node = Sphere(edgeWidth.toFloat(), 15) joint.material = mat - joint.position = JOMLVector3.convert(points[k]) + joint.position = points[k] joints!!.add(joint) addChild(joint) } diff --git a/src/main/java/sc/iview/process/ControlPoints.kt b/src/main/java/sc/iview/process/ControlPoints.kt index 654a109c..666a7bcd 100644 --- a/src/main/java/sc/iview/process/ControlPoints.kt +++ b/src/main/java/sc/iview/process/ControlPoints.kt @@ -3,14 +3,13 @@ package sc.iview.process import graphics.scenery.Material import graphics.scenery.Node import graphics.scenery.Sphere +import org.joml.Vector3f import org.scijava.ui.behaviour.Behaviour import org.scijava.ui.behaviour.ClickBehaviour import org.scijava.ui.behaviour.ScrollBehaviour import org.scijava.util.ColorRGB import sc.iview.SciView import sc.iview.Utils -import sc.iview.vector.JOMLVector3 -import sc.iview.vector.Vector3 import java.util.ArrayList /** @@ -20,27 +19,27 @@ import java.util.ArrayList */ class ControlPoints { protected var nodes: MutableList - private var targetPoint: Node? = null + private lateinit var targetPoint: Node private var controlPointDistance = 0f fun clearPoints() { nodes.clear() } - val vertices: List + val vertices: List get() { - val points: MutableList = ArrayList() + val points: MutableList = ArrayList() for (k in nodes.indices) { - points.add(JOMLVector3(nodes[k].position)) + points.add(Vector3f(nodes[k].position)) } return points } - fun setPoints(newPoints: Array) { + fun setPoints(newPoints: Array) { nodes.clear() nodes = ArrayList() for (k in newPoints.indices) { val cp = Sphere(DEFAULT_RADIUS, DEFAULT_SEGMENTS) - cp.position = JOMLVector3.convert(newPoints[k]) + cp.position = newPoints[k] nodes.add(cp) } } @@ -55,6 +54,7 @@ class ControlPoints { } fun initializeSciView(sciView: SciView, controlPointDistance: Float) { + val cam= sciView.camera ?: return // This is where the command should change the current inputs setup sciView.stashControls() sciView.sceneryInputHandler.addBehaviour("place_control_point", @@ -72,14 +72,14 @@ class ControlPoints { mat.ambient = Utils.convertToVector3f(TARGET_COLOR) mat.diffuse = Utils.convertToVector3f(TARGET_COLOR) (targetPoint as Sphere).material = mat - (targetPoint as Sphere).position = sciView.camera.position.add(sciView.camera.forward.mul(controlPointDistance)) + (targetPoint as Sphere).position = cam.position.add(cam.forward.mul(controlPointDistance)) sciView.addNode(targetPoint, false) //sciView.getCamera().addChild(targetPoint); (targetPoint as Sphere).update.add { //targetPoint.getRotation().set(sciView.getCamera().getRotation().conjugate().rotateByAngleY((float) Math.PI)); // Set rotation before setting position - (targetPoint as Sphere).position = sciView.camera.position.add(sciView.camera.forward.mul(controlPointDistance)) + (targetPoint as Sphere).position = cam.position.add(cam.forward.mul(controlPointDistance)) } } @@ -89,8 +89,9 @@ class ControlPoints { private fun distanceControlPointBehaviour(sciView: SciView): Behaviour { return ScrollBehaviour { wheelRotation, _, _, _ -> + val cam = sciView.camera!! controlPointDistance += wheelRotation.toFloat() - targetPoint!!.position = sciView.camera.position.add(sciView.camera.forward.mul(controlPointDistance)) + targetPoint.position = cam.position.add(cam.forward.mul(controlPointDistance)) } } @@ -102,7 +103,7 @@ class ControlPoints { controlPoint.material = mat //controlPoint.setPosition( sciView.getCamera().getTransformation().mult(targetPoint.getPosition().xyzw()) ); - controlPoint.position = targetPoint!!.position + controlPoint.position = targetPoint.position addPoint(controlPoint) sciView.addNode(controlPoint, false) } diff --git a/src/main/java/sc/iview/ui/ContextPopUpNodeChooser.kt b/src/main/java/sc/iview/ui/ContextPopUpNodeChooser.kt index 38819e58..2954dc4f 100644 --- a/src/main/java/sc/iview/ui/ContextPopUpNodeChooser.kt +++ b/src/main/java/sc/iview/ui/ContextPopUpNodeChooser.kt @@ -6,10 +6,9 @@ import javax.swing.JPopupMenu class ContextPopUpNodeChooser(sv: SciView) : JPopupMenu() { init { - for (m in sv.objectSelectionLastResult.matches) { - var n = m.node - add( JMenuItem(n.name) ) - .addActionListener { sv.activeNode = n } + sv.objectSelectionLastResult?.matches?.forEach { match -> + add( JMenuItem(match.node.name) ) + .addActionListener { sv.setActiveNode(match.node) } } } } \ No newline at end of file diff --git a/src/main/java/sc/iview/ui/ProgressPie.kt b/src/main/java/sc/iview/ui/ProgressPie.kt new file mode 100644 index 00000000..497225d7 --- /dev/null +++ b/src/main/java/sc/iview/ui/ProgressPie.kt @@ -0,0 +1,60 @@ +package sc.iview.ui + +import graphics.scenery.utils.LazyLogger +import java.awt.Color +import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.Rectangle +import javax.swing.JComponent +import kotlin.math.roundToInt + +class ProgressPie: JComponent() { + class Slice(var value: Double, var color: Color) + private val logger by LazyLogger() + + private var slices = arrayOf( + Slice(0.0, Color.WHITE), Slice(100.0, Color.LIGHT_GRAY) + ) + + init { + isVisible = true + } + + var value = 0.0 + set(value) { + slices[1].value = value + slices[0].value = 100.0 - value + field = value + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + drawPie(g, bounds, slices) + } + + fun drawPie(graphics: Graphics, area: Rectangle, slices: Array) { + val g = graphics.create() as Graphics2D + + var total = 0.0 + for (i in slices.indices) { + total += slices[i].value + } + var curValue = 0.0 + var startAngle: Int + for (i in slices.indices) { + // angles for fillArc start at East, so we move back 90 degrees to start at North + startAngle = (curValue * 360 / total).toInt() + 90 + val arcAngle = (slices[i].value * 360 / total).toInt() + g.color = slices[i].color + + val relativeSize = 0.75f + val size = (area.height * relativeSize).roundToInt() + val centerX = (size*(1.0f-relativeSize)/2.0f).roundToInt() + val centerY = (size*(1.0f-relativeSize)/2.0f).roundToInt() + g.fillArc(centerX, centerY, size, size, startAngle, arcAngle) + curValue += slices[i].value + } + + graphics.dispose() + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/ui/Task.kt b/src/main/java/sc/iview/ui/Task.kt new file mode 100644 index 00000000..bc98e3bb --- /dev/null +++ b/src/main/java/sc/iview/ui/Task.kt @@ -0,0 +1,3 @@ +package sc.iview.ui + +data class Task(val source: String, var status: String, var completion: Float = 0.0f) \ No newline at end of file diff --git a/src/main/java/sc/iview/ui/TaskManager.kt b/src/main/java/sc/iview/ui/TaskManager.kt new file mode 100644 index 00000000..bbdefe23 --- /dev/null +++ b/src/main/java/sc/iview/ui/TaskManager.kt @@ -0,0 +1,40 @@ +package sc.iview.ui + +import graphics.scenery.utils.LazyLogger +import java.util.* +import java.util.concurrent.CopyOnWriteArrayList +import javax.swing.JLabel + +class TaskManager(var update: ((Task?) -> Any)? = null) { + val currentTasks = CopyOnWriteArrayList() + val pie = ProgressPie() + val label = JLabel() + val logger by LazyLogger() + + init { + + val timerTask = object: TimerTask() { + override fun run() { + currentTasks.removeIf { it.completion > 99.9999f } + val current = currentTasks.lastOrNull() + + update?.invoke(current) + } + } + Timer().scheduleAtFixedRate(timerTask, 0L, 200L) + } + + fun addTask(task: Task) { + currentTasks.add(task) + } + + fun newTask(source: String, status: String = ""): Task { + val task = Task(source, status, 0.0f) + currentTasks.add(task) + return task + } + + fun removeTask(task: Task) { + currentTasks.remove(task) + } +} \ No newline at end of file diff --git a/src/main/java/sc/iview/vector/DoubleVector3.java b/src/main/java/sc/iview/vector/DoubleVector3.java deleted file mode 100644 index fcdf293f..00000000 --- a/src/main/java/sc/iview/vector/DoubleVector3.java +++ /dev/null @@ -1,72 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.vector; - -/** - * {@link Vector3} backed by three {@code double}s. - * - * @author Curtis Rueden - * @author Kyle Harrington - */ -public class DoubleVector3 implements Vector3 { - - private double x, y, z; - - public DoubleVector3( double x, double y, double z ) { - this.x = x; - this.y = y; - this.z = z; - } - - @Override public float xf() { return (float) x; } - @Override public float yf() { return (float) y; } - @Override public float zf() { return (float) z; } - - @Override public void setX( float position ) { x = position; } - @Override public void setY( float position ) { y = position; } - @Override public void setZ( float position ) { z = position; } - - @Override public double xd() { return x; } - @Override public double yd() { return y; } - @Override public double zd() { return z; } - - @Override public void setX( double position ) { x = position; } - @Override public void setY( double position ) { y = position; } - @Override public void setZ( double position ) { z = position; } - - @Override - public Vector3 copy() { - return new DoubleVector3(xd(),yd(),zd()); - } - - @Override - public String toString() { - return "[" + xd() + "; " + yd() + "; " + zd() + "]"; - } -} diff --git a/src/main/java/sc/iview/vector/DoubleVector4.java b/src/main/java/sc/iview/vector/DoubleVector4.java deleted file mode 100644 index b1c4ec75..00000000 --- a/src/main/java/sc/iview/vector/DoubleVector4.java +++ /dev/null @@ -1,76 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.vector; - -/** - * {@link Vector4} backed by three {@code double}s. - * - * @author Kyle Harrington - */ -public class DoubleVector4 implements Vector4 { - - private double x, y, z, w; - - public DoubleVector4(double x, double y, double z, double w ) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; - } - - @Override public float xf() { return (float) x; } - @Override public float yf() { return (float) y; } - @Override public float zf() { return (float) z; } - @Override public float wf() { return (float) w; } - - @Override public void setX( float position ) { x = position; } - @Override public void setY( float position ) { y = position; } - @Override public void setZ( float position ) { z = position; } - @Override public void setW( float position ) { w = position; } - - @Override public double xd() { return x; } - @Override public double yd() { return y; } - @Override public double zd() { return z; } - @Override public double wd() { return w; } - - @Override public void setX( double position ) { x = position; } - @Override public void setY( double position ) { y = position; } - @Override public void setZ( double position ) { z = position; } - @Override public void setW( double position ) { w = position; } - - @Override - public Vector4 copy() { - return new DoubleVector4(xd(),yd(),zd(),wd()); - } - - @Override - public String toString() { - return "[" + xd() + "; " + yd() + "; " + zd() + "; " + wd() + "]"; - } -} diff --git a/src/main/java/sc/iview/vector/FloatVector4.java b/src/main/java/sc/iview/vector/FloatVector4.java deleted file mode 100644 index 282bdd6d..00000000 --- a/src/main/java/sc/iview/vector/FloatVector4.java +++ /dev/null @@ -1,66 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.vector; - -/** - * {@link Vector4} backed by three {@code float}s. - * - * @author Kyle Harrington - */ -public class FloatVector4 implements Vector4 { - - private float x, y, z, w; - - public FloatVector4(float x, float y, float z, float w ) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; - } - - @Override public float xf() { return x; } - @Override public float yf() { return y; } - @Override public float zf() { return z; } - @Override public float wf() { return w; } - - @Override public void setX( float position ) { x = position; } - @Override public void setY( float position ) { y = position; } - @Override public void setZ( float position ) { z = position; } - @Override public void setW( float position ) { w = position; } - - @Override - public Vector4 copy() { - return new FloatVector4(xf(),yf(),zf(),wf()); - } - - @Override - public String toString() { - return "[" + xf() + "; " + yf() + "; " + zf() + "; " + wf() + "]"; - } -} diff --git a/src/main/java/sc/iview/vector/JOMLVector3.java b/src/main/java/sc/iview/vector/JOMLVector3.java deleted file mode 100644 index 855ca1fd..00000000 --- a/src/main/java/sc/iview/vector/JOMLVector3.java +++ /dev/null @@ -1,75 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.vector; - -import org.joml.Vector3f; - -/** - * {@link Vector3} backed by a JOML {@link Vector3f}. - * - * @author Kyle Harrington - * @author Curtis Rueden - */ -public class JOMLVector3 implements Vector3 { - - private Vector3f source; - - public JOMLVector3( float x, float y, float z ) { - this( new Vector3f( x, y, z ) ); - } - - public JOMLVector3( Vector3f source ) { - this.source = source; - } - - public Vector3f source() { return source; } - - @Override public float xf() { return source.x(); } - @Override public float yf() { return source.y(); } - @Override public float zf() { return source.z(); } - - @Override public void setX( float position ) { source.set( position, yf(), zf() ); } - @Override public void setY( float position ) { source.set( xf(), position, zf() ); } - @Override public void setZ( float position ) { source.set( xf(), yf(), position ); } - - @Override - public Vector3 copy() { - return new JOMLVector3(xf(),yf(),zf()); - } - - @Override - public String toString() { - return "[" + xf() + "; " + yf() + "; " + zf() + "]"; - } - - public static Vector3f convert( Vector3 v ) { - if( v instanceof JOMLVector3 ) return (( JOMLVector3 ) v).source(); - return new Vector3f( v.xf(), v.yf(), v.zf() ); - } -} diff --git a/src/main/java/sc/iview/vector/JOMLVector4.java b/src/main/java/sc/iview/vector/JOMLVector4.java deleted file mode 100644 index ed45f396..00000000 --- a/src/main/java/sc/iview/vector/JOMLVector4.java +++ /dev/null @@ -1,76 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.vector; - -import org.joml.Vector4f; - -/** - * {@link Vector4} backed by a JOML {@link Vector4f}. - * - * @author Kyle Harrington - */ -public class JOMLVector4 implements Vector4 { - - private Vector4f source; - - public JOMLVector4(float x, float y, float z, float w ) { - this( new Vector4f( x, y, z, w ) ); - } - - public JOMLVector4(Vector4f source ) { - this.source = source; - } - - public Vector4f source() { return source; } - - @Override public float xf() { return source.x(); } - @Override public float yf() { return source.y(); } - @Override public float zf() { return source.z(); } - @Override public float wf() { return source.w(); } - - @Override public void setX( float position ) { source.set( position, yf(), zf(), wf() ); } - @Override public void setY( float position ) { source.set( xf(), position, zf(), wf() ); } - @Override public void setZ( float position ) { source.set( xf(), yf(), position, wf() ); } - @Override public void setW( float position ) { source.set( xf(), yf(), zf(), position ); } - - @Override - public Vector4 copy() { - return new JOMLVector4(xf(),yf(),zf(),wf()); - } - - @Override - public String toString() { - return "[" + xf() + "; " + yf() + "; " + zf() + "; " + wf() + "]"; - } - - public static Vector4f convert( Vector4 v ) { - if( v instanceof JOMLVector4) return ((JOMLVector4) v).source(); - return new Vector4f( v.xf(), v.yf(), v.zf(), v.wf() ); - } -} diff --git a/src/main/java/sc/iview/vector/Vector3.java b/src/main/java/sc/iview/vector/Vector3.java deleted file mode 100644 index d65a0f83..00000000 --- a/src/main/java/sc/iview/vector/Vector3.java +++ /dev/null @@ -1,344 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.vector; - -import net.imglib2.Localizable; -import net.imglib2.RealLocalizable; -import net.imglib2.RealPositionable; - -/** - * Interface for 3D vectors. - * - * @author Kyle Harrington - * @author Curtis Rueden - */ -public interface Vector3 extends RealLocalizable, RealPositionable { - - // -- Vector3 methods -- - - float xf(); - float yf(); - float zf(); - - default void moveX(float distance) { setX( xf() + distance ); } - default void moveY(float distance) { setY( yf() + distance ); } - default void moveZ(float distance) { setZ( zf() + distance ); } - default void move( float xDist, float yDist, float zDist ) { - moveX( xDist ); - moveY( yDist ); - moveZ( zDist ); - } - - void setX(float position); - void setY(float position); - void setZ(float position); - - default void setPosition( float x, float y, float z ) { - setX( x ); - setY( y ); - setZ( z ); - } - - default double xd() { return xf(); } - default double yd() { return yf(); } - default double zd() { return zf(); } - - default void moveX(double distance) { setX( xd() + distance ); } - default void moveY(double distance) { setY( yd() + distance ); } - default void moveZ(double distance) { setZ( zd() + distance ); } - default void move( double xDist, double yDist, double zDist ) { - moveX( xDist ); - moveY( yDist ); - moveZ( zDist ); - } - - default void setX(double position) { setX((float) position); } - default void setY(double position) { setY((float) position); } - default void setZ(double position) { setZ((float) position); } - - default void setPosition( double x, double y, double z ) { - setX( x ); - setY( y ); - setZ( z ); - } - - // -- RealLocalizable methods -- - - @Override - default void localize( float[] position ) { - position[0] = xf(); - position[1] = yf(); - position[2] = zf(); - } - - @Override - default void localize( double[] position ) { - position[0] = xd(); - position[1] = yd(); - position[2] = zd(); - } - - @Override - default float getFloatPosition( int d ) { - if( d == 0 ) return xf(); - if( d == 1 ) return yf(); - if( d == 2 ) return zf(); - throw new IndexOutOfBoundsException( "" + d ); - } - - @Override - default double getDoublePosition( int d ) { - if( d == 0 ) return xd(); - if( d == 1 ) return yd(); - if( d == 2 ) return zd(); - throw new IndexOutOfBoundsException( "" + d ); - } - - // -- RealPositionable methods -- - - @Override - default void move( float distance, int d ) { - if( d == 0 ) moveX( distance ); - else if( d == 1 ) moveY( distance ); - else if( d == 2 ) moveZ( distance ); - else throw new IndexOutOfBoundsException( "" + d ); - } - - @Override - default void move( double distance, int d ) { - if( d == 0 ) moveX( distance ); - else if( d == 1 ) moveY( distance ); - else if( d == 2 ) moveZ( distance ); - else throw new IndexOutOfBoundsException( "" + d ); - } - - @Override - default void move( RealLocalizable distance ) { - moveX( distance.getDoublePosition( 0 ) ); - moveY( distance.getDoublePosition( 1 ) ); - moveZ( distance.getDoublePosition( 2 ) ); - } - - @Override - default void move( float[] distance ) { - moveX( distance[0] ); - moveY( distance[1] ); - moveZ( distance[2] ); - } - - @Override - default void move( double[] distance ) { - moveX( distance[0] ); - moveY( distance[1] ); - moveZ( distance[2] ); - } - - @Override - default void setPosition( RealLocalizable localizable ) { - setX( localizable.getDoublePosition( 0 ) ); - setY( localizable.getDoublePosition( 1 ) ); - setZ( localizable.getDoublePosition( 2 ) ); - } - - @Override - default void setPosition( float[] position ) { - setX( position[0] ); - setY( position[1] ); - setZ( position[2] ); - } - - @Override - default void setPosition( double[] position ) { - setX( position[0] ); - setY( position[1] ); - setZ( position[2] ); - } - - @Override - default void setPosition( float position, int d ) { - if( d == 0 ) setX( position ); - else if( d == 1 ) setY( position ); - else if( d == 2 ) setZ( position ); - else throw new IndexOutOfBoundsException( "" + d ); - } - - @Override - default void setPosition( double position, int d ) { - if( d == 0 ) setX( position ); - else if( d == 1 ) setY( position ); - else if( d == 2 ) setZ( position ); - else throw new IndexOutOfBoundsException( "" + d ); - } - - // -- Positionable methods -- - - @Override - default void fwd( int d ) { move( 1, d ); } - @Override - default void bck( int d ) { move( 1, d ); } - - @Override - default void move( int distance, int d ) { - move( ( double ) distance, d ); - } - - @Override - default void move( long distance, int d ) { - move( ( double ) distance, d ); - } - - @Override - default void move( Localizable distance ) { - moveX( distance.getDoublePosition( 0 ) ); - moveY( distance.getDoublePosition( 1 ) ); - moveZ( distance.getDoublePosition( 2 ) ); - } - - @Override - default void move( int[] distance ) { - moveX( (double) distance[0] ); - moveY( (double) distance[1] ); - moveZ( (double) distance[2] ); - } - - @Override - default void move( long[] distance ) { - moveX( (double) distance[0] ); - moveY( (double) distance[1] ); - moveZ( (double) distance[2] ); - } - - @Override - default void setPosition( Localizable localizable ) { - setX( localizable.getDoublePosition( 0 ) ); - setY( localizable.getDoublePosition( 1 ) ); - setZ( localizable.getDoublePosition( 2 ) ); - } - - @Override - default void setPosition( int[] position ) { - setX( (double) position[0] ); - setY( (double) position[1] ); - setZ( (double) position[2] ); - } - - @Override - default void setPosition( long[] position ) { - setX( (double) position[0] ); - setY( (double) position[1] ); - setZ( (double) position[2] ); - } - - @Override - default void setPosition( int position, int d ) { - setPosition( ( double ) position, d ); - } - - @Override - default void setPosition( long position, int d ) { - setPosition( ( double ) position, d ); - } - - // -- EuclideanSpace methods -- - - @Override - default int numDimensions() { return 3; } - - // Extra convenience methods - default double getLength() { - return Math.sqrt( getDoublePosition(0) * getDoublePosition(0) + getDoublePosition(1) * getDoublePosition(1) + getDoublePosition(2) * getDoublePosition(2) ); - } - - default Vector3 add(Vector3 p2) { - Vector3 result = this.copy(); - result.moveX(p2.getDoublePosition(0)); - result.moveY(p2.getDoublePosition(1)); - result.moveZ(p2.getDoublePosition(2)); - return result; - } - - default Vector3 minus(Vector3 p2) { - Vector3 result = this.copy(); - result.moveX(-p2.getDoublePosition(0)); - result.moveY(-p2.getDoublePosition(1)); - result.moveZ(-p2.getDoublePosition(2)); - return result; - } - - default Vector3 multiply(float s) { - Vector3 result = this.copy(); - result.setPosition( result.getDoublePosition(0) * s, 0 ); - result.setPosition( result.getDoublePosition(1) * s, 1 ); - result.setPosition( result.getDoublePosition(2) * s, 2 ); - return result; - } - - default float[] asFloatArray() { - float[] a = new float[3]; - a[0] = xf(); - a[1] = yf(); - a[2] = zf(); - return a; - } - - default double[] asDoubleArray() { - double[] a = new double[3]; - a[0] = xd(); - a[1] = yd(); - a[2] = zd(); - return a; - } - - default Vector3 cross(Vector3 v2) { - return new JOMLVector3(JOMLVector3.convert(this).cross(JOMLVector3.convert(v2))); - } - - default Vector3 elmul(Vector3 v2) { - Vector3 r = this.copy(); - r.setX( r.xf() * v2.xf() ); - r.setY( r.yf() * v2.yf() ); - r.setZ( r.zf() * v2.zf() ); - return r; - } - - default float dot(Vector3 v2) { - return ( this.xf() * v2.xf() + this.yf() * v2.yf() + this.zf() * v2.zf() ); - } - - default Vector3 normalize() { - Vector3 r = this.copy(); - double f = 1 / this.getLength(); - r.setX(r.xf() * f); - r.setY(r.yf() * f); - r.setZ(r.zf() * f); - return r; - } - - Vector3 copy(); -} diff --git a/src/main/java/sc/iview/vector/Vector4.java b/src/main/java/sc/iview/vector/Vector4.java deleted file mode 100644 index a640cbab..00000000 --- a/src/main/java/sc/iview/vector/Vector4.java +++ /dev/null @@ -1,384 +0,0 @@ -/*- - * #%L - * Scenery-backed 3D visualization package for ImageJ. - * %% - * Copyright (C) 2016 - 2020 SciView developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package sc.iview.vector; - -import net.imglib2.Localizable; -import net.imglib2.RealLocalizable; -import net.imglib2.RealPositionable; - -/** - * Interface for 4D vectors. - * - * @author Kyle Harrington - */ -public interface Vector4 extends RealLocalizable, RealPositionable { - - // -- Vector3 methods -- - - float xf(); - float yf(); - float zf(); - float wf(); - - default void moveX(float distance) { setX( xf() + distance ); } - default void moveY(float distance) { setY( yf() + distance ); } - default void moveZ(float distance) { setZ( zf() + distance ); } - default void moveW(float distance) { setZ( wf() + distance ); } - default void move(float xDist, float yDist, float zDist, float wDist) { - moveX( xDist ); - moveY( yDist ); - moveZ( zDist ); - moveW( wDist ); - } - - void setX(float position); - void setY(float position); - void setZ(float position); - void setW(float position); - - default void setPosition(float x, float y, float z, float w) { - setX( x ); - setY( y ); - setZ( z ); - setW( w ); - } - - default double xd() { return xf(); } - default double yd() { return yf(); } - default double zd() { return zf(); } - default double wd() { return wf(); } - - default void moveX(double distance) { setX( xd() + distance ); } - default void moveY(double distance) { setY( yd() + distance ); } - default void moveZ(double distance) { setZ( zd() + distance ); } - default void moveW(double distance) { setW( wd() + distance ); } - default void move(double xDist, double yDist, double zDist, double wDist) { - moveX( xDist ); - moveY( yDist ); - moveZ( zDist ); - moveW( wDist ); - } - - default void setX(double position) { setX((float) position); } - default void setY(double position) { setY((float) position); } - default void setZ(double position) { setZ((float) position); } - default void setW(double position) { setW((float) position); } - - default void setPosition(double x, double y, double z, double w) { - setX( x ); - setY( y ); - setZ( z ); - setW( w ); - } - - // -- RealLocalizable methods -- - - @Override - default void localize(float[] position) { - position[0] = xf(); - position[1] = yf(); - position[2] = zf(); - position[3] = wf(); - } - - @Override - default void localize(double[] position) { - position[0] = xd(); - position[1] = yd(); - position[2] = zd(); - position[3] = wd(); - } - - @Override - default float getFloatPosition(int d) { - if( d == 0 ) return xf(); - if( d == 1 ) return yf(); - if( d == 2 ) return zf(); - if( d == 3 ) return wf(); - throw new IndexOutOfBoundsException( "" + d ); - } - - @Override - default double getDoublePosition(int d) { - if( d == 0 ) return xd(); - if( d == 1 ) return yd(); - if( d == 2 ) return zd(); - if( d == 3 ) return wd(); - throw new IndexOutOfBoundsException( "" + d ); - } - - // -- RealPositionable methods -- - - @Override - default void move(float distance, int d) { - if( d == 0 ) moveX( distance ); - else if( d == 1 ) moveY( distance ); - else if( d == 2 ) moveZ( distance ); - else if( d == 3 ) moveW( distance ); - else throw new IndexOutOfBoundsException( "" + d ); - } - - @Override - default void move(double distance, int d) { - if( d == 0 ) moveX( distance ); - else if( d == 1 ) moveY( distance ); - else if( d == 2 ) moveZ( distance ); - else if( d == 3 ) moveW( distance ); - else throw new IndexOutOfBoundsException( "" + d ); - } - - @Override - default void move(RealLocalizable distance) { - moveX( distance.getDoublePosition( 0 ) ); - moveY( distance.getDoublePosition( 1 ) ); - moveZ( distance.getDoublePosition( 2 ) ); - moveW( distance.getDoublePosition( 3 ) ); - } - - @Override - default void move(float[] distance) { - moveX( distance[0] ); - moveY( distance[1] ); - moveZ( distance[2] ); - moveW( distance[3] ); - } - - @Override - default void move(double[] distance) { - moveX( distance[0] ); - moveY( distance[1] ); - moveZ( distance[2] ); - moveW( distance[3] ); - } - - @Override - default void setPosition(RealLocalizable localizable) { - setX( localizable.getDoublePosition( 0 ) ); - setY( localizable.getDoublePosition( 1 ) ); - setZ( localizable.getDoublePosition( 2 ) ); - setW( localizable.getDoublePosition( 3 ) ); - } - - @Override - default void setPosition(float[] position) { - setX( position[0] ); - setY( position[1] ); - setZ( position[2] ); - setW( position[3] ); - } - - @Override - default void setPosition(double[] position) { - setX( position[0] ); - setY( position[1] ); - setZ( position[2] ); - setW( position[3] ); - } - - @Override - default void setPosition(float position, int d) { - if( d == 0 ) setX( position ); - else if( d == 1 ) setY( position ); - else if( d == 2 ) setZ( position ); - else if( d == 3 ) setW( position ); - else throw new IndexOutOfBoundsException( "" + d ); - } - - @Override - default void setPosition(double position, int d) { - if( d == 0 ) setX( position ); - else if( d == 1 ) setY( position ); - else if( d == 2 ) setZ( position ); - else if( d == 3 ) setW( position ); - else throw new IndexOutOfBoundsException( "" + d ); - } - - // -- Positionable methods -- - - @Override - default void fwd(int d) { move( 1, d ); } - @Override - default void bck(int d) { move( 1, d ); } - - @Override - default void move(int distance, int d) { - move( ( double ) distance, d ); - } - - @Override - default void move(long distance, int d) { - move( ( double ) distance, d ); - } - - @Override - default void move(Localizable distance) { - moveX( distance.getDoublePosition( 0 ) ); - moveY( distance.getDoublePosition( 1 ) ); - moveZ( distance.getDoublePosition( 2 ) ); - moveW( distance.getDoublePosition( 3 ) ); - } - - @Override - default void move(int[] distance) { - moveX( (double) distance[0] ); - moveY( (double) distance[1] ); - moveZ( (double) distance[2] ); - moveW( (double) distance[3] ); - } - - @Override - default void move(long[] distance) { - moveX( (double) distance[0] ); - moveY( (double) distance[1] ); - moveZ( (double) distance[2] ); - moveW( (double) distance[3] ); - } - - @Override - default void setPosition(Localizable localizable) { - setX( localizable.getDoublePosition( 0 ) ); - setY( localizable.getDoublePosition( 1 ) ); - setZ( localizable.getDoublePosition( 2 ) ); - setW( localizable.getDoublePosition( 3 ) ); - } - - @Override - default void setPosition(int[] position) { - setX( (double) position[0] ); - setY( (double) position[1] ); - setZ( (double) position[2] ); - setW( (double) position[3] ); - } - - @Override - default void setPosition(long[] position) { - setX( (double) position[0] ); - setY( (double) position[1] ); - setZ( (double) position[2] ); - setW( (double) position[3] ); - } - - @Override - default void setPosition(int position, int d) { - setPosition( ( double ) position, d ); - } - - @Override - default void setPosition(long position, int d) { - setPosition( ( double ) position, d ); - } - - // -- EuclideanSpace methods -- - - @Override - default int numDimensions() { return 3; } - - // Extra convenience methods - default double getLength() { - return Math.sqrt( getDoublePosition(0) * getDoublePosition(0) + // - getDoublePosition(1) * getDoublePosition(1) + // - getDoublePosition(2) * getDoublePosition(2) + // - getDoublePosition(3) * getDoublePosition(3) ); - } - - default Vector4 add(Vector4 p2) { - Vector4 result = this.copy(); - result.moveX(p2.getDoublePosition(0)); - result.moveY(p2.getDoublePosition(1)); - result.moveZ(p2.getDoublePosition(2)); - result.moveW(p2.getDoublePosition(3)); - return result; - } - - default Vector4 minus(Vector4 p2) { - Vector4 result = this.copy(); - result.moveX(-p2.getDoublePosition(0)); - result.moveY(-p2.getDoublePosition(1)); - result.moveZ(-p2.getDoublePosition(2)); - result.moveW(-p2.getDoublePosition(3)); - return result; - } - - default Vector4 multiply(float s) { - Vector4 result = this.copy(); - result.setPosition( result.getDoublePosition(0) * s, 0 ); - result.setPosition( result.getDoublePosition(1) * s, 1 ); - result.setPosition( result.getDoublePosition(2) * s, 2 ); - result.setPosition( result.getDoublePosition(3) * s, 3 ); - return result; - } - - default float[] asFloatArray() { - float[] a = new float[4]; - a[0] = xf(); - a[1] = yf(); - a[2] = zf(); - a[3] = wf(); - return a; - } - - default double[] asDoubleArray() { - double[] a = new double[4]; - a[0] = xd(); - a[1] = yd(); - a[2] = zd(); - a[3] = wd(); - return a; - } - - default Vector4 cross(Vector4 v2) { - JOMLVector4 v = new JOMLVector4(JOMLVector4.convert(this)); - return v.cross(new JOMLVector4(JOMLVector4.convert(v2))); - } - - default Vector4 elmul(Vector4 v2) { - Vector4 r = this.copy(); - r.setX( r.xf() * v2.xf() ); - r.setY( r.yf() * v2.yf() ); - r.setZ( r.zf() * v2.zf() ); - r.setW( r.wf() * v2.wf() ); - return r; - } - - default float dot(Vector4 v2) { - return ( this.xf() * v2.xf() + this.yf() * v2.yf() + this.zf() * v2.zf() + this.wf() * v2.wf() ); - } - - default Vector4 normalize() { - Vector4 r = this.copy(); - double f = 1 / this.getLength(); - r.setX(r.xf() * f); - r.setY(r.yf() * f); - r.setZ(r.zf() * f); - r.setW(r.wf() * f); - return r; - } - - Vector4 copy(); -} diff --git a/src/test/tests/sc/iview/test/SciViewTest.java b/src/test/tests/sc/iview/test/SciViewTest.java index 84f6be0f..87720af5 100644 --- a/src/test/tests/sc/iview/test/SciViewTest.java +++ b/src/test/tests/sc/iview/test/SciViewTest.java @@ -39,7 +39,6 @@ import org.scijava.thread.ThreadService; import sc.iview.SciView; import sc.iview.SciViewService; -import sc.iview.vector.JOMLVector3; public class SciViewTest { @@ -83,7 +82,7 @@ public void nestedNodeDeletionTest() throws Exception { final Sphere sphere = new Sphere( 1, 20 ); sphere.setMaterial( material ); - sphere.setPosition( JOMLVector3.convert( new JOMLVector3(0,0,0) ) ); + sphere.setPosition( new Vector3f(0,0,0) ); //sphere.setParent(group); group.addChild(sphere); sciView.addNode(group);