Browse Source

Added the Functionality to Edit and Add Motion Events, Motion Paths and their Waypoints.
We hereby celebrate our 1500th's SDK Commit :tada: .
Note that this functionality might still be buggy so please provide some Error Reports and Save often!
Also Note that (see Code) there are some Workarounds which will be unneeded with new alpha-4 (PR's for Core are opened)

MeFisto94 9 years ago
parent
commit
2bad91cc2d

+ 1 - 1
build.gradle

@@ -22,7 +22,7 @@ repositories {
 
 }
 
-ext.jmeFullVersion = "3.1.0-alpha3"
+ext.jmeFullVersion = "3.1.0-SNAPSHOT" // 3.1.0-alpha3
 ext.jmeNbmRevision = 0
 
 configurations {

+ 2 - 0
jme3-core/src/com/jme3/gde/core/icons/IconList.java

@@ -126,6 +126,8 @@ public class IconList {
             ImageUtilities.loadImageIcon("com/jme3/gde/core/icons/light.gif", false);
     public static ImageIcon mesh =
             ImageUtilities.loadImageIcon("com/jme3/gde/core/icons/mesh.gif", false);
+    public static ImageIcon motionEvent =
+            ImageUtilities.loadImageIcon("com/jme3/gde/core/icons/chimpanzee-sad.gif", false); // TODO: Find something better
     public static ImageIcon node =
             ImageUtilities.loadImageIcon("com/jme3/gde/core/icons/node.gif", false);
     public static ImageIcon emitter =

+ 11 - 0
jme3-core/src/com/jme3/gde/core/scene/SceneApplication.java

@@ -661,6 +661,17 @@ public class SceneApplication extends Application implements LookupProvider {
     public Node getGuiNode() {
         return guiNode;
     }
+    
+    /**
+     * Gets the RootNode of this Application.
+     * Warning: With great Power comes great responsibility ;)
+     * You shouldn't use this unless you exactly know about it's implications.
+     * Adding Spatials here won't make them Serialize into the .j3o file...
+     * @return 
+     */
+    public Node getRootNode() {
+        return rootNode;
+    }
 
     public AbstractCameraController getActiveCameraController() {
         return activeCamController;

+ 193 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeMotionEvent.java

@@ -0,0 +1,193 @@
+/*
+ *  Copyright (c) 2009-2016 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * 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.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  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 OWNER 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.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes;
+
+import com.jme3.cinematic.events.AbstractCinematicEvent;
+import com.jme3.cinematic.events.MotionEvent;
+import com.jme3.gde.core.icons.IconList;
+import com.jme3.gde.core.scene.SceneApplication;
+import java.awt.Image;
+import java.io.IOException;
+import org.openide.loaders.DataObject;
+import org.openide.nodes.AbstractNode;
+import org.openide.nodes.Node;
+import org.openide.nodes.Sheet;
+
+/**
+ * This is the SceneExplorer Node (Display Component Class) for Motion Events
+ * @author MeFisto94
+ */
+
[email protected](service = SceneExplorerNode.class)
+@SuppressWarnings({"unchecked", "rawtypes", "LeakingThisInConstructor"})
+public class JmeMotionEvent extends JmeControl {
+    private MotionEvent motionEvent;
+    private static Image smallImage = IconList.motionEvent.getImage();
+
+    public JmeMotionEvent() {
+        super();
+    }
+
+    public JmeMotionEvent(MotionEvent motionEvent, DataObject dataObject, JmeMotionPathChildren children) {
+        super(children, dataObject);
+        this.motionEvent = motionEvent;
+        control = motionEvent; // to have JmeControl work
+        
+        lookupContents.add(this);
+        lookupContents.add(control);
+        lookupContents.add(children);
+        setName("MotionEvent");
+        setDisplayName("Motion Event");
+        children.setMotionEventControl(this);
+    }
+
+    @Override
+    public Image getIcon(int type) {
+        return smallImage;
+    }
+
+    @Override
+    public Image getOpenedIcon(int type) {
+        return smallImage;
+    }
+
+    /**
+     * This method creates the Property Sheet (i.e. the Contents for the Properties Editor)
+     * See {@link AbstractNode#createSheet() } for more information
+     * @return 
+     */
+    @Override
+    protected Sheet createSheet() {
+        Sheet sheet = new Sheet(); // Sheet.createDefault(); // Create a sheet with an empty set.
+        
+        Sheet.Set abstractSet = Sheet.createPropertiesSet();
+        abstractSet.setName("AbstractCinematicEvent");
+        abstractSet.setDisplayName("Abstract Cinematic Event (Superclass)");
+        abstractSet.setShortDescription("This is the Superclass of MotionEvent: The Abstract Cinematic Event");
+        
+        createFields(AbstractCinematicEvent.class, abstractSet, motionEvent);
+        sheet.put(abstractSet);
+        
+        Sheet.Set set = Sheet.createPropertiesSet(); // Create a Properties "Set"/Entry for that Sheet. (A category so to say)
+        set.setDisplayName("Motion Event");
+        set.setShortDescription("These are the Properties of this Motion Event");
+        set.setName(MotionEvent.class.getName());
+        
+        MotionEvent obj = motionEvent;
+        if (obj == null) {
+            return sheet;
+        }
+        
+        createFields(MotionEvent.class, set, obj);
+        set.remove("Spatial"); // since we're a Control we don't set that value, we just belong to it.
+        set.remove("Path");
+        
+        sheet.put(set);
+        
+        return sheet;
+
+    }
+
+    /**
+     * Returns the class of the underlying Object of this Node.
+     * This is how we are related to things found in the SceneGraph
+     * @return {@link Class}
+     */
+    @Override
+    public Class getExplorerObjectClass() {
+        return MotionEvent.class;
+    }
+
+    /**
+     * Returns the class of this Node
+     * @return {@link Class}
+     */
+    @Override
+    public Class getExplorerNodeClass() {
+        return JmeMotionEvent.class;
+    }
+
+    public MotionEvent getMotionEvent() {
+        return motionEvent;
+    }
+            
+    @Override
+    public org.openide.nodes.Node[] createNodes(Object key, DataObject key2, boolean cookie) {
+        JmeMotionPathChildren children = new JmeMotionPathChildren();
+        children.setReadOnly(cookie);
+        return new org.openide.nodes.Node[] { new JmeMotionEvent((MotionEvent) key, key2, children).setReadOnly(cookie) }; // If we would return null here, we would have the JmeControl default (i.e. auto-exposure of properties, no icon but also no createSheet method)
+    }
+    
+    public void refreshChildren() {
+        ((JmeMotionPathChildren)this.jmeChildren).refreshChildren(true);
+        for (Object node : getChildren().getNodes()) {
+            JmeMotionPath mPath = (JmeMotionPath) node;
+            ((JmeVector3fChildren) mPath.getChildren()).refreshChildren(true);
+        }
+    }
+
+    @Override
+    public void destroy() throws IOException {
+        for (Node n: getChildren().getNodes()) {
+            ((JmeMotionPath)n).destroy();
+        }
+        super.destroy();
+    }
+    
+    
+    public void setModified(boolean immediate) {
+        dataObject.setModified(immediate);
+    }
+
+    @Override
+    public boolean isEnableable() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        if (motionEvent == null)
+            return false;
+        else
+            return motionEvent.isEnabled();
+    }
+
+    @Override
+    public boolean setEnabled(boolean enabled) {
+        if (motionEvent == null)
+            return false;
+        else {
+            motionEvent.setEnabled(enabled);
+            return true;
+        }
+    }
+}

+ 330 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeMotionPath.java

@@ -0,0 +1,330 @@
+/*
+ *  Copyright (c) 2009-2016 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * 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.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  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 OWNER 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.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes;
+
+import com.jme3.cinematic.MotionPath;
+import com.jme3.gde.core.icons.IconList;
+import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.sceneexplorer.nodes.actions.MotionPathPopup;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Spline;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Curve;
+import java.awt.Image;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Action;
+import org.openide.actions.DeleteAction;
+import org.openide.actions.PropertiesAction;
+import org.openide.loaders.DataObject;
+import org.openide.nodes.Node;
+import org.openide.nodes.Sheet;
+import org.openide.util.actions.SystemAction;
+
+/**
+ * This Class actually represents the MotionPath in the SceneComposer.<br>
+ * It is added and managed by {@link JmeMotionPathChildren }
+ * @author MeFisto94
+ */
[email protected](service = SceneExplorerNode.class)
+@SuppressWarnings({"unchecked", "rawtypes", "OverridableMethodCallInConstructor", "LeakingThisInConstructor"})
+public class JmeMotionPath extends AbstractSceneExplorerNode {
+
+    private static Image smallImage = IconList.chimpSmile.getImage();
+    private MotionPath motionPath;
+    private JmeMotionEvent motionEvent;
+    private float debugBoxExtents = 0.5f;
+    private Spatial spatial;
+    
+    public JmeMotionPath() {
+    }
+
+    public JmeMotionPath(MotionPath motionPath, JmeMotionEvent parent, JmeVector3fChildren children) {
+        super(children);
+        
+        this.motionPath = motionPath;
+        getLookupContents().add(motionPath);
+        getLookupContents().add(this);
+        getLookupContents().add(children);
+        super.setName("MotionPath");
+        super.setDisplayName("Motion Path");
+        children.setJmeMotionPath(this);
+        motionEvent = parent;
+        
+        updateSpline(false);
+    }
+
+    //<editor-fold defaultstate="collapsed" desc="Some boring Overrides">
+    @Override
+    public Image getIcon(int type) {
+        return smallImage;
+    }
+    @Override
+    public Image getOpenedIcon(int type) {
+        return smallImage;
+    }
+    
+    @Override
+    public Action[] getActions(boolean context) {
+        MotionPathPopup m = new MotionPathPopup(this);
+        return new Action[]{
+            m.getAddAction(),
+            m,
+            SystemAction.get(PropertiesAction.class),
+            SystemAction.get(DeleteAction.class)
+        };
+    }
+//</editor-fold>
+    
+    @Override
+    protected Sheet createSheet() {
+        Sheet sheet = super.createSheet();
+        Sheet.Set set = Sheet.createPropertiesSet();
+        set.setDisplayName("Motion Path");
+        set.setName(MotionPath.class.getName());
+        set.setShortDescription("These are the Properties of the Motion Event's Motion Path");
+       
+        if (motionPath == null) {
+            return sheet;
+        }
+        
+        Property<?> prop = makeEmbedProperty(this, getExplorerNodeClass(), motionPath.getPathSplineType().getClass(), "getPathSplineType", "setPathSplineType", "PathSplineType");
+        prop.setShortDescription("Sets the Type of the Paths' Spline. This will define how the single waypoints are interpolated (linear, curvy)");
+        set.put(prop);
+        
+        prop = makeEmbedProperty(this, getExplorerNodeClass(), float.class, "getCurveTension", "setCurveTension", "Curve Tension");
+        prop.setShortDescription("Sets the Curves' Tension. This defines how \"Curvy\" a curve will be. A tension of 0 would be completely linear.");
+        set.put(prop);
+        
+        prop = makeProperty(motionPath, boolean.class, "isCycle", "setCycle", "Cycle?");
+        prop.setShortDescription("Should the Path be a Cycle? This essentially means it will be looped. (Starting from the beginning after we're finished)");
+        set.put(prop);
+        
+        prop = makeProperty(motionPath, int.class, "getLength", null, "Path Length");
+        prop.setShortDescription("This is the total length this path has");
+        set.put(prop);
+        
+        prop = makeEmbedProperty(motionPath, motionPath.getClass(), int.class, "getNbWayPoints", null, "Number of Waypoints");
+        prop.setShortDescription("Shows the Number of Waypoints this Path consists of");
+        set.put(prop);
+        
+        sheet.put(set);
+        
+        set = Sheet.createPropertiesSet();
+        set.setDisplayName("Motion Path SDK");
+        set.setName("MotionPathSDK");
+        set.setShortDescription("These are SDK-dependent Settings which have nothing to do with MotionEvent or MotionPath in the first place.");
+        
+        prop = makeEmbedProperty(this, JmeMotionPath.class, float.class, "getDebugBoxExtents", "setDebugBoxExtents", "DebugBox Extents");
+        prop.setShortDescription("The DebugBox Extents defines how big the Debug Boxes (i.e. the Boxes you see for each Waypoint) are. Note: The BoxSize is 2 * extents");
+        set.put(prop);
+        sheet.put(set);
+        
+        return sheet;
+    }
+    
+    public MotionPath getMotionPath() {
+        return motionPath;
+    }
+    
+    public JmeMotionEvent getMotionEvent() {
+        return motionEvent;
+    }
+    
+    //<editor-fold defaultstate="collapsed" desc="Properties Getter/Setter">
+    public float getDebugBoxExtents() {
+        return debugBoxExtents;
+    }
+    
+    public void setDebugBoxExtents(float extents) {
+        debugBoxExtents = extents;
+        
+        if (getChildren() != null) {
+            for (Node n : getChildren().getNodes()) {
+                if (n instanceof JmeVector3f) {
+                    ((JmeVector3f)n).updateBox();
+                } else {
+                    Logger.getLogger(JmeMotionPath.class.getName()).log(Level.WARNING, "JmeMotionPath has some unknown Children...");
+                }
+            }
+        }
+    }
+    
+    public Spline.SplineType getPathSplineType() {
+        return motionPath.getPathSplineType();
+    }
+    
+    public void setPathSplineType(Spline.SplineType sType) {
+        if (sType == Spline.SplineType.Nurb) {
+            Logger.getLogger(JmeMotionPath.class.getName()).log(Level.SEVERE, "Nurb Curves aren't possible at the moment (they require additional helper points). Reverting to Catmull..");
+            setPathSplineType(Spline.SplineType.CatmullRom);
+            return;
+        } else if (sType == Spline.SplineType.Bezier) {
+            Logger.getLogger(JmeMotionPath.class.getName()).log(Level.SEVERE, "Bezier Curves are bugged and crash the SDK. Reverting to Catmull..");
+            setPathSplineType(Spline.SplineType.CatmullRom);
+            return;
+        }
+        
+        motionPath.setPathSplineType(sType);
+        updateSpline(true);
+    }
+    
+    public float getCurveTension() {
+        return motionPath.getCurveTension();
+    }
+    
+    public void setCurveTension(float f) {
+        motionPath.setCurveTension(f);
+        updateSpline(true);
+    }
+//</editor-fold>
+
+    @Override
+    public Class getExplorerObjectClass() {
+        return MotionPath.class;
+    }
+
+    @Override
+    public Class getExplorerNodeClass() {
+        return JmeMotionPath.class;
+    }
+
+    @Override
+    public org.openide.nodes.Node[] createNodes(Object key, DataObject key2, boolean cookie) {
+        return null;
+    }
+    
+    public void refreshChildren() {
+        ((JmeVector3fChildren)this.jmeChildren).refreshChildren(true);
+    }
+    
+    @Override
+    public void destroy() throws IOException {
+        for (Node n: getChildren().getNodes()) {
+            ((JmeVector3f)n).destroy();
+        }
+        super.destroy();
+        ((AbstractSceneExplorerNode) getParentNode()).refresh(true);
+    }
+    
+    public void removeWaypoint(JmeVector3f jme) {
+        motionPath.removeWayPoint(jme.getVector3f()); // We need to clear this or else the keys will still have that Vector3f.
+        // Also we should modify the motionPath instead of just showing the changes ;)
+    }
+    
+    public void enableDebugShapes() {
+        for (Node n : getChildren().getNodes()) {
+            if (n instanceof JmeVector3f) {
+                ((JmeVector3f)n).attachBox(((JmeVector3f)n).spatial, this);
+            }
+        }
+        
+        updateSpline(false);
+    }
+    
+    public void disableDebugShapes() {
+        for (Node n : getChildren().getNodes()) {
+            if (n instanceof JmeVector3f) {
+                ((JmeVector3f)n).detachBox(((JmeVector3f)n).spatial);
+            }
+        }
+        
+        if (spatial != null) {
+            final Spatial spat = spatial;
+            SceneApplication.getApplication().enqueue(new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    spat.removeFromParent();
+                    return null;
+                }
+            });
+        }
+        
+    }
+    /**
+     * Call this to update the visual Spline.
+     * @param wasModified If the Spatial was Modified and hence the dirty-safe flag should be triggered (only false for the Constructors first initiation)
+     */
+    public void updateSpline(boolean wasModified) {
+        if (spatial != null) {
+            final Spatial spat = spatial;
+            SceneApplication.getApplication().enqueue(new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    spat.removeFromParent();
+                    return null;
+                }
+            });
+        }
+        
+        Material m = new Material(SceneApplication.getApplication().getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+        m.setColor("Color", ColorRGBA.Red);
+        m.getAdditionalRenderState().setLineWidth(4f); // Brand new feature ;)
+        
+        switch (motionPath.getPathSplineType())
+        {
+            case CatmullRom:
+                Geometry geo = new Geometry("Curve", new Curve(motionPath.getSpline(), 10));
+                geo.setMaterial(m);
+                spatial = geo;
+                break;
+                
+            case Linear:
+                geo = new Geometry("Curve", new Curve(motionPath.getSpline(), 0));
+                geo.setMaterial(m);
+                spatial = geo;
+                break;
+                
+            default:
+                geo = new Geometry("Curve", new Curve(motionPath.getSpline(), 10));
+                geo.setMaterial(m);
+                spatial = geo;
+                break;
+        }
+        
+        final Spatial spat = spatial;
+        SceneApplication.getApplication().enqueue(new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    SceneApplication.getApplication().getRootNode().attachChild(spat);
+                    return null;
+                }
+        });
+        
+        if (wasModified)
+            motionEvent.setModified(true);
+    }
+}

