diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..3d47f986c41db29ec6dc0d5036bf760b3a1cf366 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +target/ +.settings/ +*.iml +.project +.classpath \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..2429c1dbb53dd77b6dcaec5b05769b583fb6dd8b --- /dev/null +++ b/pom.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <!-- Inherited Icy Parent POM --> + <parent> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>parent-pom-plugin</artifactId> + <version>1.0.3</version> + </parent> + + <!-- Project Information --> + <artifactId>animation-3d</artifactId> + <version>1.3.1</version> + + <packaging>jar</packaging> + + <name>Animation 3D</name> + <description>Generate nice dataset animations with interpolated camera points.</description> + <url>http://icy.bioimageanalysis.org/plugin/animation-3d/</url> + <inceptionYear>2020</inceptionYear> + + <organization> + <name>Institut Pasteur</name> + <url>https://pasteur.fr</url> + </organization> + + <licenses> + <license> + <name>GNU GPLv3</name> + <url>https://www.gnu.org/licenses/gpl-3.0.en.html</url> + <distribution>repo</distribution> + </license> + </licenses> + + <developers> + <developer> + <id>tprovoost</id> + <name>Thomas Provoost</name> + <url>http://icy.bioimageanalysis.org/author/tprovoost/</url> + <roles> + <role>founder</role> + <role>developer</role> + </roles> + </developer> + <developer> + <id>sdallongeville</id> + <name>Stéphane Dallongeville</name> + <url>https://research.pasteur.fr/fr/member/stephane-dallongeville/</url> + <roles> + <role>founder</role> + <role>lead</role> + <role>architect</role> + <role>developer</role> + <role>debugger</role> + <role>tester</role> + <role>maintainer</role> + <role>support</role> + </roles> + </developer> + </developers> + + <!-- Project properties --> + <properties> + + </properties> + + <!-- Project build configuration --> + <build> + + </build> + + <!-- List of project's dependencies --> + <dependencies> + <!-- The core of Icy --> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>icy-kernel</artifactId> + </dependency> + + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>flying-camera</artifactId> + <version>1.6.0</version> + </dependency> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>xuggler</artifactId> + <version>5.5.2</version> + </dependency> + </dependencies> + + <!-- Icy Maven repository (to find parent POM) --> + <repositories> + <repository> + <id>icy</id> + <name>Icy's Nexus</name> + <url>https://icy-nexus.pasteur.fr/repository/Icy/</url> + </repository> + </repositories> +</project> diff --git a/src/main/java/plugins/tprovoost/animation3d/Animation3D.java b/src/main/java/plugins/tprovoost/animation3d/Animation3D.java new file mode 100644 index 0000000000000000000000000000000000000000..c6255f34bcaac6cff381b815aba0d77cca5c5059 --- /dev/null +++ b/src/main/java/plugins/tprovoost/animation3d/Animation3D.java @@ -0,0 +1,94 @@ +package plugins.tprovoost.animation3d; + +import icy.gui.dialog.MessageDialog; +import icy.gui.frame.IcyFrame; +import icy.plugin.PluginLauncher; +import icy.plugin.abstract_.PluginActionable; +import icy.sequence.Sequence; +import icy.system.thread.ThreadUtil; + +import java.util.ArrayList; + +import plugins.tprovoost.flycam.FlyCam; +import plugins.tprovoost.flycam.FlyCamOverlay; + +public class Animation3D extends PluginActionable +{ + private Kinematics3D mainFrame; + + @Override + public void run() + { + Sequence seq = getActiveSequence(); + if (seq == null) + { + MessageDialog.showDialog("You must have a sequence opened for this operation."); + return; + } + + // FlyCam is not running on this sequence ? + if (!seq.hasOverlay(FlyCamOverlay.class)) + { + // start FlyCam + ThreadUtil.invokeNow(new Runnable() + { + @Override + public void run() + { + new FlyCam().run(); + } + }); + } + + // get the FlyCam overlay + final FlyCamOverlay overlay = (FlyCamOverlay) seq.getOverlays(FlyCamOverlay.class).get(0); + + // create the frame now + mainFrame = new Kinematics3D(overlay); + mainFrame.setVisible(true); + mainFrame.addToDesktopPane(); + } + + public Kinematics3D getKinematics3D() + { + return mainFrame; + } + + /** + * Returns the first Animation3D in the list of IcyFrames. If it doesn't + * exist, create an instance first. + * + * @return Kinematics3D + */ + public static Kinematics3D getAnimation3D() + { + ArrayList<IcyFrame> frames = IcyFrame.getAllFrames(Kinematics3D.class); + if (frames.isEmpty()) + { + Animation3D p = (Animation3D) PluginLauncher.start(Animation3D.class.getName()); + return p.getKinematics3D(); + } + else + return (Kinematics3D) frames.get(0); + } + + /** + * Returns the Animation3D of index <code>idx</code> in the list of + * IcyFrames. If it doesn't exist, returns null; + * + * @param idx int + * @return Kinematics3D + */ + public static Kinematics3D getAnimation3D(int idx) + { + ArrayList<IcyFrame> frames = IcyFrame.getAllFrames(Kinematics3D.class); + if (frames.isEmpty()) + { + return null; + } + else + { + return (Kinematics3D) frames.get(idx); + } + } +} diff --git a/src/main/java/plugins/tprovoost/animation3d/AnimationPanel.java b/src/main/java/plugins/tprovoost/animation3d/AnimationPanel.java new file mode 100644 index 0000000000000000000000000000000000000000..ead606adf8e3f909423e4b7f65e099bdbcf8c260 --- /dev/null +++ b/src/main/java/plugins/tprovoost/animation3d/AnimationPanel.java @@ -0,0 +1,661 @@ +package plugins.tprovoost.animation3d; + +import icy.gui.component.button.IcyButton; +import icy.main.Icy; +import icy.preferences.XMLPreferences; +import icy.preferences.XMLPreferences.XMLPreferencesRoot; +import icy.resource.icon.IcyIcon; +import icy.util.XMLUtil; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; +import javax.swing.event.ChangeListener; +import javax.swing.filechooser.FileNameExtensionFilter; + +import org.w3c.dom.Element; + +import plugins.tprovoost.flycam.CameraPosition; + +public class AnimationPanel extends JPanel +{ + private static final long serialVersionUID = 1L; + + private static final String PREF_CAMERAS = "cameras"; + private static final String PREF_CAM_LOCATION_DATA = "CamLocationData"; + + private static final String PREF_FPS = "fps"; + private static final String PREF_TIME = "time"; + private static final String PREF_SMOOTH = "smooth"; + + private KeySlider timeSlider; + private IcyButton gotostart; + private IcyButton gotoend; + private JButton playbackward; + private IcyButton playforward; + private IcyButton stop; + private JButton previousframe; + private JButton nextframe; + private JButton record; + private JButton recordCancel; + private JButton previouskey; + private JButton nextkey; + private JButton addkey; + private JButton removekey; + private JButton resetkey; + private JButton rescale; + private JTextField rescalefps; + private JTextField rescaletime; + private JTextField smoothpathfactor; + private JProgressBar recordProgressBar; + private JCheckBox checkBoxAutoUpdate; + private JButton openanimation; + private JButton saveanimation; + private ActionListener actionListener; + private ChangeListener changeListener; + private JComboBox comboPresets; + private JCheckBox cboxAutoChangeSeqT; + private JPanel panelLoadSave; + private JButton btnLoad; + private JButton btnSave; + + private enum AnimationPreset + { + CUSTOM + { + @Override + public String toString() + { + return "Custom"; + } + }, + ROTATION_X + { + @Override + public String toString() + { + return "Rotation on X"; + } + }, + ROTATION_Y + { + @Override + public String toString() + { + return "Rotation on Y"; + } + }, + ROTATION_Z + { + @Override + public String toString() + { + return "Rotation on Z"; + } + }, + GO_INTO + { + @Override + public String toString() + { + return "Go Into"; + } + } + }; + + public AnimationPanel(ActionListener actionListener, ChangeListener changeListener) + { + this.actionListener = actionListener; + this.changeListener = changeListener; + this.setPreferredSize(new Dimension(200, 465)); + + timeSlider = new KeySlider(0, 5 * 15, 15); + timeSlider.setBorder(new EmptyBorder(4, 4, 4, 4)); + timeSlider.setSnapToTicks(false); + timeSlider.setValue(0); + timeSlider.addChangeListener(changeListener); + timeSlider.setPreferredSize(new Dimension(290, 60)); + + playbackward = new JButton("<"); + playbackward.setActionCommand("playbackward"); + playbackward.addActionListener(actionListener); + + record = new JButton("Render"); + record.setActionCommand("record"); + record.addActionListener(actionListener); + + recordCancel = new JButton("Cancel"); + // recordCancel.setEnabled(false); + recordCancel.setActionCommand("recordCancel"); + recordCancel.addActionListener(actionListener); + + recordProgressBar = new JProgressBar(); + recordProgressBar.setMaximum(100); + recordProgressBar.setValue(0); + + openanimation = new JButton("Open..."); + openanimation.setActionCommand("openanimation"); + openanimation.addActionListener(actionListener); + + saveanimation = new JButton("Save..."); + saveanimation.setActionCommand("saveanimation"); + saveanimation.addActionListener(actionListener); + + JPanel rescalePanel = new JPanel(); + rescalePanel.setPreferredSize(new Dimension(300, 80)); + rescalePanel.setBorder(BorderFactory.createTitledBorder("Rescale tool")); + rescalePanel.setLayout(new BoxLayout(rescalePanel, BoxLayout.Y_AXIS)); + + JPanel panel_1 = new JPanel(); + panel_1.setBorder(new EmptyBorder(4, 4, 4, 4)); + rescalePanel.add(panel_1); + panel_1.setLayout(new GridLayout(0, 2, 0, 0)); + + JLabel label = new JLabel("FPS:"); + panel_1.add(label); + + rescalefps = new JTextField(); + panel_1.add(rescalefps); + rescalefps.setPreferredSize(new Dimension(30, 20)); + rescalefps.setText("" + getTimeSlider().getFps()); + + JPanel panel_2 = new JPanel(); + panel_2.setBorder(new EmptyBorder(4, 4, 4, 4)); + rescalePanel.add(panel_2); + panel_2.setLayout(new GridLayout(0, 2, 0, 0)); + JLabel label_1 = new JLabel("Seconds :"); + panel_2.add(label_1); + rescaletime = new JTextField("5"); + panel_2.add(rescaletime); + rescaletime.setPreferredSize(new Dimension(30, 20)); + + JPanel panelRescaleButton = new JPanel(); + rescalePanel.add(panelRescaleButton); + panelRescaleButton.setLayout(new BoxLayout(panelRescaleButton, BoxLayout.X_AXIS)); + + panelRescaleButton.add(Box.createHorizontalGlue()); + + rescale = new JButton("Rescale"); + panelRescaleButton.add(rescale); + rescale.setActionCommand("rescale"); + + panelRescaleButton.add(Box.createHorizontalGlue()); + rescale.addActionListener(actionListener); + + JPanel smoothingPanel = new JPanel(); + smoothingPanel.setBorder(BorderFactory.createTitledBorder("Smooth Camera path")); + smoothingPanel.setLayout(new GridLayout(0, 2, 0, 0)); + smoothingPanel.add(new JLabel("Smooth factor: ")); + setSmoothpathfactor(new JTextField("0")); + getSmoothpathfactor().setPreferredSize(new Dimension(190, 20)); + smoothingPanel.add(getSmoothpathfactor()); + + JPanel p8 = new JPanel(); + p8.setBorder(BorderFactory.createTitledBorder("Record")); + p8.setLayout(new GridLayout(4, 1)); + + cboxAutoChangeSeqT = new JCheckBox("Auto Change Time Position"); + cboxAutoChangeSeqT.setHorizontalAlignment(SwingConstants.CENTER); + cboxAutoChangeSeqT.setToolTipText("Also modifies the position of the current T in the Sequence."); + p8.add(cboxAutoChangeSeqT); + p8.add(record); + p8.add(recordCancel); + p8.add(getRecordProgressBar()); + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + // JPanel p9 = new JPanel(); + // p9.setBorder( BorderFactory.createTitledBorder("File")); + // p9.add( openanimation ); + // p9.add( saveanimation ); + + this.add(getTimeSlider()); + + comboPresets = new JComboBox(); + comboPresets.setModel(new DefaultComboBoxModel(AnimationPreset.values())); + comboPresets.addItemListener(new ItemListener() + { + + @Override + public void itemStateChanged(ItemEvent e) + { + if (e.getStateChange() == ItemEvent.SELECTED) + { + switch (AnimationPreset.valueOf(comboPresets.getSelectedItem().toString())) + { + case ROTATION_X: + { + // TODO + } + break; + + case ROTATION_Y: + { + // TODO + } + break; + + case ROTATION_Z: + { + // TODO + } + break; + + case CUSTOM: + default: + break; + } + } + + } + }); + + panelLoadSave = new JPanel(); + add(panelLoadSave); + panelLoadSave.setLayout(new GridLayout(1, 0, 0, 0)); + + btnLoad = new JButton("Load"); + btnLoad.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + String filename = animFileChooser(); + if (filename == null) + return; + XMLPreferencesRoot root = new XMLPreferencesRoot(filename); + loadXMLFile(root.getPreferences()); + } + }); + panelLoadSave.add(btnLoad); + + btnSave = new JButton("Save"); + btnSave.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + String filename = animFileChooser(); + if (filename == null) + return; + XMLPreferencesRoot root = new XMLPreferencesRoot(filename); + saveToXML(root.getPreferences()); + root.save(); + } + }); + panelLoadSave.add(btnSave); + // add(comboPresets); // TODO + + JPanel panel = new JPanel(); + add(panel); + + playforward = new IcyButton(new IcyIcon("sq_next.png")); + playforward.setActionCommand("playforward"); + playforward.addActionListener(actionListener); + + previousframe = new IcyButton(new IcyIcon("sq_br_prev.png")); + previousframe.setActionCommand("previousframe"); + previousframe.addActionListener(actionListener); + panel.setLayout(new GridLayout(0, 2, 0, 0)); + + JPanel p1 = new JPanel(); + p1.setBorder(new TitledBorder(null, "Frames", TitledBorder.LEADING, TitledBorder.TOP, null, null)); + panel.add(p1); + p1.setPreferredSize(new Dimension(200, 50)); + p1.setLayout(new GridLayout(2, 3)); + p1.add(previousframe); + p1.add(getPlayforward()); + + gotostart = new IcyButton(new IcyIcon("sq_br_first.png")); + gotostart.setActionCommand("gotostart"); + gotostart.addActionListener(actionListener); + + nextframe = new IcyButton(new IcyIcon("sq_br_next.png")); + nextframe.setActionCommand("nextframe"); + nextframe.addActionListener(actionListener); + p1.add(nextframe); + + p1.add(gotostart); + + stop = new IcyButton(new IcyIcon("square_shape.png")); + stop.setActionCommand("stop"); + stop.addActionListener(actionListener); + p1.add(stop); + + gotoend = new IcyButton(new IcyIcon("sq_br_last.png")); + gotoend.setActionCommand("gotoend"); + gotoend.addActionListener(actionListener); + p1.add(gotoend); + + previouskey = new IcyButton(new IcyIcon("arrow_left.png")); + previouskey.setActionCommand("previouskey"); + previouskey.addActionListener(actionListener); + + nextkey = new IcyButton(new IcyIcon("arrow_right.png")); + nextkey.setActionCommand("nextkey"); + nextkey.addActionListener(actionListener); + + addkey = new IcyButton(new IcyIcon("pin_map.png")); + addkey.setActionCommand("addkey"); + addkey.addActionListener(actionListener); + + removekey = new IcyButton(new IcyIcon("delete.png")); + removekey.setActionCommand("removekey"); + removekey.addActionListener(actionListener); + + resetkey = new IcyButton(new IcyIcon("playback_reload.png")); + resetkey.setActionCommand("resetkey"); + resetkey.addActionListener(actionListener); + + JPanel keyPanel = new JPanel(); + panel.add(keyPanel); + keyPanel.setLayout(new GridLayout(2, 3)); + keyPanel.setBorder(BorderFactory.createTitledBorder("Keys")); + keyPanel.setPreferredSize(new Dimension(300, 75)); + + keyPanel.add(addkey); + keyPanel.add(previouskey); + keyPanel.add(nextkey); + keyPanel.add(removekey); + keyPanel.add(resetkey); + + checkBoxAutoUpdate = new JCheckBox("Auto Update"); + // panel.add(checkBoxAutoUpdate); + checkBoxAutoUpdate.setSelected(true); + + this.add(rescalePanel); + this.add(smoothingPanel); + this.add(p8); + // this.add(p9); + } + + public void removeListeners() + { + timeSlider.removeChangeListener(changeListener); + gotostart.removeActionListener(actionListener); + gotoend.removeActionListener(actionListener); + playbackward.removeActionListener(actionListener); + playforward.removeActionListener(actionListener); + stop.removeActionListener(actionListener); + previousframe.removeActionListener(actionListener); + nextframe.removeActionListener(actionListener); + record.removeActionListener(actionListener); + recordCancel.removeActionListener(actionListener); + previouskey.removeActionListener(actionListener); + nextkey.removeActionListener(actionListener); + addkey.removeActionListener(actionListener); + removekey.removeActionListener(actionListener); + resetkey.removeActionListener(actionListener); + rescale.removeActionListener(actionListener); + rescalefps.removeActionListener(actionListener); + rescaletime.removeActionListener(actionListener); + smoothpathfactor.removeActionListener(actionListener); + checkBoxAutoUpdate.removeActionListener(actionListener); + openanimation.removeActionListener(actionListener); + saveanimation.removeActionListener(actionListener); + actionListener = null; + changeListener = null; + timeSlider = null; + } + + public void setEnabledRecursive(JPanel parent, boolean enabled) + { + parent.setEnabled(enabled); + Component[] components = parent.getComponents(); + if (components != null && components.length > 0) + { + int count = components.length; + for (int i = 0; i < count; i++) + { + components[i].setEnabled(enabled); + if (components[i].getClass().getName() == "javax.swing.JPanel") + { + setEnabledRecursive((JPanel) components[i], enabled); + } + } + } + } + + /** + * @return the timeSlider + */ + public KeySlider getTimeSlider() + { + return timeSlider; + } + + /** + * @param timeSlider + * the timeSlider to set + */ + public void setTimeSlider(KeySlider timeSlider) + { + this.timeSlider = timeSlider; + } + + /** + * @return the stop + */ + public JButton getStop() + { + return stop; + } + + /** + * @param stop + * the stop to set + */ + public void setStop(IcyButton stop) + { + this.stop = stop; + } + + /** + * @return the smoothpathfactor + */ + public JTextField getSmoothpathfactor() + { + return smoothpathfactor; + } + + /** + * @param smoothpathfactor + * the smoothpathfactor to set + */ + public void setSmoothpathfactor(JTextField smoothpathfactor) + { + this.smoothpathfactor = smoothpathfactor; + } + + /** + * @return the recordProgressBar + */ + public JProgressBar getRecordProgressBar() + { + return recordProgressBar; + } + + /** + * @param recordProgressBar + * the recordProgressBar to set + */ + public void setRecordProgressBar(JProgressBar recordProgressBar) + { + this.recordProgressBar = recordProgressBar; + } + + /** + * @return the playforward + */ + public JButton getPlayforward() + { + return playforward; + } + + /** + * @param playforward + * the playforward to set + */ + public void setPlayforward(IcyButton playforward) + { + this.playforward = playforward; + } + + /** + * @return the rescalefps + */ + public JTextField getRescalefps() + { + return rescalefps; + } + + /** + * @param rescalefps + * the rescalefps to set + */ + public void setRescalefps(JTextField rescalefps) + { + this.rescalefps = rescalefps; + } + + /** + * @return the rescaletime + */ + public JTextField getRescaletime() + { + return rescaletime; + } + + /** + * @param rescaletime + * the rescaletime to set + */ + public void setRescaletime(JTextField rescaletime) + { + this.rescaletime = rescaletime; + } + + /** + * @return the checkBoxAutoUpdate + */ + public JCheckBox getCheckBoxAutoUpdate() + { + return checkBoxAutoUpdate; + } + + /** + * @param checkBoxAutoUpdate + * the checkBoxAutoUpdate to set + */ + public void setCheckBoxAutoUpdate(JCheckBox checkBoxAutoUpdate) + { + this.checkBoxAutoUpdate = checkBoxAutoUpdate; + } + + public JButton getCancel() + { + return recordCancel; + } + + public boolean isAutoChangeSeqT() + { + return cboxAutoChangeSeqT.isSelected(); + } + + /** + * Used to load XML Files + * + * @return Returns null if Dialog exited. + */ + private String animFileChooser() + { + String filename = null; + JFileChooser fc = new JFileChooser(); + fc.setFileFilter(new FileNameExtensionFilter("Animation Files (.anim)", "anim")); + int returnVal = fc.showDialog(Icy.getMainInterface().getMainFrame(), "Save File"); + if (returnVal == JFileChooser.APPROVE_OPTION) + { + filename = fc.getSelectedFile().getAbsolutePath(); + if (!filename.endsWith(".anim")) + filename += ".anim"; + } + else + { + filename = null; + } + + return filename; + } + + /** + * Save all the properties into an XML file. + * + * @param root + * : file and node where data is saved. + */ + private void saveToXML(XMLPreferences root) + { + root.removeChildren(); + + XMLPreferences cameras = root.node(PREF_CAMERAS); + cameras.removeChildren(); + + root.put(PREF_FPS, rescalefps.getText()); + root.put(PREF_TIME, rescaletime.getText()); + root.put(PREF_SMOOTH, smoothpathfactor.getText()); + + // save camera positions into XML + for (CameraPosition cam : timeSlider.getCameraPositions()) + cam.saveCamera(XMLUtil.addElement(cameras.getXMLNode(), PREF_CAM_LOCATION_DATA)); + } + + /** + * Load all the properties into a file. + * + * @param root + * : file and node where data is saved. + */ + private void loadXMLFile(XMLPreferences root) + { + if (!root.nodeExists(PREF_CAMERAS)) + return; + + rescalefps.setText(root.get(PREF_FPS, "15")); + rescaletime.setText(root.get(PREF_TIME, "5")); + smoothpathfactor.setText(root.get(PREF_SMOOTH, "0")); + XMLPreferences cameras = root.node(PREF_CAMERAS); + + List<CameraPosition> cams = timeSlider.getCameraPositions(); + cams.clear(); + for (Element e : XMLUtil.getElements(cameras.getXMLNode(), PREF_CAM_LOCATION_DATA)) + { + CameraPosition cam = new CameraPosition(); + cam.loadCamera(e); + cams.add(cam); + } + + timeSlider.repaint(); + } +} diff --git a/src/main/java/plugins/tprovoost/animation3d/KeySlider.java b/src/main/java/plugins/tprovoost/animation3d/KeySlider.java new file mode 100644 index 0000000000000000000000000000000000000000..461429f9fc5a85110c0e26e0de3c0b20391f3a49 --- /dev/null +++ b/src/main/java/plugins/tprovoost/animation3d/KeySlider.java @@ -0,0 +1,274 @@ +package plugins.tprovoost.animation3d; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JSlider; + +import plugins.tprovoost.flycam.CameraPosition; + +public class KeySlider extends JSlider implements MouseListener, MouseMotionListener +{ + private static final long serialVersionUID = 1L; + // private ArrayList<Integer> key = new ArrayList<Integer>(); + private int fps = 15; + private int timelength; + private Point currentPoint = null; + private CameraPosition currentCamera; + private List<CameraPosition> cameraPositions = new ArrayList<CameraPosition>(); + + public KeySlider(int min, int max, int value) + { + super(min, max, value); + setPaintTicks(true); + setPaintLabels(true); + addMouseListener(this); + addMouseMotionListener(this); + } + + @Override + protected void paintComponent(Graphics g) + { + super.paintComponent(g); + Insets ins = getInsets(); + int w = getWidth(); + int h = getHeight(); + Graphics2D g2 = (Graphics2D) g.create(); + // g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + // RenderingHints.VALUE_ANTIALIAS_ON); + // g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // display red keys in component. + g2.setColor(new Color(210, 27, 58)); + for (CameraPosition cam : cameraPositions) + { + float step = ((w - 31) / (float) getMaximum()); + int positionx = Math.round(ins.left + 11 + cam.timeIndex * step); + g2.fillRect(positionx - 1, h - 12, 3, 6); + } + // // display Seconds:Frame. + g2.setColor(Color.black); + drawCenteredString(g2, "Frame: #" + getValue() + " / " + timeFormat(getValue()) + " s", getMaximum() / 2, 10, + false); + g2.dispose(); + } + + private String timeFormat(int t) + { + String modulo; + if (t % getFps() < 10) + { + modulo = "0" + t % getFps(); + } + else + { + modulo = "" + t % getFps(); + } + String SecondFrame; + SecondFrame = "" + (t / getFps()) + ":" + modulo + "0"; + return SecondFrame; + } + + /** + * @param g + * @param s + * @param v + * Tick corresponding to the center of the string. + */ + private void drawCenteredString(Graphics g, String s, int v, int y, boolean ShowTick) + { + FontMetrics metrics = getFontMetrics(g.getFont()); + float step = ((getWidth() - 16.0f) / (float) getMaximum()); + int positionx = Math.round(6 + v * step); + g.drawString(s, positionx - metrics.stringWidth(s) / 2, y); + if (ShowTick) + g.drawLine(positionx, 35, positionx, 30); + } + + // public void addKey(int timeKey) + // { + // key.add(timeKey); + // repaint(); + // } + // + // public void removeKey(int TimeKey) + // { + // key.remove((Integer) TimeKey); + // repaint(); + // } + // + // public void removeAllKey() + // { + // key.clear(); + // repaint(); + // } + + // public ArrayList<Integer> getKeys() + // { + // return key; + // } + + /** + * @return the timelength + */ + public int getTimelength() + { + return timelength; + } + + /** + * @param timelength + * the timelength to set + */ + public void setTimelength(int timelength) + { + this.timelength = timelength; + } + + /** + * @return the fps + */ + public int getFps() + { + return fps; + } + + /** + * @param fps + * the fps to set + */ + public void setFps(int fps) + { + this.fps = fps; + } + + @Override + public void setMaximum(int maximum) + { + super.setMaximum(maximum); + if (maximum < 5) + { + setMinorTickSpacing(maximum); + setLabelTable(createStandardLabels(maximum)); + } + else + { + setMajorTickSpacing(maximum / 5); + setMinorTickSpacing(maximum / 10); + setLabelTable(createStandardLabels(maximum / 5)); + } + } + + @Override + public void mouseDragged(MouseEvent e) + { + if (currentPoint != null && currentCamera != null) + { + int xmov = e.getPoint().x - currentPoint.x; + int max = getMaximum(); + float step = (getWidth()) / (float) max; + int timeIndex = (int) (e.getPoint().x / step); + + if (timeIndex == 0) + { + timeIndex = 1; + } + else if (timeIndex >= max) + { + timeIndex = max; + } + else if (cameraExists(timeIndex)) + { + if (Math.signum(xmov) == -1.0d) + { + timeIndex--; + } + else if (Math.signum(xmov) == 1.0d) + { + timeIndex++; + } + } + System.out.println(timeIndex); + currentCamera.timeIndex = timeIndex; + currentPoint = e.getPoint(); + } + repaint(); + } + + private boolean cameraExists(int timeIndex) + { + for (CameraPosition cam : cameraPositions) + { + if (cam.timeIndex == timeIndex) + return true; + } + return false; + } + + @Override + public void mouseMoved(MouseEvent e) + { + } + + @Override + public void mouseClicked(MouseEvent e) + { + } + + @Override + public void mousePressed(MouseEvent e) + { + Insets ins = getInsets(); + int w = getWidth(); + int h = getHeight(); + for (CameraPosition cam : cameraPositions) + { + float step = ((w - 31) / (float) getMaximum()); + int positionx = Math.round(ins.left + 11 + cam.timeIndex * step); + Rectangle rect = new Rectangle(positionx - 1, h - 12, 3, 6); + if (rect.contains(e.getPoint())) + { + currentCamera = cam; + currentPoint = e.getPoint(); + break; + } + } + } + + @Override + public void mouseReleased(MouseEvent e) + { + currentCamera = null; + currentPoint = e.getPoint(); + } + + @Override + public void mouseEntered(MouseEvent e) + { + } + + @Override + public void mouseExited(MouseEvent e) + { + } + + public void setCameraPositions(List<CameraPosition> value) + { + cameraPositions = value; + } + + public List<CameraPosition> getCameraPositions() + { + return cameraPositions; + } +} diff --git a/src/main/java/plugins/tprovoost/animation3d/Kinematics3D.java b/src/main/java/plugins/tprovoost/animation3d/Kinematics3D.java new file mode 100644 index 0000000000000000000000000000000000000000..788ecbcfb12bad2ede742c6af92024b42792430b --- /dev/null +++ b/src/main/java/plugins/tprovoost/animation3d/Kinematics3D.java @@ -0,0 +1,878 @@ +package plugins.tprovoost.animation3d; + +// Management of generality +import icy.file.FileUtil; +import icy.gui.dialog.MessageDialog; +import icy.gui.frame.IcyFrame; +import icy.image.ImageUtil; +import icy.main.Icy; +import icy.preferences.PluginsPreferences; +import icy.preferences.XMLPreferences; +import icy.system.thread.ThreadUtil; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.filechooser.FileNameExtensionFilter; + +import plugins.kernel.canvas.VtkCanvas; +import plugins.tprovoost.flycam.CameraPosition; +import plugins.tprovoost.flycam.FlyCamOverlay; + +import com.xuggle.mediatool.IMediaWriter; +import com.xuggle.mediatool.ToolFactory; +import com.xuggle.xuggler.ICodec; +import com.xuggle.xuggler.IPixelFormat; +import com.xuggle.xuggler.IRational; +import com.xuggle.xuggler.IStreamCoder; + +public class Kinematics3D extends IcyFrame +{ + private static final String LAST_DIR = "last_directory"; + private XMLPreferences prefs = PluginsPreferences.root(Animation3D.class); + + /** + * Original animation user's key. + */ + private List<CameraPosition> cameraPositions; + private AnimationPanel animPanel = null; + private PanelKinematics kinematicPanel = null; + private FlyCamOverlay flyCam; + private KinematicsThread kinemAnim; + + public Kinematics3D(FlyCamOverlay flyCam) + { + super("Animation 3D", true); + + cameraPositions = new ArrayList<CameraPosition>(); + kinematicPanel = new PanelKinematics(this); + this.flyCam = flyCam; + + rescaleTimeLine(); + setContentPane(getPanel()); + pack(); + } + + // private float[] linearInterp(float[] v1, float[] v2, float h) + // { + // float[] toBeReturn = new float[3]; + // toBeReturn[0] = v1[0] * (1 - h) + v2[0] * h; + // toBeReturn[1] = v1[1] * (1 - h) + v2[1] * h; + // toBeReturn[2] = v1[2] * (1 - h) + v2[2] * h; + // return toBeReturn; + // + // } + + public List<CameraPosition> getCameraPositions() + { + return cameraPositions; + } + + public void setCameraPositions(List<CameraPosition> value) + { + if (value != null) + cameraPositions = value; + } + + public void seekAndDestroyKeyAt(int t) + { + synchronized (cameraPositions) + { + for (int i = cameraPositions.size() - 1; i >= 0; i--) + { + if (cameraPositions.get(i).timeIndex == t) + cameraPositions.remove(i); + } + } + } + + public int getNumberOfUserKey() + { + return getCameraPositions().size(); + } + + /** + * @return the first key available. + * + * @author Fab + */ + public CameraPosition getFirstCamKey() + { + if (getCameraPositions().isEmpty()) + { + System.out.println("Kinematic3D : Problem : GetFirstCamKey should be able to get a valid key."); + return null; + } + + return getCameraPositions().get(0); + } + + /** + * @return the last key available. + * + * @author Fab + */ + public CameraPosition getLastCamKey() + { + if (getCameraPositions().isEmpty()) + { + System.out.println("Kinematic3D : Problem : GetLastCamKey should be able to get a valid key."); + return null; + } + return getCameraPositions().get(getNumberOfUserKey() - 1); + } + + /** + * @param t int + * @param CreateKeyIfDoNotExists boolean + * @return Get Previous Key <=t. + * + * @author Fab + */ + public CameraPosition getPreviousFlyCamKey(int t, boolean CreateKeyIfDoNotExists) + { + if (t < 0) + t = 0; + + CameraPosition Previous = null; // Previous detected key. + int previousbest = animPanel.getTimeSlider().getMaximum(); + + // find previous + for (CameraPosition camPos : getCameraPositions()) + { + // if (povk.CameraType == canvas.getCameraType()) + if (camPos.timeIndex <= t) + { + if ((t - camPos.timeIndex) < previousbest) + { + previousbest = t - camPos.timeIndex; + Previous = camPos; + } + } + } + + if (Previous == null && CreateKeyIfDoNotExists == true) + return getFirstCamKey(); + + return Previous; + } + + /** + * @param t int + * @param CreateKeyIfDoNotExists boolean + * @return Get Previous Key >=t. + * + * @author Fab + */ + public CameraPosition getNextFlyCamKey(int t, boolean CreateKeyIfDoNotExists) + { + if (t > animPanel.getTimeSlider().getMaximum()) + t = animPanel.getTimeSlider().getMaximum(); + + CameraPosition Next = null; // Previous detected key. + int nextbest = animPanel.getTimeSlider().getMaximum(); + + // find best + for (CameraPosition camPos : getCameraPositions()) + { + // if (povk.CameraType == canvas.getCameraType()) + if (camPos.timeIndex >= t) + { + if ((camPos.timeIndex - t) < nextbest) + { + nextbest = camPos.timeIndex - t; + Next = camPos; + } + } + } + + if (Next == null && CreateKeyIfDoNotExists == true) + return getLastCamKey(); + + return Next; + } + + /** + * @param t int + * @param smooth int + * @return CameraPosition + */ + public CameraPosition computeSmoothInterpolatedCamKey(int t, int smooth) + { + CameraPosition cumul = new CameraPosition(); + + int iter = 0; + for (int i = t - smooth; i <= t + smooth; i++) + { + iter++; + CameraPosition tmppovd = computeInterpolatedCamKey(i); + for (int j = 0; j < 3; j++) + { + cumul.position[j] += tmppovd.position[j]; + cumul.focal[j] += tmppovd.focal[j]; + cumul.viewUp[j] += tmppovd.viewUp[j]; + } + cumul.viewAngle += tmppovd.viewAngle; + } + + for (int j = 0; j < 3; j++) + { + cumul.position[j] /= iter; + cumul.focal[j] /= iter; + cumul.viewUp[j] /= iter; + } + cumul.viewAngle /= iter; + + return cumul; + } + + /** + * Creates an interpolated FlyCamKey. + * + * @param t + * Time index in animation's sequence ( frame # ) + * @return Interpolated FlyCamKey as CamPointOfViewData + * @author Fab 27/11/06 + */ + public CameraPosition computeInterpolatedCamKey(int t) + { + CameraPosition povd = new CameraPosition(); + CameraPosition step = new CameraPosition(); + CameraPosition Previous = getPreviousFlyCamKey(t, true); + CameraPosition Next = getNextFlyCamKey(t, true); + + if (Previous.timeIndex == Next.timeIndex) + return Previous; // We are on the key. No need to interpolate. + + final int delta = Next.timeIndex - Previous.timeIndex; + final int delta2 = t - Previous.timeIndex; + + for (int i = 0; i < 3; i++) + { + step.position[i] = (Next.position[i] - Previous.position[i]) / delta; + step.focal[i] = (Next.focal[i] - Previous.focal[i]) / delta; + step.viewUp[i] = (Next.viewUp[i] - Previous.viewUp[i]) / delta; + povd.position[i] = Previous.position[i] + step.position[i] * delta2; + povd.focal[i] = Previous.focal[i] + step.focal[i] * delta2; + povd.viewUp[i] = Previous.viewUp[i] + step.viewUp[i] * delta2; + } + step.viewAngle = (Next.viewAngle - Previous.viewAngle) / delta; + povd.viewAngle = Previous.viewAngle + step.viewAngle * delta2; + + return povd; + } + + /** + * @return Get the kinematic interaction panel + */ + public JPanel getPanel() + { + return kinematicPanel.getPanel(); + } + + /** + * needed when extends Object3D + */ + public void display() + { + // nothing to do here + + } + + /** + * clean up the object + */ + public void unload() + { + + } + + public void refreshAnimPanel() + { + // Put new computed key in Animation's Slider. + // animPanel.getTimeSlider().removeAllKey(); + for (CameraPosition camPos : getCameraPositions()) + { + // CameraPosition povk = e.nextElement(); + // if (canvas.getCameraType() == povk.CameraType) + // animPanel.getTimeSlider().addKey(povk.timeIndex); + + // Pour forcer un rafraichissement. + int i = animPanel.getTimeSlider().getValue(); + animPanel.getTimeSlider().setValue(i++); + animPanel.getTimeSlider().setValue(i); + + } + } + + /** + * Set a new CameraPosition at the specific time position. + * + * @param cam CameraPosition + * @param pos int + */ + public void addKey(CameraPosition cam, int pos) + { + kinematicPanel.addKey(pos); + } + + public class PanelKinematics implements ActionListener, ChangeListener + + { + // to define the panel : + private JPanel panel = null; + private Recorder recorder; + + /** + * Constructor + * @param kine Kinematics3D + */ + public PanelKinematics(Kinematics3D kine) + { + kinemAnim = new KinematicsThread(); + createPanel(); + } + + /** + * Build the panel. + */ + private void createPanel() + { + panel = new JPanel(); + panel.setLayout(new BorderLayout()); + + animPanel = new AnimationPanel(this, this); + animPanel.getTimeSlider().setCameraPositions(getCameraPositions()); + panel.add(animPanel); + } + + /** + * Get the panel to be used in the GUI for the list of marching cube. + * Build it on the first time. + * + * @return the panel to be used + */ + public JPanel getPanel() + { + if (panel != null) + return panel; + + createPanel(); + return panel; + } + + public void addKey(int t) + { + addKey(null, t); + } + + public void addKey(CameraPosition cam, int t) + { + seekAndDestroyKeyAt(t); + + // Sequence s = canvas.getSequence(); + // s.setSelectedT( s.getSelectedT()+1 ); + + // animPanel.getTimeSlider().addKey(t); + + if (cam == null) + cam = flyCam.getCameraPosition(); + cam.timeIndex = t; + // povd.CameraType = canvas.getCameraType(); + System.out.println(cam); + getCameraPositions().add(cam); + + } + + public void actionPerformed(ActionEvent event) + { + + String action = event.getActionCommand(); + + if (action == "gotostart") + { + animPanel.getTimeSlider().setValue(0); + } + + if (action == "gotoend") + { + animPanel.getTimeSlider().setValue(animPanel.getTimeSlider().getMaximum()); + } + + if (action == "playbackward") + { + // AnimPanel.getTimeSlider().setValue( + // AnimPanel.getTimeSlider().getMaximum() ); + } + + if (action == "playforward") + { + if (getNumberOfUserKey() < 2) + { + System.out.println("Must have at least 2 keys to create animation."); + // animPanel.setEnabledRecursive(animPanel, true); + return; + } + + animPanel.setEnabledRecursive(animPanel, false); + animPanel.getStop().setEnabled(true); + animPanel.getStop().requestFocus(); + + if (kinemAnim != null) + { + kinemAnim.stop = true; + kinemAnim = new KinematicsThread(); + } + ThreadUtil.bgRun(kinemAnim); + } + + if (action == "stop") + { + if (kinemAnim != null) + kinemAnim.stop = true; + animPanel.setEnabledRecursive(animPanel, true); + animPanel.getPlayforward().requestFocus(); + } + + if (action == "previousframe") + { + int v = animPanel.getTimeSlider().getValue(); + v--; + if (v < 0) + v = 0; + animPanel.getTimeSlider().setValue(v); + } + + if (action == "nextframe") + { + int v = animPanel.getTimeSlider().getValue(); + v++; + if (v > animPanel.getTimeSlider().getMaximum()) + v = animPanel.getTimeSlider().getMaximum(); + animPanel.getTimeSlider().setValue(v); + } + + if (action == "recordCancel") + { + if (recorder != null) + recorder.recording = false; + } + + if (action == "record") + { + if (recorder != null) + { + recorder.recording = false; + } + if (getNumberOfUserKey() < 2) + { + MessageDialog.showDialog("You should at least have 2 keys to start rendering."); + return; + } + + final JFileChooser fc = new JFileChooser(prefs.get(LAST_DIR, "")); + fc.setFileFilter(new FileNameExtensionFilter("AVI Files", "avi", "AVI")); + fc.addChoosableFileFilter(new FileNameExtensionFilter("MP4 Files", "mp4", "MP4")); + fc.addChoosableFileFilter(new FileNameExtensionFilter("MOV Files", "mov", "MOV")); + fc.setMultiSelectionEnabled(false); + int res = fc.showSaveDialog(Icy.getMainInterface().getMainFrame()); + if (res == JFileChooser.APPROVE_OPTION) + { + File file = fc.getSelectedFile(); + if (file == null) + return; + prefs.put(LAST_DIR, file.getParent()); + if (FileUtil.getFileExtension(file.getAbsolutePath(), false).isEmpty()) + { + String desc = fc.getFileFilter().getDescription(); + if (desc.contentEquals("MP4 Files")) + file = new File(file.getAbsoluteFile() + ".mp4"); + else if (desc.contentEquals("MOV Files")) + file = new File(file.getAbsoluteFile() + ".mov"); + else + file = new File(file.getAbsoluteFile() + ".avi"); + } + recorder = new Recorder(file); + ThreadUtil.bgRun(recorder); + } + } + + if (action == "previouskey") + { + CameraPosition previous = getPreviousFlyCamKey(animPanel.getTimeSlider().getValue() - 1, false); + if (previous != null) + { + animPanel.getTimeSlider().setValue(previous.timeIndex); + flyCam.setCameraPosition(previous, true); + } + + } + if (action == "nextkey") + { + CameraPosition next = getNextFlyCamKey(animPanel.getTimeSlider().getValue() + 1, false); + if (next != null) + { + animPanel.getTimeSlider().setValue(next.timeIndex); + flyCam.setCameraPosition(next, true); + } + } + + if (action == "removekey") + { + if (getNumberOfUserKey() > 1) + { + int t = animPanel.getTimeSlider().getValue(); + seekAndDestroyKeyAt(t); + // animPanel.getTimeSlider().removeKey(t); + } + else + { + System.out.println("Animator > Last remaining animation's key cannot be removed."); + } + + } + + if (action == "addkey") + { + // Destroy potential key at time t. + int t = animPanel.getTimeSlider().getValue(); + // animPanel.getTimeSlider().removeKey(t); // same for time + // slider + addKey(t); + } + if (action == "resetkey") + { + // animPanel.getTimeSlider().removeAllKey(); + getCameraPositions().clear(); + + // Ajoute une clef de chaque camera. + // int cameratype = canvas.getCameraType(); + // canvas.setCameraType(CanvasViewPort.CAMERA_FLYCAM); + addKey(0); + // canvas.setCameraType(CanvasViewPort.CAMERA_ROTATION); + // AddKey(0); + // canvas.setCameraType(cameratype); + + animPanel.getTimeSlider().setValue(0); + animPanel.getTimeSlider().repaint(); + } + + if (action == "rescale") + { + // Rescale TimeLine. + + rescaleTimeLine(); + + } + + // if (action == "saveanimation") { + // // Create a file chooser + // JFileChooser fc = new JFileChooser(); + // int returnVal = fc.showSaveDialog(null); + // + // if (returnVal == JFileChooser.APPROVE_OPTION) { + // File file = fc.getSelectedFile(); + // + // try { + // FileOutputStream fos; + // fos = new FileOutputStream( file ); + // ObjectOutputStream oos = new ObjectOutputStream(fos); + // + // AnimationSaveData d = new AnimationSaveData(); + // d.CampovData = CamPointOfViewKey; + // d.fps = AnimPanel.getTimeSlider().fps; + // d.smoothfactor = AnimPanel.smoothpathfactor.getText(); + // d.timelenght = AnimPanel.getTimeSlider().getTimelength(); + // oos.writeObject( d ); + // fos.close(); + // + // } + // catch (FileNotFoundException e) { e.printStackTrace(); } + // catch (IOException e) { e.printStackTrace(); } + // } + // } + + // if (action == "openanimation") { + // // Create a file chooser + // JFileChooser fc = new JFileChooser(); + // int returnVal = fc.showOpenDialog(null); + // + // if (returnVal == JFileChooser.APPROVE_OPTION) { + // File file = fc.getSelectedFile(); + // try { + // + // FileInputStream fis = new FileInputStream( file ); + // ObjectInputStream ois = new ObjectInputStream(fis); + // AnimationSaveData d = + // (AnimationSaveData)ois.readObject(); + // CamPointOfViewKey = d.CampovData; + // AnimPanel.getTimeSlider().setFps(d.fps); + // AnimPanel.getTimeSlider().setTimelength( d.timelenght); + // AnimPanel.getTimeSlider().setMaximum( d.fps * d.timelenght ); + // AnimPanel.rescalefps.setText( ""+d.fps ); + // AnimPanel.rescaletime.setText( ""+d.timelenght ); + // AnimPanel.smoothpathfactor.setText( "" + d.smoothfactor + // ); + // fis.close(); + // } + // catch (FileNotFoundException e) { e.printStackTrace(); } + // catch (IOException e) { e.printStackTrace(); } + // catch (ClassNotFoundException e) { e.printStackTrace(); } + // + // RefreshAnimPanel(); + // } + // } + + } + + public void stateChanged(ChangeEvent e) + { + // Listening to Animation's Panel TimeSlider. + if (animPanel.getCheckBoxAutoUpdate().isSelected() || (kinemAnim != null && !kinemAnim.stop)) + { + if (getNumberOfUserKey() == 0) + addKey(0); // Prevent system from having no key to read at + // all + JSlider slider = (JSlider) e.getSource(); + CameraPosition d = new CameraPosition(); + d = computeSmoothInterpolatedCamKey(slider.getValue(), + Integer.parseInt(animPanel.getSmoothpathfactor().getText())); + + if (d != null) + flyCam.setCameraPosition(d, true); + } + + } + + } + + public void actionPerformed(ActionEvent e) + { + + } + + public void rescaleTimeLine() + { + int oldfps = animPanel.getTimeSlider().getFps(); + int oldlength = animPanel.getTimeSlider().getTimelength(); + int newfps = Integer.parseInt(animPanel.getRescalefps().getText()); + int newlength = Integer.parseInt(animPanel.getRescaletime().getText()); + + animPanel.getTimeSlider().setFps(newfps); + animPanel.getTimeSlider().setTimelength(newlength); + // animPanel.getTimeSlider().removeAllKey(); + animPanel.getTimeSlider().setMaximum(newfps * newlength); + animPanel.getRescalefps().setText("" + newfps); + animPanel.getRescaletime().setText("" + newlength); + + // change towards length + double timeratio = newlength / (double) oldlength; + timeratio *= newfps / (double) oldfps; + for (CameraPosition cam : getCameraPositions()) + cam.timeIndex *= timeratio; + + animPanel.getTimeSlider().setValue((int) (animPanel.getTimeSlider().getValue() * timeratio)); + + // Put new computed key in Animation's Slider. + // for (Enumeration<CameraPosition> e = cameraPositions.elements(); + // e.hasMoreElements();) + // { + // CameraPosition povk = e.nextElement(); + // if (canvas.getCameraType() == povk.CameraType) + // animPanel.getTimeSlider().addKey(povk.timeIndex); + // } + } + + private class KinematicsThread implements Runnable + { + + private boolean stop = false; + + /** + * Play the animation // And has record capability + */ + @Override + public void run() + { + int idx = animPanel.getTimeSlider().getValue(); + int size = animPanel.getTimeSlider().getTimelength(); + int fps = animPanel.getTimeSlider().getFps(); + // long time = 0; + + idx = animPanel.getTimeSlider().getValue(); + size = animPanel.getTimeSlider().getTimelength() * animPanel.getTimeSlider().getFps(); + fps = animPanel.getTimeSlider().getFps(); + // time = System.nanoTime(); + + while (!stop) + { + if (idx >= size) + idx = 0; + + ThreadUtil.sleep((int) (1d / fps * 1000)); + // long delay; + // delay = (System.nanoTime() - time) * 1000000L; + + // if (animPanel.getCheckBoxSmooth().isSelected()) + // { + // float step; + // step = delay / (float) (1000 / fps); + // idx += step; + // } + // else + // { + idx += 1; + // } + + animPanel.getTimeSlider().setValue(idx); + + // time = System.nanoTime(); + + } + } + } + + class Recorder implements Runnable + { + private boolean recording; + private IRational frameRate; + private long nextTimeStamp; + private File f; + private IMediaWriter writer; + + public Recorder(File f) + { + this.f = f; + } + + @Override + public void run() + { + VtkCanvas canvas = flyCam.getCanvas(); + + if (canvas == null) + return; + + int width = canvas.getVtkPanel().getWidth(); + int height = canvas.getVtkPanel().getHeight(); + int fps = Integer.valueOf(animPanel.getRescalefps().getText()); + + frameRate = IRational.make(fps, 1); + writer = ToolFactory.makeWriter(f.getAbsolutePath()); + writer.addVideoStream(0, 0, frameRate, width, height); + + String ext = FileUtil.getFileExtension(f.getAbsolutePath(), false).toLowerCase(); + if (ext.contentEquals("mp4") || ext.contentEquals("mov")) + { + IStreamCoder coder = writer.getContainer().getStream(0).getStreamCoder(); + coder.setCodec(ICodec.ID.CODEC_ID_H264); + coder.setPixelType(IPixelFormat.Type.YUV422P); + } + + recording = true; + + nextTimeStamp = 0; + + // final int nbframe = animPanel.getTimeSlider().getFps() * + // animPanel.getTimeSlider().getTimelength(); + final int nbframe = animPanel.getTimeSlider().getMaximum(); + + ThreadUtil.invokeLater(new Runnable() + { + @Override + public void run() + { + animPanel.getRecordProgressBar().setMaximum(nbframe); + animPanel.getRecordProgressBar().setValue(0); + } + }); + try + { + for (int i = 0; i < nbframe && recording; i++) + { + final CameraPosition d = computeSmoothInterpolatedCamKey(i, + Integer.parseInt(animPanel.getSmoothpathfactor().getText())); + final int tPos = i; + int sequenceT = canvas.getPositionT(); + if (animPanel.isAutoChangeSeqT()) + sequenceT = (sequenceT + i) % canvas.getSequence().getSizeT(); + + canvas = flyCam.getCanvas(); + if ((canvas == null) || (canvas.getRenderer() == null)) + return; + + // set camera position + flyCam.setCameraPosition(d, false); + + BufferedImage originalImage = canvas.getRenderedImage(sequenceT, -1); + BufferedImage worksWithXugglerBufferedImage = ImageUtil.convert(originalImage, new BufferedImage( + originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR)); + + writer.encodeVideo(0, worksWithXugglerBufferedImage, nextTimeStamp, TimeUnit.MICROSECONDS); + + nextTimeStamp += (1e6 / frameRate.getNumerator()); + // recordedSequence.setImage(tPos, 0, + // canvas.getRenderedImage(sequenceT, -1)); + ThreadUtil.invokeLater(new Runnable() + { + @Override + public void run() + { + animPanel.getTimeSlider().setValue(tPos); + animPanel.getRecordProgressBar().setValue(tPos); + } + }); + } + } + finally + { + writer.close(); + ThreadUtil.invokeLater(new Runnable() + { + @Override + public void run() + { + animPanel.getRecordProgressBar().setValue(0); + } + }); + } + } + } + + public void clearKeys() + { + getCameraPositions().clear(); + } + + public void close() + { + flyCam = null; + if (kinemAnim != null) + { + kinemAnim.stop = true; + kinemAnim = null; + } + if (animPanel != null) + { + animPanel.removeListeners(); + animPanel.removeAll(); + animPanel = null; + } + } + + public void setFlyCamOverlay(FlyCamOverlay flyCam) + { + this.flyCam = flyCam; + } +} diff --git a/src/main/java/plugins/tprovoost/animation3d/TablePanel.java b/src/main/java/plugins/tprovoost/animation3d/TablePanel.java new file mode 100644 index 0000000000000000000000000000000000000000..36abac6720335c23e74dcdf977bf7cc53f7c4af6 --- /dev/null +++ b/src/main/java/plugins/tprovoost/animation3d/TablePanel.java @@ -0,0 +1,115 @@ +package plugins.tprovoost.animation3d; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.Hashtable; + +import javax.swing.JPanel; + +public class TablePanel extends JPanel +{ + + /** + * + */ + private static final long serialVersionUID = 1L; + + private Hashtable<String, Boolean> busy = new Hashtable<String, Boolean>(); + + private GridBagLayout gridbag; + private GridBagConstraints c; + + public final static int WEST = GridBagConstraints.WEST; + public final static int EAST = GridBagConstraints.EAST; + public final static int CENTER = GridBagConstraints.CENTER; + + private final Insets DEFAULT_INSETS = new Insets(2, 2, 2, 2); + private final int DEFAULT_ANCHOR = CENTER; + + public TablePanel() + { + super(); + gridbag = new GridBagLayout(); + setLayout(gridbag); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + // c.fill = GridBagConstraints.NONE; + + c.gridx = -1; + c.gridy = -1; + c.gridwidth = 1; + c.gridheight = 1; + + c.weightx = 0.0; // see later + c.weighty = 0.0; // see later + + } + + public void newLine() + { + c.gridx = -1; + c.gridy++; + } + + public Component add(Component component) + { + return add(component, 1, 1, null, -1); + } + + public Component add(Component component, int colspan, int rowspan) + { + return add(component, colspan, rowspan, null, -1); + } + + public Component add(Component component, int colspan, int rowspan, int anchor) + { + return add(component, colspan, rowspan, null, anchor); + } + + public Component add(Component component, int colspan, int rowspan, Insets insets, int anchor) + { + c.gridx++; + while (isBusy(c.gridx, c.gridy)) + c.gridx++; + c.gridwidth = colspan; + c.gridheight = rowspan; + for (int i = 0; i < colspan; i++) + { + for (int j = 0; j < rowspan; j++) + { + setBusy(c.gridx + i, c.gridy + j); + } + } + if (anchor != -1) + { + c.anchor = anchor; + } + else + { + c.anchor = DEFAULT_ANCHOR; + } + if (insets != null) + { + c.insets = insets; + } + else + { + c.insets = DEFAULT_INSETS; + } + + gridbag.setConstraints(component, c); + return super.add(component); + } + + private void setBusy(int x, int y) + { + busy.put(new String(y + "-" + x), new Boolean(true)); + } + + private boolean isBusy(int x, int y) + { + return busy.get(new String(c.gridy + "-" + c.gridx)) != null; + } +}