+ 129 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeMotionPathChildren.java

@@ -0,0 +1,129 @@
+/*
+ *  Copyright (c) 2009-2016 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * 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.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  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 OWNER 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.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes;
+
+import com.jme3.cinematic.MotionPath;
+import com.jme3.cinematic.events.MotionEvent;
+import com.jme3.gde.core.scene.SceneApplication;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import org.openide.loaders.DataObject;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.openide.util.Exceptions;
+
+/**
+ * This Class is responsible for the management of all underlying MotionPaths as Nodes<br>
+ * (In fact we currently only have one possible MotionPath but we keep this to be consistent with JmeBoneChildren).<br>
+ * You have to ensure that you set the appropriate JmeMotionEvent for this class (this happens when JmeMotionEvent is creating it's Nodes)<br>
+ * It will use this class as Children (which are JmeMotionPaths)<br>
+ * @author MeFisto94
+ */
+public class JmeMotionPathChildren extends Children.Keys<Object> {
+
+    protected MotionPath path;
+    protected JmeMotionEvent jmeMotionEvent;
+    protected boolean readOnly = true;
+    protected HashMap<Object, Node> map = new HashMap<Object, Node>();
+    private DataObject dataObject;
+
+    public JmeMotionPathChildren() {
+    }
+
+    public JmeMotionPathChildren(JmeMotionEvent jmeMotionEvent, MotionPath path) {
+        this.path = path;
+        this.jmeMotionEvent = jmeMotionEvent;
+    }
+
+    public void refreshChildren(boolean immediate) {
+        setKeys(createKeys());
+        refresh();
+    }
+
+    public void setReadOnly(boolean cookie) {
+        this.readOnly = cookie;
+    }
+
+    @Override
+    protected void addNotify() {
+        super.addNotify();
+        setKeys(createKeys());
+    }
+
+    protected List<Object> createKeys() {
+        try {
+            return SceneApplication.getApplication().enqueue(new Callable<List<Object>>() {
+
+                public List<Object> call() throws Exception {
+                    List<Object> keys = new LinkedList<Object>();
+                    if (path != null) {
+                        keys.add(path);
+                    } else {
+                        keys.add(((MotionEvent)jmeMotionEvent.control).getPath());
+                    }
+
+                    return keys;
+                }
+            }).get();
+        } catch (InterruptedException ex) {
+            Exceptions.printStackTrace(ex);
+        } catch (ExecutionException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+        return null;
+    }
+
+    @Override
+    protected Node[] createNodes(Object key) {
+        if (key instanceof MotionPath) {
+            JmeVector3fChildren children = new JmeVector3fChildren();
+            children.setReadOnly(readOnly);
+            return new Node[]{new JmeMotionPath((MotionPath) key, jmeMotionEvent, children).setReadOnly(readOnly)}; // We pass null so we don't have another MotionPath als Child.
+        }
+        return new Node[]{Node.EMPTY};
+    }
+
+    public void setMotionEventControl(JmeMotionEvent jmeMotionEvent) {
+        this.jmeMotionEvent = jmeMotionEvent;
+    }
+
+    public DataObject getDataObject() {
+        return dataObject;
+    }
+
+    public void setDataObject(DataObject dataObject) {
+        this.dataObject = dataObject;
+    }
+}

+ 10 - 1
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeSpatial.java

@@ -76,10 +76,19 @@ import org.openide.util.actions.SystemAction;
 @org.openide.util.lookup.ServiceProvider(service = SceneExplorerNode.class)
 public class JmeSpatial extends AbstractSceneExplorerNode {
 
-    private Spatial spatial;
+    protected Spatial spatial;
     protected final DataFlavor SPATIAL_FLAVOR = new DataFlavor(ClipboardSpatial.class, "Spatial");
 
     public JmeSpatial() {
+        super();
+    }
+    
+    public JmeSpatial(Spatial spatial) {
+        super();
+        this.spatial = spatial;
+        getLookupContents().add(spatial);
+        getLookupContents().add(this);
+        super.setName(spatial.getName());
     }
 
     public JmeSpatial(Spatial spatial, JmeSpatialChildren factory) {

+ 349 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeVector3f.java

@@ -0,0 +1,349 @@
+/*
+ *  Copyright (c) 2009-2016 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * 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.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  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 OWNER 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.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes;
+
+import com.jme3.gde.core.icons.IconList;
+import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import java.awt.Image;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import javax.swing.Action;
+import org.openide.actions.DeleteAction;
+import org.openide.actions.MoveDownAction;
+import org.openide.actions.MoveUpAction;
+import org.openide.loaders.DataObject;
+import org.openide.nodes.Sheet;
+import org.openide.util.actions.SystemAction;
+
+/**
+ * This Class actually represents the MotionPaths Waypoints in the SceneComposer.<br>
+ * It is added and managed by {@link JmeVector3fChildren } but it could also be used for any other Waypointish Thing
+ * @author MeFisto94
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "OverridableMethodCallInConstructor", "LeakingThisInConstructor"})
+public class JmeVector3f extends JmeSpatial {
+
+    private static Image smallImage = IconList.wireBox.getImage();
+    private JmeVector3fChildren parent;
+    private JmeMotionPath jmeMotionPath;
+    private Vector3f v;
+    private float extents;
+    
+    public JmeVector3f() {
+        super();
+    }
+    
+    public JmeVector3f(JmeVector3fChildren parent, JmeMotionPath jmeMotionPath, Vector3f vec) {
+        super();
+        
+        v = vec;
+        this.parent = parent;
+        this.jmeMotionPath = jmeMotionPath;
+        spatial = generateBox();
+        
+        getLookupContents().add(spatial);
+        getLookupContents().add(vec);
+        getLookupContents().add(this);
+        
+        super.setDisplayName("Waypoint");
+        super.setName(spatial.getName());
+        
+        attachBox(spatial, jmeMotionPath);
+    }
+    
+    /**
+     * GenerateBox will simply generate our {@link DebugBoxGeometry }
+     * @return 
+     */
+    private Geometry generateBox() {
+        extents = jmeMotionPath.getDebugBoxExtents();
+        DebugBoxGeometry geom = new DebugBoxGeometry("Waypoint", extents, this);
+        
+        Material mat = new Material(SceneApplication.getApplication().getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Cyan);
+        geom.setMaterial(mat);
+        geom.setInternalLocalTranslation(v);
+        
+        return geom;
+    } 
+    /**
+     * AttachBox is the internal method simply used to attach the DebugBox to the Scene Graph.
+     * @param s The Spatial to attach
+     * @param jmeMotionPath The Parental Node to refresh.
+     */
+    protected void attachBox(final Spatial s, final JmeMotionPath jmeMotionPath) {
+        SceneApplication.getApplication().enqueue(new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    SceneApplication.getApplication().getRootNode().attachChild(s);
+                    jmeMotionPath.refresh(true);
+                    return null;
+                }
+        });
+    }
+    /**
+     * This detaches the DebugBox from the SceneGraph but also waits for this to happen.
+     * This is because we want to reattach a new box and it's just better this way :P
+     * @param s
+     */
+    protected void detachBox(final Spatial s) {
+        SceneApplication.getApplication().enqueue(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                s.removeFromParent();
+                return null;
+            }
+        });
+    }
+    /**
+     * This will trigger a rebuild of the whole Box.
+     * It is used internally if the box dimensions have changed ({@link JmeMotionPath#setDebugBoxExtents(float) })
+     * Moving the Boxes around does NOT need a rebuild.
+     */
+    public void updateBox() {
+        getLookupContents().remove(spatial);
+        detachBox(spatial);
+        spatial = generateBox();
+        getLookupContents().add(spatial);
+        attachBox(spatial, jmeMotionPath);
+    }
+    /**
+     * This is called when the Vector v has been moved (without the setVector3f which is our callback).
+     * This means it was moved by the Properties Dialog (see setXYZ())
+     */
+    public void moveBox() {
+        SceneApplication.getApplication().enqueue(new Runnable() {
+            @Override
+            public void run() {
+                spatial.setLocalTranslation(v); // It's a bit redundant since it will call #setVector3f() but there's no other way.
+                                                // Plus it will invoke updateSpline() for us.
+            }
+        });
+    }
+    
+    // <editor-fold defaultstate="collapsed" desc="Just some boring Overrides ">
+    @Override
+    public Image getIcon(int type) {
+        return smallImage;
+    }
+
+    @Override
+    public Image getOpenedIcon(int type) {
+        return smallImage;
+    }
+    
+    @Override
+    public Action[] getActions(boolean context) {
+        return new Action[]{
+            SystemAction.get(DeleteAction.class),
+            SystemAction.get(MoveUpAction.class),
+            SystemAction.get(MoveDownAction.class)
+        };
+    }
+    
+    @Override
+    public boolean canRename() {
+        return true;
+    }
+    
+    @Override
+    public boolean canDestroy() {
+        return true;
+    }
+    
+    @Override
+    public boolean canCut() {
+        return false;
+    }
+    
+    @Override
+    public boolean canCopy() {
+        return false;
+    }
+
+    @Override
+    public void destroy() throws IOException {
+        detachBox(spatial);
+        jmeMotionPath.updateSpline(true);
+        
+        /* These are mandatory:
+         * Without them the Node looks like it's undeletable
+         * (since it stays in the Motion Path and gets readded everytime)
+         */
+        jmeMotionPath.removeWaypoint(this);
+        parent.refreshChildren(true);
+        super.destroy();
+    }
+
+// </editor-fold>
+    
+    
+    /* For Properties */
+    public int getChildIndex() {
+        int idx = parent.indexOf(this);
+        setDisplayName("Waypoint " + idx);
+        return idx;
+    }
+    
+    @Override
+    protected Sheet createSheet() {
+        Sheet sheet = Sheet.createDefault();
+        Sheet.Set set = Sheet.createPropertiesSet();
+        set.setDisplayName("Vector3f");
+        set.setName(Vector3f.class.getName());
+        set.setShortDescription("These are the Properties of the Motion Paths's Waypoint (Vector3f). Feel free to either edit the floats seperately or use the [x, y, z] way. Make sure that you defocus and focus this Node again in order to have the Properties be reloaded");
+       
+        if (v == null) {
+            return sheet;
+        }
+        
+        Property p = makeEmbedProperty(this, getExplorerNodeClass(), float.class, "getX", "setX", "X");
+        p.setShortDescription("The Vector3f's X-Value");
+        set.put(p);
+        
+        p = makeEmbedProperty(this, getExplorerNodeClass(), float.class, "getY", "setY", "Y");
+        p.setShortDescription("The Vector3f's Y-Value");
+        set.put(p);
+        
+        p = makeEmbedProperty(this, getExplorerNodeClass(), float.class, "getZ", "setZ", "Z");
+        p.setShortDescription("The Vector3f's Z-Value");
+        set.put(p);
+        
+        p = makeEmbedProperty(this, getExplorerNodeClass(), int.class, "getChildIndex", null, "Child Index");
+        p.setShortDescription("The Index of this Node inside of the MotionPath's Children");
+        set.put(p);
+        
+        createFields(Vector3f.class, set, v);
+        
+        sheet.put(set);
+        return sheet;
+    }
+    
+    /**
+     * This is a conveniance method to access the internal Vector3f.
+     * Currently it's even unused.
+     * @return The Vector3f representated by this Node
+     */
+    public Vector3f getVector3f() {
+        return v;
+    }
+    /**
+     * This is the callback which will be called by the Geometry.
+     * It is used to update the DataStructure with the Debug Box Position
+     * @param to 
+     */
+    public void setVector3f(Vector3f to) {
+        v.set(to);
+    }
+    
+    /* The following 6 methods are just so we know when the user typed in some properties.
+     * Note: A PropertyChangeListener would also be appropriate and even less code.
+     */
+    //<editor-fold defaultstate="collapsed" desc="The Setters for the Properties Panel">
+    public void setX(float x) {
+        //v.x = x;
+        v.setX(x);
+        moveBox();
+    }
+    
+    public void setY(float y) {
+        v.y = y;
+        moveBox();
+    }
+    
+    public void setZ(float z) {
+        v.z = z;
+        moveBox();
+    }
+    
+    public float getX() {
+        return v.x;
+    }
+    
+    public float getY() {
+        return v.y;
+    }
+    
+    public float getZ() {
+        return v.z;
+    }
+//</editor-fold>
+    
+    @Override
+    public Class getExplorerObjectClass() {
+        return Vector3f.class;
+    }
+    @Override
+    public Class getExplorerNodeClass() {
+        return JmeVector3f.class;
+    }
+    @Override
+    public org.openide.nodes.Node[] createNodes(Object key, DataObject key2, boolean cookie) {
+        return null;
+    }
+    
+    private class DebugBoxGeometry extends Geometry {
+        JmeVector3f self;
+        
+        public DebugBoxGeometry(String s, float extents, JmeVector3f jme) {
+            super(s, new Box(extents, extents, extents));
+            self = jme;
+        }
+        
+        @Override
+        public void setLocalTranslation(float x, float y, float z) {
+            this.setLocalTranslation(new Vector3f(x, y, z));
+        }
+
+        @Override
+        public void setLocalTranslation(Vector3f localTranslation) {
+            self.setVector3f(localTranslation);
+            self.jmeMotionPath.updateSpline(true); // This also triggers setModified() to have it saved.
+            super.setLocalTranslation(localTranslation);
+        }
+
+        /**
+         * Since the usual translation updates the spline and hence triggers setModified,
+         * we need an internal method (e.g. for the Constructor)
+         * @param localTranslation the translation to set. 
+         */
+        public void setInternalLocalTranslation(Vector3f localTranslation) {
+            super.setLocalTranslation(localTranslation);
+        }
+    }
+}

+ 298 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeVector3fChildren.java

@@ -0,0 +1,298 @@
+/*
+ *  Copyright (c) 2009-2016 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * 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.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  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 OWNER 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.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes;
+
+import com.jme3.cinematic.MotionPath;
+import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.scene.controller.SceneToolController;
+import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
+import com.jme3.math.Vector3f;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.openide.loaders.DataObject;
+import org.openide.nodes.Children;
+import org.openide.nodes.Index;
+import org.openide.nodes.Node;
+import org.openide.util.Exceptions;
+
+/**
+ * This Class is responsible for the management of all underlying Vector3fs as Nodes<br>
+ * (In fact we currently only have one possible MotionPath but we keep this to be consistent with JmeBoneChildren).<br>
+ * You have to ensure that you set the appropriate JmeMotionEvent for this class (this happens when JmeMotionEvent is creating it's Nodes)<br>
+ * It will use this class as Children (which are JmeMotionPaths)<br>
+ * @author MeFisto94
+ */
+public class JmeVector3fChildren extends Children.Keys<Vector3f> implements Index {
+
+    protected JmeMotionPath jmeMotionPath;
+    protected boolean readOnly = true;
+    
+    protected List<Vector3f> keys;
+    protected LinkedList<ChangeListener> listeners = new LinkedList<ChangeListener>();
+    private DataObject dataObject;
+
+    public JmeVector3fChildren() {
+    }
+
+    public JmeVector3fChildren(JmeMotionPath jmeMotionPath) {
+        this.jmeMotionPath = jmeMotionPath;
+    }
+
+    public void refreshChildren(boolean immediate) {
+        keys = createKeys();
+        setKeys(keys);
+        refresh();
+    }
+
+    public void setReadOnly(boolean cookie) {
+        this.readOnly = cookie;
+    }
+
+    @Override
+    protected void addNotify() {
+        super.addNotify();
+        keys = createKeys();
+        setKeys(keys);
+    }
+
+    protected List<Vector3f> createKeys() {
+        try {
+            return SceneApplication.getApplication().enqueue(new Callable<List<Vector3f>>() {
+
+                @Override
+                public List<Vector3f> call() throws Exception {
+                    List<Vector3f> keys = new LinkedList<Vector3f>();
+                    if (jmeMotionPath.getMotionPath() != null) {
+                        for (int i = 0; i <jmeMotionPath.getMotionPath().getNbWayPoints(); i++) {
+                            keys.add(jmeMotionPath.getMotionPath().getWayPoint(i));
+                        }
+                    }
+                    
+                    return keys;
+                }
+            }).get();
+        } catch (InterruptedException ex) {
+            Exceptions.printStackTrace(ex);
+        } catch (ExecutionException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+        return null;
+    }
+    
+    @Override
+    protected Node[] createNodes(Vector3f key) {
+        Node n = new JmeVector3f(this, jmeMotionPath, key).setReadOnly(readOnly);
+        n.setDisplayName("Waypoint " + keys.indexOf(key));
+        return new Node[]{ n };
+    }
+
+    /**
+     * This Method is used because before createNodes takes place we are called by the default constructor and have to pass things...
+     * @param jmeMotionPath The JmeMotionPath instance which is this nodes parent. 
+     */
+    public void setJmeMotionPath(JmeMotionPath jmeMotionPath) {
+        this.jmeMotionPath = jmeMotionPath;
+    }
+
+    public DataObject getDataObject() {
+        return dataObject;
+    }
+    public void setDataObject(DataObject dataObject) {
+        this.dataObject = dataObject;
+    }
+
+    @Override
+    public int indexOf(Node node) {
+        Vector3f key = node.getLookup().lookup(Vector3f.class); // reverseLookup.get(node);
+        
+        if (key == null)
+            return -1;
+        
+        return keys.indexOf(key); // For this to work, keys has to have ALWAYS be in sync for EVERY Change to the MotionPath.Waypoints
+    }
+
+    /**
+     * &quot;Invoke a dialog for reordering the children.&quot;
+     */
+    @Override
+    public void reorder() {
+        // We wont...
+    }
+
+    /**
+     * Reorder all children with a given permutation.
+     * TODO: Test this....
+     * @param perm - permutation with the length of current nodes. The permutation lists the new positions of the original nodes, that is, for nodes [A,B,C,D] and permutation [0,3,1,2], the final order would be [A,C,D,B].
+     * @throws IllegalArgumentException - if the permutation is not valid
+     */
+    @Override
+    public void reorder(int[] perm) {
+        List<Vector3f> oldKeys = Arrays.asList((Vector3f[])keys.toArray());
+        
+        for (int i = 0; i < perm.length; i++) {
+            keys.get(i).set(oldKeys.get(perm[i]));
+        }
+        
+        triggerChangeListeners();
+        
+        for (Node n: getNodes()) {
+            ((JmeVector3f)n).moveBox();
+        }
+    }
+
+    /**
+     * Move the element at the x-th position to the y-th position. All elements after the y-th position are moved down.
+     * @param x the position to remove the element from
+     * @param y the position to insert the element to
+     */
+    @Override
+    public void move(int x, int y) {
+        MotionPath m = jmeMotionPath.getMotionPath();
+        /* TODO: Check and Implement Method correctly. */
+        if (x < y) { /* X above Y, means: remove x, shift everything until y up, move everything from y one down. set x to y's value. */
+            Vector3f v_x = keys.get(x).clone(); // We can't use remove() because of the MotionPaths
+            for (int i = x+1; i <= y - 1; i++) {
+                moveUp(i); // This causes quite some rendering updates and messes up the selection, but when it's there, we use it.
+            }
+            
+            for (int i = keys.size() - 2; i >= y; i--) {
+                moveDown(i);
+            }
+            
+            keys.get(y).set(v_x);
+        } else { /* X below Y, means: remove x, shift everything until end up, move everyting from x one down. set y to x's value */
+            // TODO IMPLEMENT
+        }
+        
+        triggerChangeListeners();
+    }
+
+    /**
+     * Exchange two elements.
+     * @param x Position of the first Element
+     * @param y Position of the second Element
+     */
+    @Override
+    public void exchange(int x, int y) {
+        
+        /**
+         * There are two ways to achieve this Exchange. Keep in mind we actually have two lists:
+         * We have the "keys" and the motionPaths Waypoints (controlPoints). In order to exchange, we have:
+         * 
+         * Method A: Exchanging the Vector3f Reference in BOTH keys and motionPaths (this basically means reordering them, really!)
+         *           This is problematic, because MotionPath doesn't support accessing it's list.
+         * 
+         * Method B: Exchanging the Vector3f's Values. This means The upper Node (Waypoint) will still be the upper, but it's contents just change
+         *           This is easy to achieve using Vector3f#set(). The downside is that that since the keys have not been altered, they aren't recreated.
+         *           We will just call the apropriate update Methods for the Visual Representations.
+         *           Note: I don't know yet what netbeans thinks of Method B. Probably we need ugly reflection and Method A.
+         */
+        
+        /* Clone because the contents will be altered */
+        Vector3f v_x = keys.get(x).clone();
+        Vector3f v_y = keys.get(y).clone();
+        
+        // Exchange keys
+        /* METHOD A:
+        //keys.set(x, v_y);
+        //keys.set(y, v_x);*/
+        
+        keys.get(x).set(v_y);
+        keys.get(y).set(v_x);
+        
+        MotionPath m = jmeMotionPath.getMotionPath();
+        
+        // Exchange Waypoints
+        // This only works because waypoint y == keys.y (v_y). If this isn't true we messed up somewhere, really hard.
+        // it would mean that the saved Motion Path derives from the shown...
+        
+        m.getWayPoint(x).set(v_y);
+        m.getWayPoint(y).set(v_x);
+        
+        
+        /* Let the nodes know that their content has been changed */
+        ((JmeVector3f)getNodeAt(x)).moveBox();
+        ((JmeVector3f)getNodeAt(y)).moveBox();
+        //jmeMotionPath.updateSpline(true);// MoveBox calls UpdateSpline
+        
+        // Netbeans API wants that.
+        triggerChangeListeners();
+        SceneExplorerTopComponent.findInstance().setSelectedNode((AbstractSceneExplorerNode)getNodeAt(y)); // Speak: "Replace X with Y"
+    }
+
+    /**
+     * Move an element up
+     * @param i index of element to move up
+     */
+    @Override
+    public void moveUp(int i) {
+        if (i <= 0 || i >= keys.size()) // Can't move up
+            throw new IndexOutOfBoundsException(); 
+        
+        exchange(i, i-1); // Clever Code reusing, huh? ;)
+    }
+
+    /**
+     * Move an element down
+     * @param i index of element to move down
+     */
+    @Override
+    public void moveDown(int i) {
+        if (i < 0 || i >= keys.size() - 1)
+            throw new IndexOutOfBoundsException();
+        
+        exchange(i, i+1); // moveUp(i+1); wouldn't respect the setSelectedNode
+    }
+    
+    private void triggerChangeListeners() {
+        for (ChangeListener cl : listeners) {
+            cl.stateChanged(new ChangeEvent(this));
+        }
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+        listeners.add(cl);
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+        listeners.remove(cl);
+    }
+}

+ 125 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/MotionPathPopup.java

@@ -0,0 +1,125 @@
+/*
+ *  Copyright (c) 2009-2010 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * 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.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  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 OWNER 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.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes.actions;
+
+import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.scene.controller.SceneToolController;
+import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeMotionPath;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeVector3f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import java.awt.event.ActionEvent;
+import javax.swing.AbstractAction;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import org.openide.loaders.DataObject;
+import org.openide.util.actions.Presenter;
+
+/**
+ * This is the Popup which enables or disables Debug Shapes of the Motion Path.
+ * Also it allows you to add new Waypoints
+ * @author MeFisto94
+ */
+public class MotionPathPopup extends AbstractAction implements Presenter.Popup {
+
+    protected JmeMotionPath jmeMotionPath;
+    protected Node node;
+    protected DataObject dataObject;
+
+    public MotionPathPopup(JmeMotionPath path) {
+        this.jmeMotionPath = path;
+        this.node = path.getLookup().lookup(Node.class);
+        this.dataObject = path.getLookup().lookup(DataObject.class);
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+    }
+
+    @Override
+    public JMenuItem getPopupPresenter() {
+        JMenu result = new JMenu("Debug Shapes");
+        result.add(new JMenuItem(getShowAction()));
+        result.add(new JMenuItem(getHideAction()));
+        
+        return result;
+    }
+    
+    public AbstractAction getShowAction() {
+        return new AbstractAction("Show") {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                jmeMotionPath.enableDebugShapes();
+            }
+        };
+    }
+
+    public AbstractAction getHideAction() {
+        return new AbstractAction("Hide") {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                jmeMotionPath.disableDebugShapes();
+            }
+        };
+    }
+    
+    /**
+     * This is the Add Waypoint Action. It resides in this Popup Class, however
+     * it's not added to the Debug Shapes Popup. Instead it is added as a seperate action
+     * @return 
+     */
+    public AbstractAction getAddAction() {
+        return new AbstractAction("Add Waypoint") {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                Vector3f pos;
+                
+                SceneToolController controller = SceneApplication.getApplication().getStateManager().getState(SceneToolController.class);
+                if (controller != null && (!controller.getCursorLocation().equals(Vector3f.ZERO))) { // Vector3f.ZERO means not yet clicked
+                    pos = controller.getCursorLocation().clone().addLocal(0, jmeMotionPath.getDebugBoxExtents() * 3f, 0); // Shifting up so a) Netbeans isn't merging Waypoints and b) it's visible
+                } else {
+                    AbstractSceneExplorerNode node = SceneExplorerTopComponent.findInstance().getLastSelected();
+                    if (node instanceof JmeVector3f) { // null instanceof JmeVector3f == false
+                        pos = ((JmeVector3f)node).getVector3f().clone().addLocal(0, jmeMotionPath.getDebugBoxExtents() * 3f, 0);
+                    } else {
+                        pos = new Vector3f(0f, 1.0f, 0f); // Default is a bit over the Center
+                    }
+                }
+                
+                jmeMotionPath.getMotionPath().addWayPoint(pos);
+                jmeMotionPath.refreshChildren();
+            }
+        };
+    }
+}

+ 75 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/impl/NewMotionEventAction.java

@@ -0,0 +1,75 @@
+/*
+ *  Copyright (c) 2009-2016 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * 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.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  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 OWNER 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.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes.actions.impl;
+
+import com.jme3.cinematic.MotionPath;
+import com.jme3.cinematic.events.MotionEvent;
+import com.jme3.gde.core.sceneexplorer.nodes.actions.AbstractNewControlAction;
+import com.jme3.gde.core.sceneexplorer.nodes.actions.NewControlAction;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+
+/**
+ * This Action is responsible to create a new Entry under "New Control ->"
+ * @author MeFisto94
+ */
[email protected](service = NewControlAction.class)
+public class NewMotionEventAction extends AbstractNewControlAction {
+
+    public NewMotionEventAction() {
+        name = "Motion Event";
+    }
+
+    @Override
+    protected Control doCreateControl(Spatial spatial) {
+        MotionEvent control = spatial.getControl(MotionEvent.class);
+        if (control != null) {
+            spatial.removeControl(control);
+        }
+        
+        if (spatial.getParent() == null)
+            spatial.removeFromParent(); // disallow the rootNode
+        
+        control = new MotionEvent();
+        control.setLookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y); // TODO: DELETE WHEN ALPHA-4 IS OUT!!
+        control.setRotation(Quaternion.IDENTITY); // TODO: DELETE WHEN ALPHA-4 IS OUT!!
+        MotionPath mPath = new MotionPath();
+        mPath.addWayPoint(Vector3f.ZERO.clone());
+        mPath.addWayPoint(new Vector3f(0f, 1f, 0f));
+        mPath.addWayPoint(new Vector3f(1f, 0f, 1f));
+        control.setPath(mPath);
+       
+        return control;
+    }
+}

+ 1 - 0
jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerTopComponent.java

@@ -943,6 +943,7 @@ private void jToggleSelectGeomActionPerformed(java.awt.event.ActionEvent evt) {/
      * listener for node selection changes
      * @param ev
      */
+    @Override
     public void resultChanged(LookupEvent ev) {
         if (currentRequest == null || !currentRequest.isDisplayed()) {
             return;