Browse Source

Merge branch 'master' of https://github.com/jMonkeyEngine/jmonkeyengine.git

Alrik 10 năm trước cách đây
mục cha
commit
4c5850bf00
37 tập tin đã thay đổi với 2292 bổ sung1266 xóa
  1. 19 2
      .travis.yml
  2. 4 1
      jme3-blender/build.gradle
  3. 2 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java
  4. 6 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java
  5. 7 5
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java
  6. 127 141
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java
  7. 128 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java
  8. 22 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java
  9. 214 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java
  10. 1 1
      jme3-core/src/main/java/com/jme3/light/SpotLight.java
  11. 2 0
      jme3-core/src/main/java/com/jme3/renderer/Limits.java
  12. 4 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
  13. 12 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
  14. 1 1
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java
  15. 21 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java
  16. 28 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
  17. 159 155
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  18. 34 4
      jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java
  19. 14 0
      jme3-core/src/main/resources/Common/ShaderLib/GLSL150Compat.glsllib
  20. 1 0
      jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java
  21. 148 0
      jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java
  22. 5 57
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java
  23. 5 1
      jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java
  24. 1 0
      sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ImportModel.java
  25. 1 0
      sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ModelImporterVisualPanel3.java
  26. 45 8
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java
  27. 0 204
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveManager.java
  28. 4 8
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java
  29. 4 7
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java
  30. 30 664
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/SelectTool.java
  31. 123 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DeleteShortcut.java
  32. 141 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DuplicateShortcut.java
  33. 227 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java
  34. 189 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java
  35. 199 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java
  36. 335 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java
  37. 29 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutTool.java

+ 19 - 2
.travis.yml

@@ -14,11 +14,28 @@ branches:
 
 notifications:
   slack:
-    secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s="
     on_success: change
     on_failure: always
+    rooms:
+      secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s="
 
-before_install:
+install:
+  -  ./gradlew assemble
+
+script:
+  - ./gradlew check
+  - ./gradlew createZipDistribution
+
+deploy:
+  provider: releases
+  api_key:
+    secure: "KbFiMt0a8FxUKvCJUYwikLYaqqGMn1p6k4OsXnGqwptQZEUIayabNLHeaD2kTNT3e6AY1ETwQLff/lB2LttmIo4g5NWW63g1K3A/HwgnhJwETengiProZ/Udl+ugPeDL/+ar43HUhFq4knBnzFKnEcHAThTPVqH/RMDvZf1UUYI="
+  file: build/distributions/jME3.1.0_snapshot-github_2015-06-20.zip
+  skip_cleanup: true
+  on:
+    tags: true
+
+# before_install:
   # required libs for android build tools
   # sudo apt-get update
   # sudo apt-get install -qq p7zip-full

+ 4 - 1
jme3-blender/build.gradle

@@ -6,4 +6,7 @@ dependencies {
     compile project(':jme3-core')
     compile project(':jme3-desktop')
     compile project(':jme3-effects')
-}
+    compile ('org.ejml:core:0.27')
+    compile ('org.ejml:dense64:0.27')
+    compile ('org.ejml:simple:0.27')
+}

+ 2 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java

@@ -64,7 +64,7 @@ public abstract class Constraint {
         Pointer pData = (Pointer) constraintStructure.getFieldValue("data");
         if (pData.isNotNull()) {
             Structure data = pData.fetchData().get(0);
-            constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, ownerOMA, blenderContext);
+            constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, name, ownerOMA, blenderContext);
             Pointer pTar = (Pointer) data.getFieldValue("tar");
             if (pTar != null && pTar.isNotNull()) {
                 targetOMA = pTar.getOldMemoryAddress();
@@ -77,7 +77,7 @@ public abstract class Constraint {
             }
         } else {
             // Null constraint has no data, so create it here
-            constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, null, blenderContext);
+            constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, name, null, blenderContext);
         }
         ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());
         ipo = influenceIpo;

+ 6 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java

@@ -30,6 +30,8 @@ public abstract class ConstraintDefinition {
     protected Set<Long>        alteredOmas;
     /** The variable that determines if the constraint will alter the track in any way. */
     protected boolean          trackToBeChanged = true;
+    /** The name of the constraint. */
+    protected String           constraintName;
 
     /**
      * Loads a constraint definition based on the constraint definition
@@ -53,6 +55,10 @@ public abstract class ConstraintDefinition {
         constraintHelper = (ConstraintHelper) (blenderContext == null ? null : blenderContext.getHelper(ConstraintHelper.class));
         this.ownerOMA = ownerOMA;
     }
+    
+    public void setConstraintName(String constraintName) {
+        this.constraintName = constraintName;
+    }
 
     /**
      * @return determines if the definition of the constraint will change the bone in any way; in most cases

+ 7 - 5
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java

@@ -92,7 +92,7 @@ public class ConstraintDefinitionFactory {
      *             this exception is thrown when the blender file is somehow
      *             corrupted
      */
-    public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException {
+    public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, String constraintName, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException {
         if (constraintStructure == null) {
             return new ConstraintDefinitionNull(null, ownerOMA, blenderContext);
         }
@@ -100,7 +100,9 @@ public class ConstraintDefinitionFactory {
         Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName);
         if (constraintDefinitionClass != null) {
             try {
-                return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext);
+                ConstraintDefinition def = (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext);
+                def.setConstraintName(constraintName);
+                return def;
             } catch (IllegalArgumentException e) {
                 throw new BlenderFileException(e.getLocalizedMessage(), e);
             } catch (SecurityException e) {
@@ -113,9 +115,9 @@ public class ConstraintDefinitionFactory {
                 throw new BlenderFileException(e.getLocalizedMessage(), e);
             }
         } else {
-            String constraintName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName);
-            if (constraintName != null) {
-                return new UnsupportedConstraintDefinition(constraintName);
+            String unsupportedConstraintClassName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName);
+            if (unsupportedConstraintClassName != null) {
+                return new UnsupportedConstraintDefinition(unsupportedConstraintClassName);
             } else {
                 throw new BlenderFileException("Unknown constraint type: " + constraintClassName);
             }

+ 127 - 141
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java

@@ -1,36 +1,32 @@
 package com.jme3.scene.plugins.blender.constraints.definitions;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+
+import org.ejml.simple.SimpleMatrix;
 
 import com.jme3.animation.Bone;
 import com.jme3.math.Transform;
-import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
+import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.math.DQuaternion;
 import com.jme3.scene.plugins.blender.math.DTransform;
+import com.jme3.scene.plugins.blender.math.Matrix;
 import com.jme3.scene.plugins.blender.math.Vector3d;
 
-/**
- * The Inverse Kinematics constraint.
- * 
- * @author Wesley Shillingford (wezrule)
- * @author Marcin Roguski (Kaelthas)
- */
 public class ConstraintDefinitionIK extends ConstraintDefinition {
-    private static final float MIN_DISTANCE  = 0.0001f;
+    private static final float MIN_DISTANCE  = 0.001f;
     private static final int   FLAG_USE_TAIL = 0x01;
     private static final int   FLAG_POSITION = 0x20;
 
+    private BonesChain         bones;
     /** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */
     private int                bonesAffected;
-    /** The total length of the bone chain. Useful for optimisation of computations speed in some cases. */
-    private double              chainLength;
     /** Indicates if the tail of the bone should be used or not. */
     private boolean            useTail;
     /** The amount of iterations of the algorithm. */
@@ -51,122 +47,85 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
         }
     }
 
+    @Override
+    public Set<Long> getAlteredOmas() {
+        return bones.alteredOmas;
+    }
+
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
         if (influence == 0 || !trackToBeChanged || targetTransform == null) {
             return;// no need to do anything
         }
+
         DQuaternion q = new DQuaternion();
         Vector3d t = new Vector3d(targetTransform.getTranslation());
-        List<BoneContext> bones = this.loadBones();
+        bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, blenderContext);
         if (bones.size() == 0) {
             return;// no need to do anything
         }
         double distanceFromTarget = Double.MAX_VALUE;
 
-        int iterations = this.iterations;
-        if (bones.size() == 1) {
-            iterations = 1;// if only one bone is in the chain then only one iteration that will properly rotate it will be needed
-        } else {
-            // if the target cannot be rached by the bones' chain then the chain will become straight and point towards the target
-            // in this case only one iteration will be needed, computed from the root to top bone
-            BoneContext rootBone = bones.get(bones.size() - 1);
-            Transform rootBoneTransform = constraintHelper.getTransform(rootBone.getArmatureObjectOMA(), rootBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
-            if (t.distance(new Vector3d(rootBoneTransform.getTranslation())) >= chainLength) {
-                Collections.reverse(bones);
-
-                for (BoneContext boneContext : bones) {
-                    Bone bone = boneContext.getBone();
-                    DTransform boneTransform = new DTransform(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD));
-
-                    Vector3d e = boneTransform.getTranslation().add(boneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(boneContext.getLength()));// effector
-                    Vector3d j = boneTransform.getTranslation(); // current join position
-
-                    Vector3d currentDir = e.subtractLocal(j).normalizeLocal();
-                    Vector3d target = t.subtract(j).normalizeLocal();
-                    double angle = currentDir.angleBetween(target);
-                    if (angle != 0) {
-                        Vector3d cross = currentDir.crossLocal(target).normalizeLocal();
-                        q.fromAngleAxis(angle, cross);
-                        
-                        if(bone.equals(this.getOwner())) {
-                            if (boneContext.isLockX()) {
-                                q.set(0, q.getY(), q.getZ(), q.getW());
-                            }
-                            if (boneContext.isLockY()) {
-                                q.set(q.getX(), 0, q.getZ(), q.getW());
-                            }
-                            if (boneContext.isLockZ()) {
-                                q.set(q.getX(), q.getY(), 0, q.getW());
-                            }
-                        }
-
-                        boneTransform.getRotation().set(q.multLocal(boneTransform.getRotation()));
-                        constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneTransform.toTransform());
-                    }
-                }
-
-                iterations = 0;
+        Vector3d target = new Vector3d(targetTransform.getTranslation());
+        Vector3d[] rotationVectors = new Vector3d[bones.size()];
+        BoneContext topBone = bones.get(0);
+        for (int i = 1; i <= iterations; ++i) {
+            DTransform topBoneTransform = bones.getWorldTransform(topBone);
+            Vector3d e = topBoneTransform.getTranslation().add(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
+            distanceFromTarget = e.distance(t);
+            if (distanceFromTarget <= MIN_DISTANCE) {
+                break;
             }
-        }
 
-        List<Transform> bestSolution = new ArrayList<Transform>(bones.size());
-        double bestSolutionDistance = Double.MAX_VALUE;
-        BoneContext topBone = bones.get(0);
-        for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) {
-            for (BoneContext boneContext : bones) {
-                Bone bone = boneContext.getBone();
-                DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
-                DTransform boneWorldTransform = new DTransform(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD));
+            Matrix deltaP = new Matrix(3, 1);
+            deltaP.setColumn(target.subtract(e), 0);
 
-                Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
+            Matrix J = new Matrix(3, bones.size());
+            int column = 0;
+            for (BoneContext boneContext : bones) {
+                DTransform boneWorldTransform = bones.getWorldTransform(boneContext);
                 Vector3d j = boneWorldTransform.getTranslation(); // current join position
+                Vector3d vectorFromJointToEffector = e.subtract(j);
+                rotationVectors[column] = vectorFromJointToEffector.cross(target.subtract(j)).normalize();
+                Vector3d col = rotationVectors[column].cross(vectorFromJointToEffector);
+                J.setColumn(col, column++);
+            }
+            Matrix J_1 = J.pseudoinverse();
 
-                Vector3d currentDir = e.subtractLocal(j).normalizeLocal();
-                Vector3d target = t.subtract(j).normalizeLocal();
-                double angle = currentDir.angleBetween(target);
-                if (angle != 0) {
-                    Vector3d cross = currentDir.crossLocal(target).normalizeLocal();
-                    q.fromAngleAxis(angle, cross);
-
-                    if(bone.equals(this.getOwner())) {
-                        if (boneContext.isLockX()) {
-                            q.set(0, q.getY(), q.getZ(), q.getW());
-                        }
-                        if (boneContext.isLockY()) {
-                            q.set(q.getX(), 0, q.getZ(), q.getW());
-                        }
-                        if (boneContext.isLockZ()) {
-                            q.set(q.getX(), q.getY(), 0, q.getW());
-                        }
-                    }
+            SimpleMatrix deltaThetas = J_1.mult(deltaP);
 
-                    boneWorldTransform.getRotation().set(q.multLocal(boneWorldTransform.getRotation()));
-                    constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform.toTransform());
-                } else {
-                    iterations = 0;
-                    break;
-                }
-            }
+            for (int j = 0; j < deltaThetas.numRows(); ++j) {
+                double angle = deltaThetas.get(j, 0);
+                Vector3d rotationVector = rotationVectors[j];
 
-            DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
-            Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
-            distanceFromTarget = e.distance(t);
-            
-            if(distanceFromTarget < bestSolutionDistance) {
-                bestSolutionDistance = distanceFromTarget;
-                bestSolution.clear();
-                for(BoneContext boneContext : bones) {
-                    bestSolution.add(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
+                q.fromAngleAxis(angle, rotationVector);
+                BoneContext boneContext = bones.get(j);
+                Bone bone = boneContext.getBone();
+                if (bone.equals(this.getOwner())) {
+                    if (boneContext.isLockX()) {
+                        q.set(0, q.getY(), q.getZ(), q.getW());
+                    }
+                    if (boneContext.isLockY()) {
+                        q.set(q.getX(), 0, q.getZ(), q.getW());
+                    }
+                    if (boneContext.isLockZ()) {
+                        q.set(q.getX(), q.getY(), 0, q.getW());
+                    }
                 }
+
+                DTransform boneTransform = bones.getWorldTransform(boneContext);
+                boneTransform.getRotation().set(q.mult(boneTransform.getRotation()));
+                bones.setWorldTransform(boneContext, boneTransform);
             }
         }
-        
-        // applying best solution
-        for(int i=0;i<bestSolution.size();++i) {
+
+        // applying the results
+        for (int i = bones.size() - 1; i >= 0; --i) {
             BoneContext boneContext = bones.get(i);
-            constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, bestSolution.get(i));
+            DTransform transform = bones.getWorldTransform(boneContext);
+            constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform.toTransform());
         }
+        bones.reset();
     }
 
     @Override
@@ -174,49 +133,12 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
         return "Inverse kinematics";
     }
 
-    /**
-     * @return the bone contexts of all bones that will be used in this constraint computations
-     */
-    private List<BoneContext> loadBones() {
-        List<BoneContext> bones = new ArrayList<BoneContext>();
-        Bone bone = (Bone) this.getOwner();
-        if (bone == null) {
-            return bones;
-        }
-        if (!useTail) {
-            bone = bone.getParent();
-        }
-        chainLength = 0;
-        while (bone != null) {
-            BoneContext boneContext = blenderContext.getBoneContext(bone);
-            chainLength += boneContext.getLength();
-            bones.add(boneContext);
-            alteredOmas.add(boneContext.getBoneOma());
-            if (bonesAffected != 0 && bones.size() >= bonesAffected) {
-                break;
-            }
-            // need to add spaces between bones to the chain length
-            Transform boneWorldTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
-            Vector3f boneWorldTranslation = boneWorldTransform.getTranslation();
-
-            bone = bone.getParent();
-
-            if (bone != null) {
-                boneContext = blenderContext.getBoneContext(bone);
-                Transform parentWorldTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
-                Vector3f parentWorldTranslation = parentWorldTransform.getTranslation();
-                chainLength += boneWorldTranslation.distance(parentWorldTranslation);
-            }
-        }
-        return bones;
-    }
-
     @Override
     public boolean isTrackToBeChanged() {
         if (trackToBeChanged) {
             // need to check the bone structure too (when constructor was called not all of the bones might have been loaded yet)
             // that is why it is also checked here
-            List<BoneContext> bones = this.loadBones();
+            bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, blenderContext);
             trackToBeChanged = bones.size() > 0;
         }
         return trackToBeChanged;
@@ -226,4 +148,68 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
     public boolean isTargetRequired() {
         return true;
     }
+
+    private static class BonesChain extends ArrayList<BoneContext> {
+        private static final long serialVersionUID      = -1850524345643600718L;
+
+        private Set<Long>         alteredOmas           = new HashSet<Long>();
+        private List<Matrix>    originalBonesMatrices = new ArrayList<Matrix>();
+        private List<Matrix>    bonesMatrices         = new ArrayList<Matrix>();
+
+        public BonesChain(Bone bone, boolean useTail, int bonesAffected, BlenderContext blenderContext) {
+            if (bone != null) {
+                ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
+                if (!useTail) {
+                    bone = bone.getParent();
+                }
+                while (bone != null && this.size() < bonesAffected) {
+                    BoneContext boneContext = blenderContext.getBoneContext(bone);
+                    this.add(boneContext);
+                    alteredOmas.add(boneContext.getBoneOma());
+
+                    Space space = this.size() < bonesAffected ? Space.CONSTRAINT_SPACE_LOCAL : Space.CONSTRAINT_SPACE_WORLD;
+                    Transform transform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), space);
+                    originalBonesMatrices.add(new DTransform(transform).toMatrix());
+
+                    bone = bone.getParent();
+                }
+                this.reset();
+            }
+        }
+
+        public DTransform getWorldTransform(BoneContext bone) {
+            int index = this.indexOf(bone);
+            return this.getWorldMatrix(index).toTransform();
+        }
+
+        public void setWorldTransform(BoneContext bone, DTransform transform) {
+            int index = this.indexOf(bone);
+            Matrix boneMatrix = transform.toMatrix();
+
+            if (index < this.size() - 1) {
+                // computing the current bone local transform
+                Matrix parentWorldMatrix = this.getWorldMatrix(index + 1);
+                SimpleMatrix m = parentWorldMatrix.invert().mult(boneMatrix);
+                boneMatrix = new Matrix(m);
+            }
+            bonesMatrices.set(index, boneMatrix);
+        }
+
+        public Matrix getWorldMatrix(int index) {
+            if (index == this.size() - 1) {
+                return new Matrix(bonesMatrices.get(this.size() - 1));
+            }
+
+            SimpleMatrix result = this.getWorldMatrix(index + 1);
+            result = result.mult(bonesMatrices.get(index));
+            return new Matrix(result);
+        }
+
+        public void reset() {
+            bonesMatrices.clear();
+            for (Matrix m : originalBonesMatrices) {
+                bonesMatrices.add(new Matrix(m));
+            }
+        }
+    }
 }

+ 128 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java

@@ -164,6 +164,134 @@ public final class DQuaternion implements Savable, Cloneable, java.io.Serializab
         w = 1;
     }
 
+    /**
+     * <code>norm</code> returns the norm of this quaternion. This is the dot
+     * product of this quaternion with itself.
+     *
+     * @return the norm of the quaternion.
+     */
+    public double norm() {
+        return w * w + x * x + y * y + z * z;
+    }
+    
+    public DQuaternion fromRotationMatrix(double m00, double m01, double m02,
+            double m10, double m11, double m12, double m20, double m21, double m22) {
+        // first normalize the forward (F), up (U) and side (S) vectors of the rotation matrix
+        // so that the scale does not affect the rotation
+        double lengthSquared = m00 * m00 + m10 * m10 + m20 * m20;
+        if (lengthSquared != 1f && lengthSquared != 0f) {
+            lengthSquared = 1.0 / Math.sqrt(lengthSquared);
+            m00 *= lengthSquared;
+            m10 *= lengthSquared;
+            m20 *= lengthSquared;
+        }
+        lengthSquared = m01 * m01 + m11 * m11 + m21 * m21;
+        if (lengthSquared != 1 && lengthSquared != 0f) {
+            lengthSquared = 1.0 / Math.sqrt(lengthSquared);
+            m01 *= lengthSquared;
+            m11 *= lengthSquared;
+            m21 *= lengthSquared;
+        }
+        lengthSquared = m02 * m02 + m12 * m12 + m22 * m22;
+        if (lengthSquared != 1f && lengthSquared != 0f) {
+            lengthSquared = 1.0 / Math.sqrt(lengthSquared);
+            m02 *= lengthSquared;
+            m12 *= lengthSquared;
+            m22 *= lengthSquared;
+        }
+
+        // Use the Graphics Gems code, from 
+        // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z
+        // *NOT* the "Matrix and Quaternions FAQ", which has errors!
+
+        // the trace is the sum of the diagonal elements; see
+        // http://mathworld.wolfram.com/MatrixTrace.html
+        double t = m00 + m11 + m22;
+
+        // we protect the division by s by ensuring that s>=1
+        if (t >= 0) { // |w| >= .5
+            double s = Math.sqrt(t + 1); // |s|>=1 ...
+            w = 0.5f * s;
+            s = 0.5f / s;                 // so this division isn't bad
+            x = (m21 - m12) * s;
+            y = (m02 - m20) * s;
+            z = (m10 - m01) * s;
+        } else if (m00 > m11 && m00 > m22) {
+            double s = Math.sqrt(1.0 + m00 - m11 - m22); // |s|>=1
+            x = s * 0.5f; // |x| >= .5
+            s = 0.5f / s;
+            y = (m10 + m01) * s;
+            z = (m02 + m20) * s;
+            w = (m21 - m12) * s;
+        } else if (m11 > m22) {
+            double s = Math.sqrt(1.0 + m11 - m00 - m22); // |s|>=1
+            y = s * 0.5f; // |y| >= .5
+            s = 0.5f / s;
+            x = (m10 + m01) * s;
+            z = (m21 + m12) * s;
+            w = (m02 - m20) * s;
+        } else {
+            double s = Math.sqrt(1.0 + m22 - m00 - m11); // |s|>=1
+            z = s * 0.5f; // |z| >= .5
+            s = 0.5f / s;
+            x = (m02 + m20) * s;
+            y = (m21 + m12) * s;
+            w = (m10 - m01) * s;
+        }
+
+        return this;
+    }
+    
+    /**
+     * <code>toRotationMatrix</code> converts this quaternion to a rotational
+     * matrix. The result is stored in result. 4th row and 4th column values are
+     * untouched. Note: the result is created from a normalized version of this quat.
+     * 
+     * @param result
+     *            The Matrix4f to store the result in.
+     * @return the rotation matrix representation of this quaternion.
+     */
+    public Matrix toRotationMatrix(Matrix result) {
+        Vector3d originalScale = new Vector3d();
+        
+        result.toScaleVector(originalScale);
+        result.setScale(1, 1, 1);
+        double norm = this.norm();
+        // we explicitly test norm against one here, saving a division
+        // at the cost of a test and branch.  Is it worth it?
+        double s = norm == 1f ? 2f : norm > 0f ? 2f / norm : 0;
+
+        // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
+        // will be used 2-4 times each.
+        double xs = x * s;
+        double ys = y * s;
+        double zs = z * s;
+        double xx = x * xs;
+        double xy = x * ys;
+        double xz = x * zs;
+        double xw = w * xs;
+        double yy = y * ys;
+        double yz = y * zs;
+        double yw = w * ys;
+        double zz = z * zs;
+        double zw = w * zs;
+
+        // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
+        result.set(0, 0, 1 - (yy + zz));
+        result.set(0, 1, xy - zw);
+        result.set(0, 2, xz + yw);
+        result.set(1, 0, xy + zw);
+        result.set(1, 1, 1 - (xx + zz));
+        result.set(1, 2, yz - xw);
+        result.set(2, 0, xz - yw);
+        result.set(2, 1, yz + xw);
+        result.set(2, 2, 1 - (xx + yy));
+        
+        result.setScale(originalScale);
+        
+        return result;
+    }
+    
     /**
      * <code>fromAngleAxis</code> sets this quaternion to the values specified
      * by an angle and an axis of rotation. This method creates an object, so

+ 22 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java

@@ -31,11 +31,15 @@
  */
 package com.jme3.scene.plugins.blender.math;
 
-import com.jme3.export.*;
-import com.jme3.math.Transform;
-
 import java.io.IOException;
 
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.Transform;
+
 /**
  * Started Date: Jul 16, 2004<br>
  * <br>
@@ -57,6 +61,12 @@ public final class DTransform implements Savable, Cloneable, java.io.Serializabl
     private Vector3d          translation;
     private Vector3d          scale;
 
+    public DTransform() {
+        translation = new Vector3d();
+        rotation = new DQuaternion();
+        scale = new Vector3d();
+    }
+    
     public DTransform(Transform transform) {
         translation = new Vector3d(transform.getTranslation());
         rotation = new DQuaternion(transform.getRotation());
@@ -66,7 +76,15 @@ public final class DTransform implements Savable, Cloneable, java.io.Serializabl
     public Transform toTransform() {
         return new Transform(translation.toVector3f(), rotation.toQuaternion(), scale.toVector3f());
     }
-
+    
+    public Matrix toMatrix() {
+        Matrix m = Matrix.identity(4);
+        m.setTranslation(translation);
+        m.setRotationQuaternion(rotation);
+        m.setScale(scale);
+        return m;
+    }
+    
     /**
      * Sets this translation to the given value.
      * @param trans

+ 214 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java

@@ -0,0 +1,214 @@
+package com.jme3.scene.plugins.blender.math;
+
+import java.text.DecimalFormat;
+
+import org.ejml.ops.CommonOps;
+import org.ejml.simple.SimpleMatrix;
+import org.ejml.simple.SimpleSVD;
+
+import com.jme3.math.FastMath;
+
+/**
+ * Encapsulates a 4x4 matrix
+ *
+ *
+ */
+public class Matrix extends SimpleMatrix {
+    private static final long serialVersionUID = 2396600537315902559L;
+
+    public Matrix(int rows, int cols) {
+        super(rows, cols);
+    }
+
+    /**
+     * Copy constructor
+     */
+    public Matrix(SimpleMatrix m) {
+        super(m);
+    }
+    
+    public Matrix(double[][] data) {
+        super(data);
+    }
+    
+    public static Matrix identity(int size) {
+        Matrix result = new Matrix(size, size);
+        CommonOps.setIdentity(result.mat);
+        return result;
+    }
+    
+    public Matrix pseudoinverse() {
+        return this.pseudoinverse(1);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public Matrix pseudoinverse(double lambda) {
+        SimpleSVD<SimpleMatrix> simpleSVD = this.svd();
+        
+        SimpleMatrix U = simpleSVD.getU();
+        SimpleMatrix S = simpleSVD.getW();
+        SimpleMatrix V = simpleSVD.getV();
+        
+        int N = Math.min(this.numRows(),this.numCols());
+        double maxSingular = 0;
+        for( int i = 0; i < N; i++ ) {
+            if( S.get(i, i) > maxSingular ) {
+                maxSingular = S.get(i, i);
+            }
+        }
+        
+        double tolerance = FastMath.DBL_EPSILON * Math.max(this.numRows(),this.numCols()) * maxSingular;
+        for(int i=0;i<Math.min(S.numRows(), S.numCols());++i) {
+            double a = S.get(i, i);
+            if(a <= tolerance) {
+                a = 0;
+            } else {
+                a = a/(a * a + lambda * lambda);
+            }
+            S.set(i, i, a);
+        }
+        return new Matrix(V.mult(S.transpose()).mult(U.transpose()));
+    }
+    
+    public void setColumn(Vector3d col, int column) {
+        this.setColumn(column, 0, col.x, col.y, col.z);
+    }
+    
+    /**
+     * Just for some debug informations in order to compare the results with the scilab computation program.
+     * @param name the name of the matrix
+     * @param m the matrix to print out
+     * @return the String format of the matrix to easily input it to Scilab
+     */
+    public String toScilabString(String name, SimpleMatrix m) {
+        String result = name + " = [";
+        
+        for(int i=0;i<m.numRows();++i) {
+            for(int j=0;j<m.numCols();++j) {
+                result += m.get(i, j) + " ";
+            }
+            result += ";";
+        }
+        
+        return result;
+    }
+    
+    /**
+     * @return a String representation of the matrix
+     */
+    @Override
+    public String toString() {
+        DecimalFormat df = new DecimalFormat("#.0000");
+        StringBuilder buf = new StringBuilder();
+        for (int r = 0; r < this.numRows(); ++r) {
+            buf.append("\n| ");
+            for (int c = 0; c < this.numCols(); ++c) {
+                buf.append(df.format(this.get(r, c))).append(' ');
+            }
+            buf.append('|');
+        }
+        return buf.toString();
+    }
+    
+    public void setTranslation(Vector3d translation) {
+        this.setColumn(translation, 3);
+    }
+    
+    /**
+     * Sets the scale.
+     * 
+     * @param scale
+     *            the scale vector to set
+     */
+    public void setScale(Vector3d scale) {
+        this.setScale(scale.x, scale.y, scale.z);
+    }
+    
+    /**
+     * Sets the scale.
+     * 
+     * @param x
+     *            the X scale
+     * @param y
+     *            the Y scale
+     * @param z
+     *            the Z scale
+     */
+    public void setScale(double x, double y, double z) {
+        Vector3d vect1 = new Vector3d(this.get(0, 0), this.get(1, 0), this.get(2, 0));
+        vect1.normalizeLocal().multLocal(x);
+        this.set(0, 0, vect1.x);
+        this.set(1, 0, vect1.y);
+        this.set(2, 0, vect1.z);
+
+        vect1.set(this.get(0, 1), this.get(1, 1), this.get(2, 1));
+        vect1.normalizeLocal().multLocal(y);
+        this.set(0, 1, vect1.x);
+        this.set(1, 1, vect1.y);
+        this.set(2, 1, vect1.z);
+
+        vect1.set(this.get(0, 2), this.get(1, 2), this.get(2, 2));
+        vect1.normalizeLocal().multLocal(z);
+        this.set(0, 2, vect1.x);
+        this.set(1, 2, vect1.y);
+        this.set(2, 2, vect1.z);
+    }
+    
+    /**
+     * <code>setRotationQuaternion</code> builds a rotation from a
+     * <code>Quaternion</code>.
+     * 
+     * @param quat
+     *            the quaternion to build the rotation from.
+     * @throws NullPointerException
+     *             if quat is null.
+     */
+    public void setRotationQuaternion(DQuaternion quat) {
+        quat.toRotationMatrix(this);
+    }
+    
+    public DTransform toTransform() {
+        DTransform result = new DTransform();
+        result.setTranslation(this.toTranslationVector());
+        result.setRotation(this.toRotationQuat());
+        result.setScale(this.toScaleVector());
+        return result;
+    }
+    
+    public Vector3d toTranslationVector() {
+        return new Vector3d(this.get(0, 3), this.get(1, 3), this.get(2, 3));
+    }
+    
+    public DQuaternion toRotationQuat() {
+        DQuaternion quat = new DQuaternion();
+        quat.fromRotationMatrix(this.get(0, 0), this.get(0, 1), this.get(0, 2), this.get(1, 0), this.get(1, 1), this.get(1, 2), this.get(2, 0), this.get(2, 1), this.get(2, 2));
+        return quat;
+    }
+    
+    /**
+     * Retreives the scale vector from the matrix and stores it into a given
+     * vector.
+     * 
+     * @param the
+     *            vector where the scale will be stored
+     */
+    public Vector3d toScaleVector() {
+        Vector3d result = new Vector3d();
+        this.toScaleVector(result);
+        return result;
+    }
+    
+    /**
+     * Retreives the scale vector from the matrix and stores it into a given
+     * vector.
+     * 
+     * @param the
+     *            vector where the scale will be stored
+     */
+    public void toScaleVector(Vector3d vector) {
+        double scaleX = Math.sqrt(this.get(0, 0) * this.get(0, 0) + this.get(1, 0) * this.get(1, 0) + this.get(2, 0) * this.get(2, 0));
+        double scaleY = Math.sqrt(this.get(0, 1) * this.get(0, 1) + this.get(1, 1) * this.get(1, 1) + this.get(2, 1) * this.get(2, 1));
+        double scaleZ = Math.sqrt(this.get(0, 2) * this.get(0, 2) + this.get(1, 2) * this.get(1, 2) + this.get(2, 2) * this.get(2, 2));
+        vector.set(scaleX, scaleY, scaleZ);
+    }
+}

+ 1 - 1
jme3-core/src/main/java/com/jme3/light/SpotLight.java

@@ -63,7 +63,7 @@ public class SpotLight extends Light {
     protected float spotInnerAngle = FastMath.QUARTER_PI / 8;
     protected float spotOuterAngle = FastMath.QUARTER_PI / 6;
     protected float spotRange = 100;
-    protected float invSpotRange = 1 / 100;
+    protected float invSpotRange = 1f / 100;
     protected float packedAngleCos=0;
     
     protected float outerAngleCosSqr, outerAngleSinSqr;

+ 2 - 0
jme3-core/src/main/java/com/jme3/renderer/Limits.java

@@ -75,4 +75,6 @@ public enum Limits {
     ColorTextureSamples,
     
     DepthTextureSamples,
+
+    VertexUniformVectors,
 }

+ 4 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java

@@ -74,6 +74,7 @@ public interface GL {
 	public static final int GL_FRONT_AND_BACK = 0x408;
 	public static final int GL_GEQUAL = 0x206;
 	public static final int GL_GREATER = 0x204;
+        public static final int GL_GREEN = 0x1904;
 	public static final int GL_INCR = 0x1E02;
 	public static final int GL_INCR_WRAP = 0x8507;
 	public static final int GL_INFO_LOG_LENGTH = 0x8B84;
@@ -99,6 +100,8 @@ public interface GL {
 	public static final int GL_MAX_TEXTURE_SIZE = 0xD33;
 	public static final int GL_MAX_VERTEX_ATTRIBS = 0x8869;
 	public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C;
+	public static final int GL_MAX_VERTEX_UNIFORM_COMPONENTS  = 0x8B4A;
+	public static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB;
 	public static final int GL_MIRRORED_REPEAT = 0x8370;
 	public static final int GL_NEAREST = 0x2600;
 	public static final int GL_NEAREST_MIPMAP_LINEAR = 0x2702;
@@ -114,6 +117,7 @@ public interface GL {
         public static final int GL_OUT_OF_MEMORY = 0x505;
 	public static final int GL_POINTS = 0x0;
 	public static final int GL_POLYGON_OFFSET_FILL = 0x8037;
+        public static final int GL_RED = 0x1903;
         public static final int GL_RENDERER = 0x1F01;
 	public static final int GL_REPEAT = 0x2901;
 	public static final int GL_REPLACE = 0x1E01;

+ 12 - 1
jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java

@@ -34,7 +34,7 @@ package com.jme3.renderer.opengl;
 import java.nio.IntBuffer;
 
 /**
- * GL functions only available on vanilla desktop OpenGL 3.0.
+ * GL functions only available on vanilla desktop OpenGL 3.0+.
  *
  * @author Kirill Vainer
  */
@@ -43,6 +43,17 @@ public interface GL3 extends GL2 {
     public static final int GL_DEPTH_STENCIL_ATTACHMENT = 0x821A;
     public static final int GL_GEOMETRY_SHADER = 0x8DD9;
     public static final int GL_NUM_EXTENSIONS = 0x821D;
+    public static final int GL_R8 = 0x8229;
+    public static final int GL_R16F = 0x822D;
+    public static final int GL_R32F = 0x822E;
+    public static final int GL_RG16F = 0x822F;
+    public static final int GL_RG32F = 0x8230;
+    public static final int GL_RG = 0x8227;
+    public static final int GL_RG8 = 0x822B;
+    public static final int GL_TEXTURE_SWIZZLE_A = 0x8E45;
+    public static final int GL_TEXTURE_SWIZZLE_B = 0x8E44;
+    public static final int GL_TEXTURE_SWIZZLE_G = 0x8E43;
+    public static final int GL_TEXTURE_SWIZZLE_R = 0x8E42;
     
     public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+
     public void glBindVertexArray(int param1); /// GL3+

+ 1 - 1
jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java

@@ -34,7 +34,7 @@ package com.jme3.renderer.opengl;
 import java.nio.IntBuffer;
 
 /**
- * GL functions only available on vanilla desktop OpenGL 3.0.
+ * GL functions only available on vanilla desktop OpenGL 4.0.
  * 
  * @author Kirill Vainer
  */

+ 21 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java

@@ -42,6 +42,7 @@ public final class GLImageFormat {
     public final int format;
     public final int dataType;
     public final boolean compressed;
+    public final boolean swizzleRequired;
 
     /**
      * Constructor for formats.
@@ -55,6 +56,7 @@ public final class GLImageFormat {
         this.format = format;
         this.dataType = dataType;
         this.compressed = false;
+        this.swizzleRequired = false;
     }
     
     /**
@@ -63,11 +65,30 @@ public final class GLImageFormat {
      * @param internalFormat OpenGL internal format
      * @param format OpenGL format
      * @param dataType OpenGL datatype
+     * @param compressed Format is compressed
      */
     public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) {
         this.internalFormat = internalFormat;
         this.format = format;
         this.dataType = dataType;
         this.compressed = compressed;
+        this.swizzleRequired = false;
+    }
+    
+    /**
+     * Constructor for formats.
+     * 
+     * @param internalFormat OpenGL internal format
+     * @param format OpenGL format
+     * @param dataType OpenGL datatype
+     * @param compressed Format is compressed
+     * @param swizzleRequired Need to use texture swizzle to upload texture
+     */
+    public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed, boolean swizzleRequired) {
+        this.internalFormat = internalFormat;
+        this.format = format;
+        this.dataType = dataType;
+        this.compressed = compressed;
+        this.swizzleRequired = swizzleRequired;
     }
 }

+ 28 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java

@@ -52,6 +52,13 @@ public final class GLImageFormats {
         formatToGL[0][format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType);
     }
     
+    private static void formatSwiz(GLImageFormat[][] formatToGL, Image.Format format, 
+                                   int glInternalFormat, 
+                                   int glFormat, 
+                                   int glDataType){
+        formatToGL[0][format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, false, true);
+    }
+    
     private static void formatSrgb(GLImageFormat[][] formatToGL, Image.Format format, 
                                    int glInternalFormat, 
                                    int glFormat, 
@@ -60,6 +67,14 @@ public final class GLImageFormats {
         formatToGL[1][format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType);
     }
     
+    private static void formatSrgbSwiz(GLImageFormat[][] formatToGL, Image.Format format, 
+                                       int glInternalFormat, 
+                                       int glFormat, 
+                                       int glDataType)
+    {
+        formatToGL[1][format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, false, true);
+    }
+    
     private static void formatComp(GLImageFormat[][] formatToGL, Image.Format format, 
                                    int glCompressedFormat,
                                    int glFormat, 
@@ -88,6 +103,19 @@ public final class GLImageFormats {
     public static GLImageFormat[][] getFormatsForCaps(EnumSet<Caps> caps) {
         GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length];
         
+        // Core Profile Formats (supported by both OpenGL Core 3.3 and OpenGL ES 3.0+)
+        if (caps.contains(Caps.CoreProfile)) {
+            formatSwiz(formatToGL,     Format.Alpha8,               GL3.GL_R8,                 GL.GL_RED,       GL.GL_UNSIGNED_BYTE);
+            formatSwiz(formatToGL,     Format.Luminance8,           GL3.GL_R8,                 GL.GL_RED,       GL.GL_UNSIGNED_BYTE);
+            formatSwiz(formatToGL,     Format.Luminance8Alpha8,     GL3.GL_RG8,                GL3.GL_RG,       GL.GL_UNSIGNED_BYTE);
+            formatSwiz(formatToGL,     Format.Luminance16F,         GL3.GL_R16F,               GL.GL_RED,       GLExt.GL_HALF_FLOAT_ARB);
+            formatSwiz(formatToGL,     Format.Luminance32F,         GL3.GL_R32F,               GL.GL_RED,       GL.GL_FLOAT);
+            formatSwiz(formatToGL,     Format.Luminance16FAlpha16F, GL3.GL_RG16F,              GL3.GL_RG,       GLExt.GL_HALF_FLOAT_ARB);
+            
+            formatSrgbSwiz(formatToGL, Format.Luminance8,           GLExt.GL_SRGB8_EXT,        GL.GL_RED,       GL.GL_UNSIGNED_BYTE);
+            formatSrgbSwiz(formatToGL, Format.Luminance8Alpha8,     GLExt.GL_SRGB8_ALPHA8_EXT, GL3.GL_RG,       GL.GL_UNSIGNED_BYTE);
+        }
+        
         if (caps.contains(Caps.OpenGL20)) {
             if (!caps.contains(Caps.CoreProfile)) {
                 format(formatToGL, Format.Alpha8,           GL2.GL_ALPHA8,            GL.GL_ALPHA,           GL.GL_UNSIGNED_BYTE);

+ 159 - 155
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -73,7 +73,7 @@ public class GLRenderer implements Renderer {
     private static final Logger logger = Logger.getLogger(GLRenderer.class.getName());
     private static final boolean VALIDATE_SHADER = false;
     private static final Pattern GLVERSION_PATTERN = Pattern.compile(".*?(\\d+)\\.(\\d+).*");
-    
+
     private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250);
     private final StringBuilder stringBuf = new StringBuilder(250);
     private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1);
@@ -83,7 +83,7 @@ public class GLRenderer implements Renderer {
     private final NativeObjectManager objManager = new NativeObjectManager();
     private final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);
     private final EnumMap<Limits, Integer> limits = new EnumMap<Limits, Integer>(Limits.class);
-    
+
     private FrameBuffer mainFbOverride = null;
     private final Statistics statistics = new Statistics();
     private int vpX, vpY, vpW, vpH;
@@ -98,7 +98,7 @@ public class GLRenderer implements Renderer {
     private final GLExt glext;
     private final GLFbo glfbo;
     private final TextureUtil texUtil;
-    
+
     public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
         this.gl = gl;
         this.gl2 = gl instanceof GL2 ? (GL2)gl : null;
@@ -106,7 +106,7 @@ public class GLRenderer implements Renderer {
         this.gl4 = gl instanceof GL4 ? (GL4)gl : null;
         this.glfbo = glfbo;
         this.glext = glext;
-        this.texUtil = new TextureUtil(gl, gl2, glext, context);
+        this.texUtil = new TextureUtil(gl, gl2, glext);
     }
 
     @Override
@@ -118,7 +118,7 @@ public class GLRenderer implements Renderer {
     public EnumSet<Caps> getCaps() {
         return caps;
     }
-    
+
     // Not making public yet ...
     public EnumMap<Limits, Integer> getLimits() {
         return limits;
@@ -140,7 +140,7 @@ public class GLRenderer implements Renderer {
         }
         return extensionSet;
     }
-    
+
     public static int extractVersion(String version) {
         Matcher m = GLVERSION_PATTERN.matcher(version);
         if (m.matches()) {
@@ -160,17 +160,17 @@ public class GLRenderer implements Renderer {
     private boolean hasExtension(String extensionName) {
         return extensions.contains(extensionName);
     }
-    
+
     private void loadCapabilitiesES() {
         caps.add(Caps.GLSL100);
         caps.add(Caps.OpenGLES20);
-        
+
         // Important: Do not add OpenGL20 - that's the desktop capability!
     }
-    
+
     private void loadCapabilitiesGL2() {
         int oglVer = extractVersion(gl.glGetString(GL.GL_VERSION));
-        
+
         if (oglVer >= 200) {
             caps.add(Caps.OpenGL20);
             if (oglVer >= 210) {
@@ -194,9 +194,9 @@ public class GLRenderer implements Renderer {
                 }
             }
         }
-        
+
         int glslVer = extractVersion(gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION));
-        
+
         switch (glslVer) {
             default:
                 if (glslVer < 400) {
@@ -222,34 +222,34 @@ public class GLRenderer implements Renderer {
                 caps.add(Caps.GLSL100);
                 break;
         }
-        
+
         // Workaround, always assume we support GLSL100 & GLSL110
         // Supporting OpenGL 2.0 means supporting GLSL 1.10.
         caps.add(Caps.GLSL110);
         caps.add(Caps.GLSL100);
-        
+
         // Fix issue in TestRenderToMemory when GL.GL_FRONT is the main
         // buffer being used.
         context.initialDrawBuf = getInteger(GL2.GL_DRAW_BUFFER);
         context.initialReadBuf = getInteger(GL2.GL_READ_BUFFER);
-        
+
         // XXX: This has to be GL.GL_BACK for canvas on Mac
         // Since initialDrawBuf is GL.GL_FRONT for pbuffer, gotta
         // change this value later on ...
 //        initialDrawBuf = GL.GL_BACK;
 //        initialReadBuf = GL.GL_BACK;
     }
-    
+
     private void loadCapabilitiesCommon() {
         extensions = loadExtensions();
-        
+
         limits.put(Limits.VertexTextureUnits, getInteger(GL.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS));
         if (limits.get(Limits.VertexTextureUnits) > 0) {
             caps.add(Caps.VertexTextureFetch);
         }
 
         limits.put(Limits.FragmentTextureUnits, getInteger(GL.GL_MAX_TEXTURE_IMAGE_UNITS));
-        
+
 //        gl.glGetInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16);
 //        vertexUniforms = intBuf16.get(0);
 //        logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms);
@@ -257,62 +257,66 @@ public class GLRenderer implements Renderer {
 //        gl.glGetInteger(GL.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16);
 //        fragUniforms = intBuf16.get(0);
 //        logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms);
-
+        if (caps.contains(Caps.OpenGLES20)) {
+            limits.put(Limits.VertexUniformVectors, getInteger(GL.GL_MAX_VERTEX_UNIFORM_VECTORS));
+        } else {
+            limits.put(Limits.VertexUniformVectors, getInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS) / 4);
+        }
         limits.put(Limits.VertexAttributes, getInteger(GL.GL_MAX_VERTEX_ATTRIBS));
         limits.put(Limits.TextureSize, getInteger(GL.GL_MAX_TEXTURE_SIZE));
         limits.put(Limits.CubemapSize, getInteger(GL.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
 
-        if (hasExtension("GL_ARB_draw_instanced") && 
-            hasExtension("GL_ARB_instanced_arrays")) {
+        if (hasExtension("GL_ARB_draw_instanced") &&
+                hasExtension("GL_ARB_instanced_arrays")) {
             caps.add(Caps.MeshInstancing);
         }
 
         if (hasExtension("GL_OES_element_index_uint") || gl2 != null) {
             caps.add(Caps.IntegerIndexBuffer);
         }
-        
+
         if (hasExtension("GL_ARB_texture_buffer_object")) {
             caps.add(Caps.TextureBuffer);
         }
-        
+
         // == texture format extensions ==
-        
+
         boolean hasFloatTexture;
 
         hasFloatTexture = hasExtension("GL_OES_texture_half_float") &&
-                          hasExtension("GL_OES_texture_float");
-        
+                hasExtension("GL_OES_texture_float");
+
         if (!hasFloatTexture) {
             hasFloatTexture = hasExtension("GL_ARB_texture_float") &&
-                              hasExtension("GL_ARB_half_float_pixel");
-            
+                    hasExtension("GL_ARB_half_float_pixel");
+
             if (!hasFloatTexture) {
                 hasFloatTexture = caps.contains(Caps.OpenGL30);
             }
         }
-        
+
         if (hasFloatTexture) {
             caps.add(Caps.FloatTexture);
         }
-        
+
         if (hasExtension("GL_OES_depth_texture") || gl2 != null) {
             caps.add(Caps.DepthTexture);
-            
+
             // TODO: GL_OES_depth24
         }
-        
-        if (hasExtension("GL_OES_rgb8_rgba8") || 
-            hasExtension("GL_ARM_rgba8") || 
-            hasExtension("GL_EXT_texture_format_BGRA8888")) {
+
+        if (hasExtension("GL_OES_rgb8_rgba8") ||
+                hasExtension("GL_ARM_rgba8") ||
+                hasExtension("GL_EXT_texture_format_BGRA8888")) {
             caps.add(Caps.Rgba8);
         }
-        
+
         if (caps.contains(Caps.OpenGL30) || hasExtension("GL_OES_packed_depth_stencil")) {
             caps.add(Caps.PackedDepthStencilBuffer);
         }
 
         if (hasExtension("GL_ARB_color_buffer_float") &&
-            hasExtension("GL_ARB_half_float_pixel")) {
+                hasExtension("GL_ARB_half_float_pixel")) {
             // XXX: Require both 16 and 32 bit float support for FloatColorBuffer.
             caps.add(Caps.FloatColorBuffer);
         }
@@ -321,44 +325,44 @@ public class GLRenderer implements Renderer {
             caps.add(Caps.FloatDepthBuffer);
         }
 
-        if ((hasExtension("GL_EXT_packed_float") && hasFloatTexture) || 
-             caps.contains(Caps.OpenGL30)) {
+        if ((hasExtension("GL_EXT_packed_float") && hasFloatTexture) ||
+                caps.contains(Caps.OpenGL30)) {
             // Either OpenGL3 is available or both packed_float & half_float_pixel.
             caps.add(Caps.PackedFloatColorBuffer);
             caps.add(Caps.PackedFloatTexture);
         }
-        
+
         if (hasExtension("GL_EXT_texture_shared_exponent") || caps.contains(Caps.OpenGL30)) {
             caps.add(Caps.SharedExponentTexture);
         }
-        
+
         if (hasExtension("GL_EXT_texture_compression_s3tc")) {
             caps.add(Caps.TextureCompressionS3TC);
         }
-        
+
         if (hasExtension("GL_ARB_ES3_compatibility")) {
             caps.add(Caps.TextureCompressionETC2);
             caps.add(Caps.TextureCompressionETC1);
         } else if (hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) {
             caps.add(Caps.TextureCompressionETC1);
         }
-        
+
         // == end texture format extensions ==
-        
+
         if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30)) {
             caps.add(Caps.VertexBufferArray);
         }
 
-        if (hasExtension("GL_ARB_texture_non_power_of_two") || 
-            hasExtension("GL_OES_texture_npot") ||
-            caps.contains(Caps.OpenGL30)) {
+        if (hasExtension("GL_ARB_texture_non_power_of_two") ||
+                hasExtension("GL_OES_texture_npot") ||
+                caps.contains(Caps.OpenGL30)) {
             caps.add(Caps.NonPowerOfTwoTextures);
         } else {
             logger.log(Level.WARNING, "Your graphics card does not "
-                                    + "support non-power-of-2 textures. "
-                                    + "Some features might not work.");
+                    + "support non-power-of-2 textures. "
+                    + "Some features might not work.");
         }
-        
+
         if (caps.contains(Caps.OpenGLES20)) {
             // OpenGL ES 2 has some limited support for NPOT textures
             caps.add(Caps.PartialNonPowerOfTwoTextures);
@@ -374,14 +378,14 @@ public class GLRenderer implements Renderer {
 
         if (hasExtension("GL_EXT_framebuffer_object") || gl3 != null) {
             caps.add(Caps.FrameBuffer);
-            
+
             limits.put(Limits.RenderBufferSize, getInteger(GLFbo.GL_MAX_RENDERBUFFER_SIZE_EXT));
             limits.put(Limits.FrameBufferAttachments, getInteger(GLFbo.GL_MAX_COLOR_ATTACHMENTS_EXT));
-            
+
             if (hasExtension("GL_EXT_framebuffer_blit")) {
                 caps.add(Caps.FrameBufferBlit);
             }
-            
+
             if (hasExtension("GL_EXT_framebuffer_multisample")) {
                 caps.add(Caps.FrameBufferMultisample);
                 limits.put(Limits.FrameBufferSamples, getInteger(GLExt.GL_MAX_SAMPLES_EXT));
@@ -419,10 +423,10 @@ public class GLRenderer implements Renderer {
             }
             caps.add(Caps.Multisample);
         }
-        
+
         // Supports sRGB pipeline.
-        if ( (hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) 
-           || caps.contains(Caps.OpenGL30) ) {
+        if ( (hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB"))
+                || caps.contains(Caps.OpenGL30) ) {
             caps.add(Caps.Srgb);
         }
 
@@ -430,33 +434,33 @@ public class GLRenderer implements Renderer {
         if (hasExtension("GL_ARB_seamless_cube_map") || caps.contains(Caps.OpenGL32)) {
             caps.add(Caps.SeamlessCubemap);
         }
-        
+
         if (caps.contains(Caps.OpenGL32) && !hasExtension("GL_ARB_compatibility")) {
             caps.add(Caps.CoreProfile);
         }
-        
+
         if (hasExtension("GL_ARB_get_program_binary")) {
             int binaryFormats = getInteger(GLExt.GL_NUM_PROGRAM_BINARY_FORMATS);
             if (binaryFormats > 0) {
                 caps.add(Caps.BinaryShader);
             }
         }
-        
+
         // Print context information
         logger.log(Level.INFO, "OpenGL Renderer Information\n" +
-                               " * Vendor: {0}\n" +
-                               " * Renderer: {1}\n" +
-                               " * OpenGL Version: {2}\n" +
-                               " * GLSL Version: {3}\n" +
-                               " * Profile: {4}",
-                               new Object[]{ 
-                                   gl.glGetString(GL.GL_VENDOR), 
-                                   gl.glGetString(GL.GL_RENDERER),
-                                   gl.glGetString(GL.GL_VERSION),
-                                   gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION),
-                                   caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility"
-                               });
-        
+                        " * Vendor: {0}\n" +
+                        " * Renderer: {1}\n" +
+                        " * OpenGL Version: {2}\n" +
+                        " * GLSL Version: {3}\n" +
+                        " * Profile: {4}",
+                new Object[]{
+                        gl.glGetString(GL.GL_VENDOR),
+                        gl.glGetString(GL.GL_RENDERER),
+                        gl.glGetString(GL.GL_VERSION),
+                        gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION),
+                        caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility"
+                });
+
         // Print capabilities (if fine logging is enabled)
         if (logger.isLoggable(Level.FINE)) {
             StringBuilder sb = new StringBuilder();
@@ -467,10 +471,10 @@ public class GLRenderer implements Renderer {
             }
             logger.log(Level.FINE, sb.toString());
         }
-        
+
         texUtil.initialize(caps);
     }
-    
+
     private void loadCapabilities() {
         if (gl2 != null) {
             loadCapabilitiesGL2();
@@ -479,31 +483,31 @@ public class GLRenderer implements Renderer {
         }
         loadCapabilitiesCommon();
     }
-    
+
     private int getInteger(int en) {
         intBuf16.clear();
         gl.glGetInteger(en, intBuf16);
         return intBuf16.get(0);
     }
-    
+
     private boolean getBoolean(int en) {
         gl.glGetBoolean(en, nameBuf);
         return nameBuf.get(0) != (byte)0;
     }
-    
+
     @SuppressWarnings("fallthrough")
     public void initialize() {
         loadCapabilities();
-        
+
         // Initialize default state..
         gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
-        
+
         if (caps.contains(Caps.CoreProfile)) {
             // Core Profile requires VAO to be bound.
             gl3.glGenVertexArrays(intBuf16);
             int vaoId = intBuf16.get(0);
             gl3.glBindVertexArray(vaoId);
-        } 
+        }
         if (gl2 != null) {
             gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
             if (!caps.contains(Caps.CoreProfile)) {
@@ -536,8 +540,8 @@ public class GLRenderer implements Renderer {
     }
 
     /*********************************************************************\
-    |* Render State                                                      *|
-    \*********************************************************************/
+     |* Render State                                                      *|
+     \*********************************************************************/
     public void setDepthRange(float start, float end) {
         gl.glDepthRange(start, end);
     }
@@ -602,7 +606,7 @@ public class GLRenderer implements Renderer {
         }
 
         if (state.isDepthTest() && !context.depthTestEnabled) {
-            gl.glEnable(GL.GL_DEPTH_TEST);      
+            gl.glEnable(GL.GL_DEPTH_TEST);
             gl.glDepthFunc(convertTestFunction(context.depthFunc));
             context.depthTestEnabled = true;
         } else if (!state.isDepthTest() && context.depthTestEnabled) {
@@ -714,7 +718,7 @@ public class GLRenderer implements Renderer {
                     case Color:
                     case Screen:
                         gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
-                        break;       
+                        break;
                     case Exclusion:
                         gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ONE_MINUS_SRC_COLOR);
                         break;
@@ -815,8 +819,8 @@ public class GLRenderer implements Renderer {
     }
 
     /*********************************************************************\
-    |* Camera and World transforms                                       *|
-    \*********************************************************************/
+     |* Camera and World transforms                                       *|
+     \*********************************************************************/
     public void setViewPort(int x, int y, int w, int h) {
         if (x != vpX || vpY != y || vpW != w || vpH != h) {
             gl.glViewport(x, y, w, h);
@@ -859,8 +863,8 @@ public class GLRenderer implements Renderer {
     }
 
     /*********************************************************************\
-    |* Shaders                                                           *|
-    \*********************************************************************/
+     |* Shaders                                                           *|
+     \*********************************************************************/
     protected void updateUniformLocation(Shader shader, Uniform uniform) {
         int loc = gl.glGetUniformLocation(shader.getId(), uniform.getName());
         if (loc < 0) {
@@ -1040,12 +1044,12 @@ public class GLRenderer implements Renderer {
 
         boolean gles2 = caps.contains(Caps.OpenGLES20);
         String language = source.getLanguage();
-        
+
         if (gles2 && !language.equals("GLSL100")) {
             throw new RendererException("This shader cannot run in OpenGL ES 2. "
-                                      + "Only GLSL 1.00 shaders are supported.");
+                    + "Only GLSL 1.00 shaders are supported.");
         }
-        
+
         // Upload shader source.
         // Merge the defines and source code.
         stringBuf.setLength(0);
@@ -1072,14 +1076,14 @@ public class GLRenderer implements Renderer {
                 }
             }
         }
-        
+
         if (linearizeSrgbImages) {
             stringBuf.append("#define SRGB 1\n");
         }
-        
+
         stringBuf.append(source.getDefines());
         stringBuf.append(source.getSource());
-        
+
         intBuf1.clear();
         intBuf1.put(0, stringBuf.length());
         gl.glShaderSource(id, new String[]{ stringBuf.toString() }, intBuf1);
@@ -1137,7 +1141,7 @@ public class GLRenderer implements Renderer {
         // If using GLSL 1.5, we bind the outputs for the user
         // For versions 3.3 and up, user should use layout qualifiers instead.
         boolean bindFragDataRequired = false;
-        
+
         for (ShaderSource source : shader.getSources()) {
             if (source.isUpdateNeeded()) {
                 updateShaderSourceData(source);
@@ -1246,8 +1250,8 @@ public class GLRenderer implements Renderer {
     }
 
     /*********************************************************************\
-    |* Framebuffers                                                      *|
-    \*********************************************************************/
+     |* Framebuffers                                                      *|
+     \*********************************************************************/
     public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) {
         copyFrameBuffer(src, dst, true);
     }
@@ -1402,7 +1406,7 @@ public class GLRenderer implements Renderer {
         } else if (attachmentSlot < 0 || attachmentSlot >= 16) {
             throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot);
         }
-        
+
         return GLFbo.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot;
     }
 
@@ -1412,7 +1416,7 @@ public class GLRenderer implements Renderer {
         if (image.isUpdateNeeded()) {
             // Check NPOT requirements
             checkNonPowerOfTwo(tex);
-            
+
             updateTexImageData(image, tex.getType(), 0, false);
 
             // NOTE: For depth textures, sets nearest/no-mips mode
@@ -1476,7 +1480,7 @@ public class GLRenderer implements Renderer {
         }
 
         checkFrameBufferError();
-        
+
         fb.clearUpdateNeeded();
     }
 
@@ -1569,7 +1573,7 @@ public class GLRenderer implements Renderer {
 
             // update viewport to reflect framebuffer's resolution
             setViewPort(0, 0, fb.getWidth(), fb.getHeight());
-            
+
             if (context.boundFBO != fb.getId()) {
                 glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, fb.getId());
                 statistics.onFrameBufferUse(fb, true);
@@ -1640,7 +1644,7 @@ public class GLRenderer implements Renderer {
     public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
         readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
     }
-    
+
     private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType) {
         if (fb != null) {
             RenderBuffer rb = fb.getColorBuffer();
@@ -1662,8 +1666,8 @@ public class GLRenderer implements Renderer {
 
         gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf);
     }
-    
-     public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {         
+
+    public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {
         GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false);
         readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType);
     }
@@ -1696,14 +1700,14 @@ public class GLRenderer implements Renderer {
     }
 
     /*********************************************************************\
-    |* Textures                                                          *|
-    \*********************************************************************/
+     |* Textures                                                          *|
+     \*********************************************************************/
     private int convertTextureType(Texture.Type type, int samples, int face) {
         if (samples > 1 && !caps.contains(Caps.TextureMultisample)) {
-            throw new RendererException("Multisample textures are not supported" + 
-                                        " by the video hardware.");
+            throw new RendererException("Multisample textures are not supported" +
+                    " by the video hardware.");
         }
-        
+
         switch (type) {
             case TwoDimensional:
                 if (samples > 1) {
@@ -1723,8 +1727,8 @@ public class GLRenderer implements Renderer {
                 }
             case ThreeDimensional:
                 if (!caps.contains(Caps.OpenGL20)) {
-                    throw new RendererException("3D textures are not supported" + 
-                                        " by the video hardware.");
+                    throw new RendererException("3D textures are not supported" +
+                            " by the video hardware.");
                 }
                 return GL2.GL_TEXTURE_3D;
             case CubeMap:
@@ -1807,11 +1811,11 @@ public class GLRenderer implements Renderer {
         int target = convertTextureType(tex.getType(), image != null ? image.getMultiSamples() : 1, -1);
 
         boolean haveMips = true;
-        
+
         if (image != null) {
             haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps();
         }
-        
+
         // filter things
         if (image.getLastTextureState().magFilter != tex.getMagFilter()) {
             int magFilter = convertMagFilter(tex.getMagFilter());
@@ -1834,7 +1838,7 @@ public class GLRenderer implements Renderer {
                 context.seamlessCubemap = false;
             }
         }
-        
+
         if (tex.getAnisotropicFilter() > 1) {
             if (caps.contains(Caps.TextureFilterAnisotropic)) {
                 gl.glTexParameterf(target,
@@ -1871,15 +1875,15 @@ public class GLRenderer implements Renderer {
             // R to Texture compare mode
             if (tex.getShadowCompareMode() != Texture.ShadowCompareMode.Off) {
                 gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL2.GL_COMPARE_R_TO_TEXTURE);
-                gl2.glTexParameteri(target, GL2.GL_DEPTH_TEXTURE_MODE, GL2.GL_INTENSITY);            
+                gl2.glTexParameteri(target, GL2.GL_DEPTH_TEXTURE_MODE, GL2.GL_INTENSITY);
                 if (tex.getShadowCompareMode() == Texture.ShadowCompareMode.GreaterOrEqual) {
                     gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_FUNC, GL.GL_GEQUAL);
                 } else {
                     gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_FUNC, GL.GL_LEQUAL);
                 }
             }else{
-                 //restoring default value
-                 gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL.GL_NONE);          
+                //restoring default value
+                gl2.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL.GL_NONE);
             }
             tex.compareModeUpdated();
         }
@@ -1891,7 +1895,7 @@ public class GLRenderer implements Renderer {
      * Textures with power-of-2 dimensions are supported on all hardware, however 
      * non-power-of-2 textures may or may not be supported depending on which
      * texturing features are used.
-     * 
+     *
      * @param tex The texture to validate.
      * @throws RendererException If the texture is not supported by the hardware
      */
@@ -1900,23 +1904,23 @@ public class GLRenderer implements Renderer {
             // Texture is power-of-2, safe to use.
             return;
         }
-        
+
         if (caps.contains(Caps.NonPowerOfTwoTextures)) {
             // Texture is NPOT but it is supported by video hardware.
             return;
         }
-        
+
         // Maybe we have some / partial support for NPOT?
         if (!caps.contains(Caps.PartialNonPowerOfTwoTextures)) {
             // Cannot use any type of NPOT texture (uncommon)
             throw new RendererException("non-power-of-2 textures are not "
-                                      + "supported by the video hardware");
+                    + "supported by the video hardware");
         }
-        
+
         // Partial NPOT supported..
         if (tex.getMinFilter().usesMipMapLevels()) {
             throw new RendererException("non-power-of-2 textures with mip-maps "
-                                      + "are not supported by the video hardware");
+                    + "are not supported by the video hardware");
         }
 
         switch (tex.getType()) {
@@ -1924,7 +1928,7 @@ public class GLRenderer implements Renderer {
             case ThreeDimensional:
                 if (tex.getWrap(WrapAxis.R) != Texture.WrapMode.EdgeClamp) {
                     throw new RendererException("repeating non-power-of-2 textures "
-                                              + "are not supported by the video hardware");
+                            + "are not supported by the video hardware");
                 }
                 // fallthrough intentional!!!
             case TwoDimensionalArray:
@@ -1932,17 +1936,17 @@ public class GLRenderer implements Renderer {
                 if (tex.getWrap(WrapAxis.S) != Texture.WrapMode.EdgeClamp
                         || tex.getWrap(WrapAxis.T) != Texture.WrapMode.EdgeClamp) {
                     throw new RendererException("repeating non-power-of-2 textures "
-                                              + "are not supported by the video hardware");
+                            + "are not supported by the video hardware");
                 }
                 break;
             default:
                 throw new UnsupportedOperationException("unrecongized texture type");
         }
     }
-    
+
     /**
      * Uploads the given image to the GL driver.
-     * 
+     *
      * @param img The image to upload
      * @param type How the data in the image argument should be interpreted.
      * @param unit The texture slot to be used to upload the image, not important
@@ -1968,7 +1972,7 @@ public class GLRenderer implements Renderer {
                 gl.glActiveTexture(GL.GL_TEXTURE0 + unit);
                 context.boundTextureUnit = unit;
             }
-        
+
             gl.glBindTexture(target, texId);
             context.boundTextures[unit] = img;
 
@@ -2011,12 +2015,12 @@ public class GLRenderer implements Renderer {
                 throw new RendererException("Multisample textures are not supported by the video hardware");
             }
         }
-        
+
         // Check if graphics card doesn't support depth textures
         if (img.getFormat().isDepthFormat() && !caps.contains(Caps.DepthTexture)) {
             throw new RendererException("Depth textures are not supported by the video hardware");
         }
-        
+
         if (target == GL.GL_TEXTURE_CUBE_MAP) {
             // Check max texture size before upload
             int cubeSize = limits.get(Limits.CubemapSize);
@@ -2053,12 +2057,12 @@ public class GLRenderer implements Renderer {
             if (!caps.contains(Caps.TextureArray)) {
                 throw new RendererException("Texture arrays not supported by graphics hardware");
             }
-            
+
             List<ByteBuffer> data = imageForUpload.getData();
-            
+
             // -1 index specifies prepare data for 2D Array
             texUtil.uploadTexture(imageForUpload, target, -1, linearizeSrgbImages);
-            
+
             for (int i = 0; i < data.size(); i++) {
                 // upload each slice of 2D array in turn
                 // this time with the appropriate index
@@ -2087,21 +2091,21 @@ public class GLRenderer implements Renderer {
         if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) {
             // Check NPOT requirements
             boolean scaleToPot = false;
-            
+
             try {
                 checkNonPowerOfTwo(tex);
             } catch (RendererException ex) {
                 if (logger.isLoggable(Level.WARNING)) {
                     int nextWidth = FastMath.nearestPowerOfTwo(tex.getImage().getWidth());
                     int nextHeight = FastMath.nearestPowerOfTwo(tex.getImage().getHeight());
-                    logger.log(Level.WARNING, 
-                               "Non-power-of-2 textures are not supported! Scaling texture '" + tex.getName() + 
-                               "' of size " + tex.getImage().getWidth() + "x" + tex.getImage().getHeight() + 
-                               " to " + nextWidth + "x" + nextHeight);
+                    logger.log(Level.WARNING,
+                            "Non-power-of-2 textures are not supported! Scaling texture '" + tex.getName() +
+                                    "' of size " + tex.getImage().getWidth() + "x" + tex.getImage().getHeight() +
+                                    " to " + nextWidth + "x" + nextHeight);
                 }
                 scaleToPot = true;
             }
-            
+
             updateTexImageData(image, tex.getType(), unit, scaleToPot);
         }
 
@@ -2147,8 +2151,8 @@ public class GLRenderer implements Renderer {
     }
 
     /*********************************************************************\
-    |* Vertex Buffers and Attributes                                     *|
-    \*********************************************************************/
+     |* Vertex Buffers and Attributes                                     *|
+     \*********************************************************************/
     private int convertUsage(Usage usage) {
         switch (usage) {
             case Static:
@@ -2222,7 +2226,7 @@ public class GLRenderer implements Renderer {
                 //statistics.onVertexBufferUse(vb, false);
             }
         }
-        
+
         int usage = convertUsage(vb.getUsage());
         vb.getData().rewind();
 
@@ -2283,7 +2287,7 @@ public class GLRenderer implements Renderer {
         if (context.boundShaderProgram <= 0) {
             throw new IllegalStateException("Cannot render mesh without shader bound");
         }
-        
+
         Attribute attrib = context.boundShader.getAttribute(vb.getBufferType());
         int loc = attrib.getLocation();
         if (loc == -1) {
@@ -2413,7 +2417,7 @@ public class GLRenderer implements Renderer {
                 // What is this?
                 throw new RendererException("Unexpected format for index buffer: " + indexBuf.getFormat());
         }
-        
+
         if (indexBuf.isUpdateNeeded()) {
             updateBufferData(indexBuf);
         }
@@ -2487,8 +2491,8 @@ public class GLRenderer implements Renderer {
     }
 
     /*********************************************************************\
-    |* Render Calls                                                      *|
-    \*********************************************************************/
+     |* Render Calls                                                      *|
+     \*********************************************************************/
     public int convertElementMode(Mesh.Mode mode) {
         switch (mode) {
             case Points:
@@ -2530,7 +2534,7 @@ public class GLRenderer implements Renderer {
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);
         }
-        
+
         if (instanceData != null) {
             setVertexAttrib(instanceData, null);
         }
@@ -2580,11 +2584,11 @@ public class GLRenderer implements Renderer {
     }
 
     private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
- 
+
         // Here while count is still passed in.  Can be removed when/if
         // the method is collapsed again.  -pspeed        
         count = Math.max(mesh.getInstanceCount(), count);
-    
+
         VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);
@@ -2602,7 +2606,7 @@ public class GLRenderer implements Renderer {
                 setVertexAttrib(vb, null);
             }
         }
-        
+
         for (VertexBuffer vb : mesh.getBufferList().getArray()) {
             if (vb.getBufferType() == Type.InterleavedData
                     || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
@@ -2637,7 +2641,7 @@ public class GLRenderer implements Renderer {
             gl.glLineWidth(mesh.getLineWidth());
             context.lineWidth = mesh.getLineWidth();
         }
-        
+
         if (gl4 != null && mesh.getMode().equals(Mode.Patch)) {
             gl4.glPatchParameter(mesh.getPatchVertexCount());
         }
@@ -2653,12 +2657,12 @@ public class GLRenderer implements Renderer {
         // Gamma correction
         if (!caps.contains(Caps.Srgb) && enableSrgb) {
             // Not supported, sorry.
-            logger.warning("sRGB framebuffer is not supported " + 
-                           "by video hardware, but was requested."); 
-            
+            logger.warning("sRGB framebuffer is not supported " +
+                    "by video hardware, but was requested.");
+
             return;
         }
-        
+
         setFrameBuffer(null);
 
         if (enableSrgb) {

+ 34 - 4
jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java

@@ -54,14 +54,12 @@ final class TextureUtil {
     private final GL gl;
     private final GL2 gl2;
     private final GLExt glext;
-    private final RenderContext context;
     private GLImageFormat[][] formats;
-
-    public TextureUtil(GL gl, GL2 gl2, GLExt glext, RenderContext context) {
+    
+    public TextureUtil(GL gl, GL2 gl2, GLExt glext) {
         this.gl = gl;
         this.gl2 = gl2;
         this.glext = glext;
-        this.context = context;
     }
     
     public void initialize(EnumSet<Caps> caps) {
@@ -103,6 +101,33 @@ final class TextureUtil {
         return glFmt;
     }
     
+    private void setupTextureSwizzle(int target, Format format) {
+        // Needed for OpenGL 3.3 to support luminance / alpha formats
+        switch (format) {
+            case Alpha8:
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_ZERO);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_ZERO);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_ZERO);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_RED);
+                break;
+            case Luminance8:
+            case Luminance16F:
+            case Luminance32F:
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_RED);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_RED);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_ONE);
+                break;
+            case Luminance8Alpha8:
+            case Luminance16FAlpha16F:
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_RED);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_RED);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED);
+                gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_GREEN);
+                break;
+        }
+    }
+    
     private void uploadTextureLevel(GLImageFormat format, int target, int level, int slice, int sliceCount, int width, int height, int depth, int samples, ByteBuffer data) {
         if (format.compressed && data != null) {
             if (target == GL2.GL_TEXTURE_3D) {
@@ -242,6 +267,11 @@ final class TextureUtil {
         }
 
         int samples = image.getMultiSamples();
+        
+        // For OGL3 core: setup texture swizzle.
+        if (oglFormat.swizzleRequired) {
+            setupTextureSwizzle(target, jmeFormat);
+        }
 
         for (int i = 0; i < mipSizes.length; i++) {
             int mipWidth = Math.max(1, width >> i);

+ 14 - 0
jme3-core/src/main/resources/Common/ShaderLib/GLSL150Compat.glsllib

@@ -0,0 +1,14 @@
+#if _VERSION_ >= 150
+out vec4 outFragColor;
+#  define texture1D texture
+#  define texture2D texture
+#  define texture3D texture
+#  define texture2DLod texture
+#  if defined VERTEX_SHADER
+#    define varying out
+#    define attribute in
+#  elif defined FRAGMENT_SHADER
+#    define varying in
+#    define gl_FragColor outFragColor
+#  endif
+#endif

+ 1 - 0
jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java

@@ -82,6 +82,7 @@ public class ZipLocator implements AssetLocator {
 
     public AssetInfo locate(AssetManager manager, AssetKey key) {
         String name = key.getName();
+        if(name.startsWith("/"))name=name.substring(1);
         ZipEntry entry = zipfile.getEntry(name);
         if (entry == null)
             return null;

+ 148 - 0
jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java

@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2015 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.network.service;
+
+import com.jme3.network.HostedConnection;
+import com.jme3.network.Server;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ *  Convenient base class for HostedServices providing some default HostedService 
+ *  interface implementations as well as a few convenience methods 
+ *  such as getServiceManager() and getService(type).  This implementation
+ *  enhances the default capabilities provided by AbstractHostedService by
+ *  adding automatic connection management.
+ * 
+ *  <p>Subclasses must at least override the onInitialize(), startHostingOnConnection(), and
+ *  stopHostingOnConnection() methods to handle service and connection initialization.</p>
+ *
+ *  <p>An autoHost flag controls whether startHostingOnConnection() is called
+ *  automatically when new connections are detected.  If autoHohst is false then it
+ *  is up to the implementation or appliction to specifically start hosting at 
+ *  some point.</p>
+ *
+ *  @author    Paul Speed
+ */
+public abstract class AbstractHostedConnectionService extends AbstractHostedService { 
+ 
+    static final Logger log = Logger.getLogger(AbstractHostedConnectionService.class.getName());
+
+    private boolean autoHost;
+    
+    /**
+     *  Creates a new HostedService that will autohost connections
+     *  when detected.
+     */
+    protected AbstractHostedConnectionService() {
+        this(true);
+    }
+
+    /**
+     *  Creates a new HostedService that will automatically host
+     *  connections only if autoHost is true.
+     */
+    protected AbstractHostedConnectionService( boolean autoHost ) {
+        this.autoHost = autoHost;
+    }
+ 
+    /**
+     *  When set to true, all new connections will automatically have
+     *  hosting services attached to them by calling startHostingOnConnection().
+     *  If this is set to false then it is up to the application or other services 
+     *  to eventually call startHostingOnConnection().
+     *  
+     *  <p>Reasons for doing this vary but usually would be because
+     *  the client shouldn't be allowed to perform any service-related calls until
+     *  it has provided more information... for example, logging in.</p>
+     */
+    public void setAutoHost( boolean b ) {
+        this.autoHost = b;
+    }
+ 
+    /**
+     *  Returns true if this service automatically attaches 
+     *  hosting capabilities to new connections.
+     */   
+    public boolean getAutoHost() {
+        return autoHost;
+    }
+
+ 
+    /**
+     *  Performs implementation specific connection hosting setup.
+     *  Generally this involves setting up some handlers or session
+     *  attributes on the connection.  If autoHost is true then this
+     *  method is called automatically during connectionAdded() 
+     *  processing.
+     */
+    public abstract void startHostingOnConnection( HostedConnection hc );
+
+    /**
+     *  Performs implementation specific connection tear-down.
+     *  This will be called automatically when the connectionRemoved()
+     *  event occurs... whether the application has already called it
+     *  or not.
+     */
+    public abstract void stopHostingOnConnection( HostedConnection hc );
+
+    /**
+     *  Called internally when a new connection is detected for
+     *  the server.  If the current autoHost property is true then
+     *  startHostingOnConnection(hc) is called. 
+     */
+    @Override
+    public void connectionAdded(Server server, HostedConnection hc) {
+        if( log.isLoggable(Level.FINEST) ) {
+            log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc});
+        }    
+        if( autoHost ) {
+            startHostingOnConnection(hc);
+        }
+    }
+
+    /**
+     *  Called internally when an existing connection is leaving
+     *  the server.  This method always calls stopHostingOnConnection(hc).
+     *  Implementations should be aware that if they stopHostingOnConnection()
+     *  early that they will get a second call when the connection goes away.
+     */
+    @Override
+    public void connectionRemoved(Server server, HostedConnection hc) {
+        if( log.isLoggable(Level.FINEST) ) {
+            log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc});
+        }    
+        stopHostingOnConnection(hc);
+    }   
+}

+ 5 - 57
jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java

@@ -35,8 +35,8 @@ package com.jme3.network.service.rpc;
 import com.jme3.network.HostedConnection;
 import com.jme3.network.Server;
 import com.jme3.network.serializing.Serializer;
+import com.jme3.network.service.AbstractHostedConnectionService;
 import com.jme3.network.util.SessionDataDelegator;
-import com.jme3.network.service.AbstractHostedService;
 import com.jme3.network.service.HostedServiceManager;
 import com.jme3.network.service.rpc.msg.RpcCallMessage;
 import com.jme3.network.service.rpc.msg.RpcResponseMessage;
@@ -62,13 +62,12 @@ import java.util.logging.Logger;
  *
  *  @author    Paul Speed
  */
-public class RpcHostedService extends AbstractHostedService {
+public class RpcHostedService extends AbstractHostedConnectionService {
 
     private static final String ATTRIBUTE_NAME = "rpcSession";
 
     static final Logger log = Logger.getLogger(RpcHostedService.class.getName());
 
-    private boolean autoHost;
     private SessionDataDelegator delegator;
 
     /**
@@ -87,7 +86,7 @@ public class RpcHostedService extends AbstractHostedService {
      *  on the specified 'autoHost' flag.
      */
     public RpcHostedService( boolean autoHost ) {
-        this.autoHost = autoHost;
+        super(autoHost);
         
         // This works for me... has to be different in
         // the general case
@@ -115,31 +114,6 @@ public class RpcHostedService extends AbstractHostedService {
         }
     }
 
-    /**
-     *  When set to true, all new connections will automatically have
-     *  RPC hosting services attached to them, meaning they can send
-     *  and receive RPC calls.  If this is set to false then it is up
-     *  to other services to eventually call startHostingOnConnection().
-     *  
-     *  <p>Reasons for doing this vary but usually would be because
-     *  the client shouldn't be allowed to perform any RPC calls until
-     *  it has provided more information.  In general, this is unnecessary
-     *  because the RpcHandler registries are not shared.  Each client
-     *  gets their own and RPC calls will fail until the appropriate
-     *  objects have been registtered.</p>
-     */
-    public void setAutoHost( boolean b ) {
-        this.autoHost = b;
-    }
- 
-    /**
-     *  Returns true if this service automatically attaches RPC
-     *  hosting capabilities to new connections.
-     */   
-    public boolean getAutoHost() {
-        return autoHost;
-    }
-
     /**
      *  Retrieves the RpcConnection for the specified HostedConnection
      *  if that HostedConnection has had RPC services started using
@@ -157,6 +131,7 @@ public class RpcHostedService extends AbstractHostedService {
      *  This method is called automatically for all new connections if
      *  autohost is set to true.
      */
+    @Override
     public void startHostingOnConnection( HostedConnection hc ) {
         if( log.isLoggable(Level.FINEST) ) {
             log.log(Level.FINEST, "startHostingOnConnection:{0}", hc);
@@ -173,6 +148,7 @@ public class RpcHostedService extends AbstractHostedService {
      *  This method is called automatically for all leaving connections if
      *  autohost is set to true.
      */
+    @Override
     public void stopHostingOnConnection( HostedConnection hc ) {
         RpcConnection rpc = hc.getAttribute(ATTRIBUTE_NAME);
         if( rpc == null ) {
@@ -195,33 +171,5 @@ public class RpcHostedService extends AbstractHostedService {
         server.removeMessageListener(delegator, delegator.getMessageTypes());
     }
 
-    /**
-     *  Called internally when a new connection is detected for
-     *  the server.  If the current autoHost property is true then
-     *  startHostingOnConnection(hc) is called. 
-     */
-    @Override
-    public void connectionAdded(Server server, HostedConnection hc) {
-        if( log.isLoggable(Level.FINEST) ) {
-            log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc});
-        }    
-        if( autoHost ) {
-            startHostingOnConnection(hc);
-        }
-    }
-
-    /**
-     *  Called internally when an existing connection is leaving
-     *  the server.  If the current autoHost property is true then
-     *  stopHostingOnConnection(hc) is called. 
-     */
-    @Override
-    public void connectionRemoved(Server server, HostedConnection hc) {
-        if( log.isLoggable(Level.FINEST) ) {
-            log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc});
-        }    
-        stopHostingOnConnection(hc);
-    }
-
 }
 

+ 5 - 1
jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java

@@ -38,6 +38,8 @@ import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import de.lessvoid.nifty.Nifty;
 import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  *
@@ -45,6 +47,8 @@ import java.io.IOException;
  */
 public class GuiEvent extends AbstractCinematicEvent {
 
+    static final Logger log = Logger.getLogger(GuiEvent.class.getName());
+
     protected String screen;
     protected Nifty nifty;
 
@@ -76,7 +80,7 @@ public class GuiEvent extends AbstractCinematicEvent {
 
     @Override
     public void onPlay() {
-        System.out.println("screen should be " + screen);
+        log.log(Level.FINEST, "screen should be {0}", screen);
         nifty.gotoScreen(screen);
     }
 

+ 1 - 0
sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ImportModel.java

@@ -212,6 +212,7 @@ public final class ImportModel implements ActionListener {
                 }
                 replaceLocatedTextures(spat, manager);
                 targetData.saveAsset();
+                targetData.closeAsset();
                 ((SpatialAssetDataObject) targetModel).getLookupContents().remove(tempProjectManager);
             }
         } catch (Exception ex) {

+ 1 - 0
sdk/jme3-model-importer/src/com/jme3/gde/modelimporter/ModelImporterVisualPanel3.java

@@ -126,6 +126,7 @@ public final class ModelImporterVisualPanel3 extends JPanel {
             DialogDisplayer.getDefault().notifyLater(msg);
             Exceptions.printStackTrace(e);
         }
+        data.closeAsset();
         manager.unregisterLocator(manager.getAssetFolderName(), UberAssetLocator.class);
         panel.fireChangeEvent();
     }

+ 45 - 8
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/SceneComposerToolController.java

@@ -11,6 +11,7 @@ import com.jme3.gde.core.scene.controller.SceneToolController;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
 import com.jme3.gde.scenecomposer.tools.PickManager;
+import com.jme3.gde.scenecomposer.tools.shortcuts.ShortcutManager;
 import com.jme3.input.event.KeyInputEvent;
 import com.jme3.light.Light;
 import com.jme3.light.PointLight;
@@ -31,6 +32,7 @@ import com.jme3.scene.control.Control;
 import com.jme3.scene.shape.Quad;
 import com.jme3.texture.Texture;
 import java.util.concurrent.Callable;
+import org.openide.util.Lookup;
 
 /**
  *
@@ -184,7 +186,12 @@ public class SceneComposerToolController extends SceneToolController {
      * @param camera
      */
     public void doEditToolActivatedPrimary(Vector2f mouseLoc, boolean pressed, Camera camera) {
-        if (editTool != null) {
+        ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class);
+        
+        if (scm.isActive()) {
+            scm.getActiveShortcut().setCamera(camera);
+            scm.getActiveShortcut().actionPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject());
+        } else if (editTool != null) {
             editTool.setCamera(camera);
             editTool.actionPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject());
         }
@@ -198,36 +205,66 @@ public class SceneComposerToolController extends SceneToolController {
      * @param camera
      */
     public void doEditToolActivatedSecondary(Vector2f mouseLoc, boolean pressed, Camera camera) {
-        if (editTool != null) {
+        ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class);
+        
+        if (scm.isActive()) {
+            scm.getActiveShortcut().setCamera(camera);
+            scm.getActiveShortcut().actionSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject());
+        } else if (editTool != null) {
             editTool.setCamera(camera);
             editTool.actionSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject());
         }
     }
 
     public void doEditToolMoved(Vector2f mouseLoc, Camera camera) {
-        if (editTool != null) {
+        ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class);
+        
+        if (scm.isActive()) {
+            scm.getActiveShortcut().setCamera(camera);
+            scm.getActiveShortcut().mouseMoved(mouseLoc, rootNode, editorController.getCurrentDataObject(), selectedSpatial);
+        } else if (editTool != null) {
             editTool.setCamera(camera);
             editTool.mouseMoved(mouseLoc, rootNode, editorController.getCurrentDataObject(), selectedSpatial);
         }
     }
 
     public void doEditToolDraggedPrimary(Vector2f mouseLoc, boolean pressed, Camera camera) {
-        if (editTool != null) {
+        ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class);
+        
+        if (scm.isActive()) {
+            scm.getActiveShortcut().setCamera(camera);
+            scm.getActiveShortcut().draggedPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject());
+        } else if (editTool != null) {
             editTool.setCamera(camera);
             editTool.draggedPrimary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject());
         }
     }
 
     public void doEditToolDraggedSecondary(Vector2f mouseLoc, boolean pressed, Camera camera) {
-        if (editTool != null) {
+        ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class);
+        
+        if (scm.isActive()) {
+            scm.getActiveShortcut().setCamera(null);
+            scm.getActiveShortcut().draggedSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject());
+        } else if (editTool != null) {
             editTool.setCamera(camera);
             editTool.draggedSecondary(mouseLoc, pressed, rootNode, editorController.getCurrentDataObject());
         }
     }
     
-    void doKeyPressed(KeyInputEvent kie) {
-        if (editTool != null) {
-            editTool.keyPressed(kie);
+    public void doKeyPressed(KeyInputEvent kie) {
+        ShortcutManager scm = Lookup.getDefault().lookup(ShortcutManager.class);
+
+        if (scm.isActive()) {
+            scm.doKeyPressed(kie);
+        } else {
+            if (scm.activateShortcut(kie)) {
+                scm.getActiveShortcut().activate(manager, toolsNode, onTopToolsNode, selected, this);
+            } else {
+                if (editTool != null) {
+                    editTool.keyPressed(kie);
+                }
+            }
         }
     }
     

+ 0 - 204
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/MoveManager.java

@@ -1,204 +0,0 @@
-/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
-package com.jme3.gde.scenecomposer.tools;
-
-import com.jme3.bullet.control.CharacterControl;
-import com.jme3.bullet.control.RigidBodyControl;
-import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
-import com.jme3.gde.scenecomposer.SceneEditTool;
-import com.jme3.math.FastMath;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Vector2f;
-import com.jme3.math.Vector3f;
-import com.jme3.renderer.Camera;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
-import com.jme3.scene.shape.Quad;
-import org.openide.util.lookup.ServiceProvider;
-
-/**
- *
- * @author Nehon
- */
-@ServiceProvider(service = MoveManager.class)
-public class MoveManager {
-
-    private Vector3f startLoc;
-    private Vector3f startWorldLoc;
-    private Vector3f lastLoc;
-    private Vector3f offset;
-    private Node alternativePickTarget = null;
-    private Node plane;
-    private Spatial spatial;
-    protected static final Quaternion XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0));
-    protected static final Quaternion YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));
-    protected static final Quaternion XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0));
-    //temp vars 
-    private Quaternion rot = new Quaternion();
-    private Vector3f newPos = new Vector3f();
-
-    public MoveManager() {
-        float size = 1000;
-        Geometry g = new Geometry("plane", new Quad(size, size));
-        g.setLocalTranslation(-size / 2, -size / 2, 0);
-        plane = new Node();
-        plane.attachChild(g);
-    }
-
-    public Vector3f getOffset() {
-        return offset;
-    }
-
-    public void reset() {
-        offset = null;
-        startLoc = null;
-        startWorldLoc = null;
-        lastLoc = null;
-        spatial = null;
-        alternativePickTarget = null;
-    }
-
-    public void initiateMove(Spatial selectedSpatial, Quaternion planeRotation, boolean local) {
-        spatial = selectedSpatial;
-        startLoc = selectedSpatial.getLocalTranslation().clone();
-        startWorldLoc = selectedSpatial.getWorldTranslation().clone();
-        if (local) {
-            rot.set(selectedSpatial.getWorldRotation());
-            plane.setLocalRotation(rot.multLocal(planeRotation));
-        } else {
-            rot.set(planeRotation);
-        }
-
-        plane.setLocalRotation(rot);
-        plane.setLocalTranslation(startWorldLoc);
-
-    }
-
-    public void updatePlaneRotation(Quaternion planeRotation) {
-        plane.setLocalRotation(rot);
-    }
-
-    public boolean move(Camera camera, Vector2f screenCoord) {
-        return move(camera, screenCoord, Vector3f.UNIT_XYZ, false);
-    }
-
-    public boolean move(Camera camera, Vector2f screenCoord, Vector3f constraintAxis, boolean gridSnap) {
-        Node toPick = alternativePickTarget == null ? plane : alternativePickTarget;
-
-        Vector3f planeHit = SceneEditTool.pickWorldLocation(camera, screenCoord, toPick, alternativePickTarget == null ? null : spatial);
-        if (planeHit == null) {
-            return false;
-        }
-
-        Spatial parent = spatial.getParent();
-        //we are moving the root node, there is a slight chance that something went wrong.
-        if (parent == null) {
-            return false;
-        }
-
-        //offset in world space
-        if (offset == null) {
-            offset = planeHit.subtract(spatial.getWorldTranslation()); // get the offset when we start so it doesn't jump
-        }
-
-        newPos.set(planeHit).subtractLocal(offset);
-
-        //constraining the translation with the contraintAxis.
-        Vector3f tmp = startWorldLoc.mult(Vector3f.UNIT_XYZ.subtract(constraintAxis));
-        newPos.multLocal(constraintAxis).addLocal(tmp);
-        worldToLocalMove(gridSnap);
-        return true;
-    }
-
-    private void worldToLocalMove(boolean gridSnap) {
-        //snap to grid (grid is assumed 1 WU per cell)
-        if (gridSnap) {
-            newPos.set(Math.round(newPos.x), Math.round(newPos.y), Math.round(newPos.z));
-        }
-
-        //computing the inverse world transform to get the new localtranslation        
-        newPos.subtractLocal(spatial.getParent().getWorldTranslation());
-        newPos = spatial.getParent().getWorldRotation().inverse().normalizeLocal().multLocal(newPos);
-        newPos.divideLocal(spatial.getParent().getWorldScale());
-       
-        lastLoc = newPos;
-        spatial.setLocalTranslation(newPos);        
-
-        RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
-        if (control != null) {
-            control.setPhysicsLocation(spatial.getWorldTranslation());
-        }
-        CharacterControl character = spatial.getControl(CharacterControl.class);
-        if (character != null) {
-            character.setPhysicsLocation(spatial.getWorldTranslation());
-        }
-    }
-
-    public boolean moveAcross(Vector3f constraintAxis, float value, boolean gridSnap) {
-        newPos.set(startWorldLoc).addLocal(constraintAxis.mult(value));
-        Spatial parent = spatial.getParent();
-        //we are moving the root node, there is a slight chance that something went wrong.
-        if (parent == null) {
-            return false;
-        }
-        worldToLocalMove(gridSnap);
-        return true;
-    }
-
-    public MoveUndo makeUndo() {
-        return new MoveUndo(spatial, startLoc, lastLoc);
-    }
-
-    public void setAlternativePickTarget(Node alternativePickTarget) {
-        this.alternativePickTarget = alternativePickTarget;
-    }
-
-    protected class MoveUndo extends AbstractUndoableSceneEdit {
-
-        private Spatial spatial;
-        private Vector3f before = new Vector3f(), after = new Vector3f();
-
-        MoveUndo(Spatial spatial, Vector3f before, Vector3f after) {
-            this.spatial = spatial;
-            this.before.set(before);
-            if (after != null) {
-                this.after.set(after);
-            }
-        }
-
-        @Override
-        public void sceneUndo() {
-            spatial.setLocalTranslation(before);
-            RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
-            if (control != null) {
-                control.setPhysicsLocation(spatial.getWorldTranslation());
-            }
-            CharacterControl character = spatial.getControl(CharacterControl.class);
-            if (character != null) {
-                character.setPhysicsLocation(spatial.getWorldTranslation());
-            }
-            //     toolController.selectedSpatialTransformed();
-        }
-
-        @Override
-        public void sceneRedo() {
-            spatial.setLocalTranslation(after);
-            RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
-            if (control != null) {
-                control.setPhysicsLocation(spatial.getWorldTranslation());
-            }
-            CharacterControl character = spatial.getControl(CharacterControl.class);
-            if (character != null) {
-                character.setPhysicsLocation(spatial.getWorldTranslation());
-            }
-            //toolController.selectedSpatialTransformed();
-        }
-
-        public void setAfter(Vector3f after) {
-            this.after.set(after);
-        }
-    }
-}

+ 4 - 8
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/PickManager.java

@@ -33,9 +33,9 @@ public class PickManager {
     private Spatial spatial;
     private SceneComposerToolController.TransformationType transformationType;
 
-    protected static final Quaternion PLANE_XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0));
-    protected static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090
-    protected static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090
+    public static final Quaternion PLANE_XY = new Quaternion().fromAngleAxis(0, new Vector3f(1, 0, 0));
+    public static final Quaternion PLANE_YZ = new Quaternion().fromAngleAxis(-FastMath.PI / 2, new Vector3f(0, 1, 0));//YAW090
+    public static final Quaternion PLANE_XZ = new Quaternion().fromAngleAxis(FastMath.PI / 2, new Vector3f(1, 0, 0)); //PITCH090
 
 
     public PickManager() {
@@ -75,7 +75,7 @@ public class PickManager {
             origineRotation = new Quaternion(Quaternion.IDENTITY);
         } else if (transformationType == SceneComposerToolController.TransformationType.camera) {
             rot.set(camera.getRotation());  
-            origineRotation = camera.getRotation().clone();
+            origineRotation = camera.getRotation();
         }
         plane.setLocalRotation(rot);
     }
@@ -87,10 +87,6 @@ public class PickManager {
      * @return true if the the new picked location is set, else return false.
      */
     public boolean updatePick(Camera camera, Vector2f screenCoord) {
-        if(transformationType == SceneComposerToolController.TransformationType.camera){
-            origineRotation = camera.getRotation();
-            plane.setLocalRotation(camera.getRotation());
-        }
         finalPickLoc = SceneEditTool.pickWorldLocation(camera, screenCoord, plane, null);
         return finalPickLoc != null;
     }

+ 4 - 7
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/RotateTool.java

@@ -25,7 +25,6 @@ import org.openide.util.Lookup;
 public class RotateTool extends SceneEditTool {
 
     private Vector3f pickedMarker;
-    private Vector2f lastScreenCoord;
     private Quaternion startRotate;
     private Quaternion lastRotate;
     private boolean wasDragging = false;
@@ -48,9 +47,8 @@ public class RotateTool extends SceneEditTool {
         if (!pressed) {
             setDefaultAxisMarkerColors();
             pickedMarker = null; // mouse released, reset selection
-            lastScreenCoord = null;
             if (wasDragging) {
-                actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate));
+                actionPerformed(new RotateUndo(toolController.getSelectedSpatial(), startRotate, lastRotate));
                 wasDragging = false;
             }
             pickManager.reset();
@@ -100,10 +98,9 @@ public class RotateTool extends SceneEditTool {
         if (!pressed) {
             setDefaultAxisMarkerColors();
             pickedMarker = null; // mouse released, reset selection
-            lastScreenCoord = null;
 
             if (wasDragging) {
-                actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startRotate, lastRotate));
+                actionPerformed(new RotateUndo(toolController.getSelectedSpatial(), startRotate, lastRotate));
                 wasDragging = false;
             }
             pickManager.reset();
@@ -138,12 +135,12 @@ public class RotateTool extends SceneEditTool {
         }
     }
 
-    private class ScaleUndo extends AbstractUndoableSceneEdit {
+    private class RotateUndo extends AbstractUndoableSceneEdit {
 
         private Spatial spatial;
         private Quaternion before, after;
 
-        ScaleUndo(Spatial spatial, Quaternion before, Quaternion after) {
+        RotateUndo(Spatial spatial, Quaternion before, Quaternion after) {
             this.spatial = spatial;
             this.before = before;
             this.after = after;

+ 30 - 664
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/SelectTool.java

@@ -8,425 +8,51 @@ import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
 import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
-import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
 import com.jme3.gde.scenecomposer.SceneEditTool;
-import com.jme3.input.KeyInput;
-import com.jme3.input.event.KeyInputEvent;
-import com.jme3.math.FastMath;
-import com.jme3.math.Quaternion;
 import com.jme3.math.Vector2f;
-import com.jme3.math.Vector3f;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.terrain.Terrain;
 import org.openide.loaders.DataObject;
-import org.openide.util.Lookup;
 
 /**
- * This duplicates the Blender manipulate tool.
- * It supports quick access to Grab, Rotate, and Scale operations
- * by typing one of the following keys: 'g', 'r', or 's'
- * Those keys can be followed by an axis key to specify what axis
- * to perform the transformation: x, y, z
- * Then, after the operation and axis are selected, you can type in a
- * number and then hit 'enter' to complete the transformation.
- * 
- * Ctrl+Shift+D will duplicate an object
- * X will delete an object
- * 
- * ITEMS TO FINISH:
- * 1) fixed scale and rotation values by holding Ctrl and dragging mouse
- * BUGS:
- * 1) window always needs focus from primary click when it should focus from secondary and middle mouse
- * 
+ * This duplicates the Blender manipulate tool. It supports quick access to
+ * Grab, Rotate, and Scale operations by typing one of the following keys: 'g',
+ * 'r', or 's' Those keys can be followed by an axis key to specify what axis to
+ * perform the transformation: x, y, z Then, after the operation and axis are
+ * selected, you can type in a number and then hit 'enter' to complete the
+ * transformation.
+ *
+ * Ctrl+Shift+D will duplicate an object X will delete an object
+ *
+ * ITEMS TO FINISH: 1) fixed scale and rotation values by holding Ctrl and
+ * dragging mouse BUGS: 1) window always needs focus from primary click when it
+ * should focus from secondary and middle mouse
+ *
  * @author Brent Owens
  */
 public class SelectTool extends SceneEditTool {
 
-    private enum State {
-
-        translate, rotate, scale
-    };
-    private State currentState = null;
-    private Vector3f currentAxis = Vector3f.UNIT_XYZ;
-    private StringBuilder numberBuilder = new StringBuilder(); // gets appended with numbers
-    private Quaternion startRot;
-    private Vector3f startTrans;
-    private Vector3f startScale;
-    private boolean wasDraggingL = false;
     private boolean wasDraggingR = false;
     private boolean wasDownR = false;
-    private boolean ctrlDown = false;
-    private boolean shiftDown = false;
-    private boolean altDown = false;
-    private MoveManager.MoveUndo moving;
-    private ScaleUndo scaling;
-    private RotateUndo rotating;
-    private Vector2f startMouseCoord; // for scaling and rotation
-    private Vector2f startSelectedCoord; // for scaling and rotation
-    private float lastRotAngle; // used for rotation
 
     /**
-     * This is stateful:
-     * First it checks for a command (rotate, translate, delete, etc..)
-     * Then it checks for an axis (x,y,z)
-     * Then it checks for a number (user typed a number
-     * Then, finally, it checks if Enter was hit.
-     * 
+     * This is stateful: First it checks for a command (rotate, translate,
+     * delete, etc..) Then it checks for an axis (x,y,z) Then it checks for a
+     * number (user typed a number Then, finally, it checks if Enter was hit.
+     *
      * If either of the commands was actioned, the preceeding states/axis/amount
-     * will be reset. For example if the user types: G Y 2 R
-     * Then it will:
-     * 1) Set state as 'Translate' for the G (grab)
-     * 2) Set the axis as 'Y'; it will translate along the Y axis
-     * 3) Distance will be 2, when the 2 key is hit
-     * 4) Distance, Axis, and state are then reset because a new state was set: Rotate
-     * it won't actually translate because 'Enter' was not hit and 'R' reset the state.
-     * 
+     * will be reset. For example if the user types: G Y 2 R Then it will: 1)
+     * Set state as 'Translate' for the G (grab) 2) Set the axis as 'Y'; it will
+     * translate along the Y axis 3) Distance will be 2, when the 2 key is hit
+     * 4) Distance, Axis, and state are then reset because a new state was set:
+     * Rotate it won't actually translate because 'Enter' was not hit and 'R'
+     * reset the state.
+     *
      */
-    @Override
-    public void keyPressed(KeyInputEvent kie) {
-
-        checkModificatorKeys(kie); // alt,shift,ctrl
-        Spatial selected = toolController.getSelectedSpatial();
-
-        if (selected == null) {
-            return; // only do anything if a spatial is selected
-        }
-        // key released
-        if (kie.isPressed()) {
-            boolean commandUsed = checkCommandKey(kie);
-            boolean stateChange = checkStateKey(kie);
-            boolean axisChange = checkAxisKey(kie);
-            boolean numberChange = checkNumberKey(kie);
-            boolean enterHit = checkEnterHit(kie);
-            boolean escHit = checkEscHit(kie);
-
-            if (commandUsed) {
-                return; // commands take priority
-            }
-            if (stateChange) {
-                currentAxis = Vector3f.UNIT_XYZ;
-                numberBuilder = new StringBuilder();
-                recordInitialState(selected);
-            } else if (axisChange) {
-            } else if (numberChange) {
-            } else if (enterHit) {
-                if (currentState != null && numberBuilder.length() > 0) {
-                    applyKeyedChangeState(selected);
-                    clearState(false);
-                }
-            }
-
-
-            // -----------------------
-            // reset conditions below:
-
-            if (escHit) {
-                if (moving != null) {
-                    moving.sceneUndo();
-                }
-
-                moving = null;
-                clearState();
-            }
-
-            if (!stateChange && !axisChange && !numberChange && !enterHit && !escHit) {
-                // nothing valid was hit, reset the state
-                //clearState(); // this will be 
-            }
-        }
-    }
-
-    /**
-     * Abort any manipulations
-     */
-    private void clearState() {
-        clearState(true);
-    }
-
-    private void clearState(boolean resetSelected) {
-        Spatial selected = toolController.getSelectedSpatial();
-        if (resetSelected && selected != null) {
-            // reset the transforms
-            if (startRot != null) {
-                selected.setLocalRotation(startRot);
-            }
-            if (startTrans != null) {
-                selected.setLocalTranslation(startTrans);
-            }
-            if (startScale != null) {
-                selected.setLocalScale(startScale);
-            }
-        }
-        currentState = null;
-        currentAxis = Vector3f.UNIT_XYZ;
-        numberBuilder = new StringBuilder();
-        startRot = null;
-        startTrans = null;
-        startScale = null;
-        startMouseCoord = null;
-        startSelectedCoord = null;
-        lastRotAngle = 0;
-    }
-
-    private void recordInitialState(Spatial selected) {
-        startRot = selected.getLocalRotation().clone();
-        startTrans = selected.getLocalTranslation().clone();
-        startScale = selected.getLocalScale().clone();
-    }
-
-    /**
-     * Applies the changes entered by a number, not by mouse.
-     * Translate: adds the value to the current local translation
-     * Rotate: rotates by X degrees
-     * Scale: scale the current scale by X amount
-     */
-    private void applyKeyedChangeState(Spatial selected) {
-        Float value = null;
-        try {
-            value = new Float(numberBuilder.toString());
-        } catch (NumberFormatException e) {
-            return;
-        }
-
-        if (currentState == State.translate) {
-            MoveManager moveManager = Lookup.getDefault().lookup(MoveManager.class);
-            moveManager.moveAcross(currentAxis, value, toolController.isSnapToGrid());
-            moving.setAfter(selected.getLocalTranslation());
-            actionPerformed(moving);
-            moving = null;
-        } else if (currentState == State.scale) {
-            float x = 1, y = 1, z = 1;
-            if (currentAxis == Vector3f.UNIT_X) {
-                x = value;
-            } else if (currentAxis == Vector3f.UNIT_Y) {
-                y = value;
-            } else if (currentAxis == Vector3f.UNIT_Z) {
-                z = value;
-            } else if (currentAxis == Vector3f.UNIT_XYZ) {
-                x = value;
-                y = value;
-                z = value;
-            }
-            Vector3f before = selected.getLocalScale().clone();
-            Vector3f after = selected.getLocalScale().multLocal(x, y, z);
-            selected.setLocalScale(after);
-            actionPerformed(new ScaleUndo(selected, before, after));
-        } else if (currentState == State.rotate) {
-            float x = 0, y = 0, z = 0;
-            if (currentAxis == Vector3f.UNIT_X) {
-                x = 1;
-            } else if (currentAxis == Vector3f.UNIT_Y) {
-                y = 1;
-            } else if (currentAxis == Vector3f.UNIT_Z) {
-                z = 1;
-            }
-            Vector3f axis = new Vector3f(x, y, z);
-            Quaternion initialRot = selected.getLocalRotation().clone();
-            Quaternion rot = new Quaternion();
-            rot = rot.fromAngleAxis(value * FastMath.DEG_TO_RAD, axis);
-            selected.setLocalRotation(selected.getLocalRotation().mult(rot));
-            RotateUndo undo = new RotateUndo(selected, initialRot, rot);
-            actionPerformed(undo);
-            toolController.updateSelection(null);// force a re-draw of the bbox shape
-            toolController.updateSelection(selected);
-
-        }
-        clearState(false);
-    }
-
-    private void checkModificatorKeys(KeyInputEvent kie) {
-        if (kie.getKeyCode() == KeyInput.KEY_LCONTROL || kie.getKeyCode() == KeyInput.KEY_RCONTROL) {
-            ctrlDown = kie.isPressed();
-        }
-
-        if (kie.getKeyCode() == KeyInput.KEY_LSHIFT || kie.getKeyCode() == KeyInput.KEY_RSHIFT) {
-            shiftDown = kie.isPressed();
-        }
-
-        if (kie.getKeyCode() == KeyInput.KEY_LMENU || kie.getKeyCode() == KeyInput.KEY_RMENU) {
-            altDown = kie.isPressed();
-        }
-    }
-
-    private boolean checkCommandKey(KeyInputEvent kie) {
-        if (kie.getKeyCode() == KeyInput.KEY_D) {
-            if (shiftDown) {
-                duplicateSelected();
-                return true;
-            }
-        }
-        // X will only delete if the user isn't already transforming
-        if (currentState == null && kie.getKeyCode() == KeyInput.KEY_X) {
-            if (!ctrlDown && !shiftDown) {
-                deleteSelected();
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean checkStateKey(KeyInputEvent kie) {
-        Spatial selected = toolController.getSelectedSpatial();
-        if (kie.getKeyCode() == KeyInput.KEY_G && !ctrlDown) {
-            currentState = State.translate;
-            MoveManager moveManager = Lookup.getDefault().lookup(MoveManager.class);
-            moveManager.reset();
-            Quaternion rot = camera.getRotation().mult(new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y));
-            moveManager.initiateMove(selected, rot, false);
-            moving = moveManager.makeUndo();
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_R && !ctrlDown) {
-            currentState = State.rotate;
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_S && !ctrlDown) {
-            currentState = State.scale;
-            return true;
-        }
-        return false;
-    }
-
-    private boolean checkAxisKey(KeyInputEvent kie) {
-        if (kie.getKeyCode() == KeyInput.KEY_X) {
-            currentAxis = Vector3f.UNIT_X;
-            checkMovePlane(MoveManager.XY, MoveManager.XZ);
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_Y) {
-            currentAxis = Vector3f.UNIT_Y;
-            checkMovePlane(MoveManager.XY, MoveManager.YZ);
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_Z) {
-            currentAxis = Vector3f.UNIT_Z;
-            checkMovePlane(MoveManager.XZ, MoveManager.YZ);
-            return true;
-        }
-        return false;
-    }
-
-    private void checkMovePlane(Quaternion rot1, Quaternion rot2) {
-        if (currentState == State.translate) {
-            MoveManager moveManager = Lookup.getDefault().lookup(MoveManager.class);
-            Quaternion rot = camera.getRotation().mult(new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y));
-            Quaternion planRot = null;
-            if (rot.dot(rot1) < rot.dot(rot2)) {
-                planRot = rot1;
-            } else {
-                planRot = rot2;
-            }
-            moveManager.updatePlaneRotation(planRot);
-        }
-    }
-
-    private boolean checkNumberKey(KeyInputEvent kie) {
-        if (kie.getKeyCode() == KeyInput.KEY_MINUS) {
-            if (numberBuilder.length() > 0) {
-                if (numberBuilder.charAt(0) == '-') {
-                    numberBuilder.replace(0, 1, "");
-                } else {
-                    numberBuilder.insert(0, '-');
-                }
-            } else {
-                numberBuilder.append('-');
-            }
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_0 || kie.getKeyCode() == KeyInput.KEY_NUMPAD0) {
-            numberBuilder.append('0');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_1 || kie.getKeyCode() == KeyInput.KEY_NUMPAD1) {
-            numberBuilder.append('1');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_2 || kie.getKeyCode() == KeyInput.KEY_NUMPAD2) {
-            numberBuilder.append('2');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_3 || kie.getKeyCode() == KeyInput.KEY_NUMPAD3) {
-            numberBuilder.append('3');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_4 || kie.getKeyCode() == KeyInput.KEY_NUMPAD4) {
-            numberBuilder.append('4');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_5 || kie.getKeyCode() == KeyInput.KEY_NUMPAD5) {
-            numberBuilder.append('5');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_6 || kie.getKeyCode() == KeyInput.KEY_NUMPAD6) {
-            numberBuilder.append('6');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_7 || kie.getKeyCode() == KeyInput.KEY_NUMPAD7) {
-            numberBuilder.append('7');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_8 || kie.getKeyCode() == KeyInput.KEY_NUMPAD8) {
-            numberBuilder.append('8');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_9 || kie.getKeyCode() == KeyInput.KEY_NUMPAD9) {
-            numberBuilder.append('9');
-            return true;
-        } else if (kie.getKeyCode() == KeyInput.KEY_PERIOD) {
-            if (numberBuilder.indexOf(".") == -1) { // if it doesn't exist yet
-                if (numberBuilder.length() == 0
-                        || (numberBuilder.length() == 1 && numberBuilder.charAt(0) == '-')) {
-                    numberBuilder.append("0.");
-                } else {
-                    numberBuilder.append(".");
-                }
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    private boolean checkEnterHit(KeyInputEvent kie) {
-        if (kie.getKeyCode() == KeyInput.KEY_RETURN) {
-            return true;
-        }
-        return false;
-    }
-
-    private boolean checkEscHit(KeyInputEvent kie) {
-        if (kie.getKeyCode() == KeyInput.KEY_ESCAPE) {
-            return true;
-        }
-        return false;
-    }
-
     @Override
     public void actionPrimary(Vector2f screenCoord, boolean pressed, final JmeNode rootNode, DataObject dataObject) {
-        if (!pressed) {
-            Spatial selected = toolController.getSelectedSpatial();
-            // left mouse released
-            if (!wasDraggingL) {
-                // left mouse pressed
-                if (currentState != null) {
-                    // finish manipulating the spatial
-                    if (moving != null) {
-                        moving.setAfter(selected.getLocalTranslation());
-                        actionPerformed(moving);
-                        moving = null;
-                        clearState(false);
-                    } else if (scaling != null) {
-                        scaling.after = selected.getLocalScale().clone();
-                        actionPerformed(scaling);
-                        scaling = null;
-                        clearState(false);
-                        toolController.rebuildSelectionBox();
-                    } else if (rotating != null) {
-                        rotating.after = selected.getLocalRotation().clone();
-                        actionPerformed(rotating);
-                        rotating = null;
-                        clearState(false);
-                    }
-                } else {
-                    // mouse released and wasn't dragging, place cursor
-                    final Vector3f result = pickWorldLocation(getCamera(), screenCoord, rootNode);
-                    if (result != null) {
-                        if (toolController.isSnapToGrid()) {
-                            result.set(Math.round(result.x), result.y, Math.round(result.z));
-                        }
-                        toolController.setCursorLocation(result);
-                    }
-                }
-            }
-            wasDraggingL = false;
-        }
+
     }
 
     @Override
@@ -435,19 +61,7 @@ public class SelectTool extends SceneEditTool {
             Spatial selected = toolController.getSelectedSpatial();
             // mouse down
 
-            if (moving != null) {
-                moving.sceneUndo();
-                moving = null;
-                clearState();
-            } else if (scaling != null) {
-                scaling.sceneUndo();
-                scaling = null;
-                clearState();
-            } else if (rotating != null) {
-                rotating.sceneUndo();
-                rotating = null;
-                clearState();
-            } else if (!wasDraggingR && !wasDownR) { // wasn't dragging and was not down already
+            if (!wasDraggingR && !wasDownR) { // wasn't dragging and was not down already
                 // pick on the spot
                 Spatial s = pickWorldSpatial(camera, screenCoord, rootNode);
                 if (!toolController.selectTerrain() && isTerrain(s)) {
@@ -498,8 +112,8 @@ public class SelectTool extends SceneEditTool {
     }
 
     /**
-     * Climb up the spatial until we find the first node parent.
-     * TODO: use userData to determine the actual model's parent.
+     * Climb up the spatial until we find the first node parent. TODO: use
+     * userData to determine the actual model's parent.
      */
     private Spatial findModelNodeParent(Spatial child) {
         if (child == null) {
@@ -519,14 +133,10 @@ public class SelectTool extends SceneEditTool {
 
     @Override
     public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) {
-        if (currentState != null) {
-            handleMouseManipulate(screenCoord, currentState, currentAxis, rootNode, currentDataObject, selectedSpatial);
-        }
     }
 
     @Override
     public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
-        wasDraggingL = pressed;
     }
 
     @Override
@@ -535,252 +145,8 @@ public class SelectTool extends SceneEditTool {
     }
 
     /**
-     * Manipulate the spatial
-     */
-    private void handleMouseManipulate(Vector2f screenCoord,
-            State state,
-            Vector3f axis,
-            JmeNode rootNode,
-            DataObject currentDataObject,
-            JmeSpatial selectedSpatial) {
-        if (state == State.translate) {
-            doMouseTranslate(axis, screenCoord, rootNode, selectedSpatial);
-        } else if (state == State.scale) {
-            doMouseScale(axis, screenCoord, rootNode, selectedSpatial);
-        } else if (state == State.rotate) {
-            doMouseRotate(axis, screenCoord, rootNode, selectedSpatial);
-        }
-
-    }
-
-    private void doMouseTranslate(Vector3f axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) {
-        MoveManager moveManager = Lookup.getDefault().lookup(MoveManager.class);
-        if (toolController.isSnapToScene()) {
-            moveManager.setAlternativePickTarget(rootNode.getLookup().lookup(Node.class));
-        }
-        // free form translation
-        moveManager.move(camera, screenCoord, axis, toolController.isSnapToGrid());
-    }
-
-    private void doMouseScale(Vector3f axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) {
-        Spatial selected = toolController.getSelectedSpatial();
-        // scale based on the original mouse position and original model-to-screen position
-        // and compare that to the distance from the new mouse position and the original distance
-        if (startMouseCoord == null) {
-            startMouseCoord = screenCoord.clone();
-        }
-        if (startSelectedCoord == null) {
-            Vector3f screen = getCamera().getScreenCoordinates(selected.getWorldTranslation());
-            startSelectedCoord = new Vector2f(screen.x, screen.y);
-        }
-
-        if (scaling == null) {
-            scaling = new ScaleUndo(selected, selected.getLocalScale().clone(), null);
-        }
-
-        float origDist = startMouseCoord.distanceSquared(startSelectedCoord);
-        float newDist = screenCoord.distanceSquared(startSelectedCoord);
-        if (origDist == 0) {
-            origDist = 1;
-        }
-        float ratio = newDist / origDist;
-        Vector3f prev = selected.getLocalScale();
-        if (axis == Vector3f.UNIT_X) {
-            selected.setLocalScale(ratio, prev.y, prev.z);
-        } else if (axis == Vector3f.UNIT_Y) {
-            selected.setLocalScale(prev.x, ratio, prev.z);
-        } else if (axis == Vector3f.UNIT_Z) {
-            selected.setLocalScale(prev.x, prev.y, ratio);
-        } else {
-            selected.setLocalScale(ratio, ratio, ratio);
-        }
-    }
-
-    private void doMouseRotate(Vector3f axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) {
-        Spatial selected = toolController.getSelectedSpatial();
-        if (startMouseCoord == null) {
-            startMouseCoord = screenCoord.clone();
-        }
-        if (startSelectedCoord == null) {
-            Vector3f screen = getCamera().getScreenCoordinates(selected.getWorldTranslation());
-            startSelectedCoord = new Vector2f(screen.x, screen.y);
-        }
-
-        if (rotating == null) {
-            rotating = new RotateUndo(selected, selected.getLocalRotation().clone(), null);
-        }
-
-        Vector2f origRot = startMouseCoord.subtract(startSelectedCoord);
-        Vector2f newRot = screenCoord.subtract(startSelectedCoord);
-        float newRotAngle = origRot.angleBetween(newRot);
-        float temp = newRotAngle;
-
-        if (lastRotAngle != 0) {
-            newRotAngle -= lastRotAngle;
-        }
-
-        lastRotAngle = temp;
-
-        Quaternion rotate = new Quaternion();
-        if (axis != Vector3f.UNIT_XYZ) {
-            rotate = rotate.fromAngleAxis(newRotAngle, selected.getWorldRotation().inverse().mult(axis));
-        } else {
-            rotate = rotate.fromAngleAxis(newRotAngle, selected.getWorldRotation().inverse().mult(getCamera().getDirection().mult(-1).normalizeLocal()));
-        }
-        selected.setLocalRotation(selected.getLocalRotation().mult(rotate));
-
-
-    }
-
-    private void duplicateSelected() {
-        Spatial selected = toolController.getSelectedSpatial();
-        if (selected == null) {
-            return;
-        }
-        Spatial clone = selected.clone();
-        clone.move(1, 0, 1);
-
-        selected.getParent().attachChild(clone);
-        actionPerformed(new DuplicateUndo(clone, selected.getParent()));
-        selected = clone;
-        final Spatial cloned = clone;
-        final JmeNode rootNode = toolController.getRootNode();
-        refreshSelected(rootNode, selected.getParent());
-
-        java.awt.EventQueue.invokeLater(new Runnable() {
-
-            @Override
-            public void run() {
-                if (cloned != null) {
-                    SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(cloned)});
-                    SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode.getChild(cloned));
-                }
-            }
-        });
-
-        // set to automatically 'grab'/'translate' the new cloned model
-        toolController.updateSelection(selected);
-        currentState = State.translate;
-        currentAxis = Vector3f.UNIT_XYZ;
-    }
-
-    private void deleteSelected() {
-        Spatial selected = toolController.getSelectedSpatial();
-        if (selected == null) {
-            return;
-        }
-        Node parent = selected.getParent();
-        selected.removeFromParent();
-        actionPerformed(new DeleteUndo(selected, parent));
-
-        selected = null;
-        toolController.updateSelection(selected);
-
-        final JmeNode rootNode = toolController.getRootNode();
-        refreshSelected(rootNode, parent);
-    }
-
-    private void refreshSelected(final JmeNode jmeRootNode, final Node parent) {
-        java.awt.EventQueue.invokeLater(new Runnable() {
-
-            @Override
-            public void run() {
-                jmeRootNode.getChild(parent).refresh(false);
-            }
-        });
-    }
-
-    private class ScaleUndo extends AbstractUndoableSceneEdit {
-
-        private Spatial spatial;
-        private Vector3f before, after;
-
-        ScaleUndo(Spatial spatial, Vector3f before, Vector3f after) {
-            this.spatial = spatial;
-            this.before = before;
-            this.after = after;
-        }
-
-        @Override
-        public void sceneUndo() {
-            spatial.setLocalScale(before);
-        }
-
-        @Override
-        public void sceneRedo() {
-            spatial.setLocalScale(after);
-        }
-    }
-
-    private class RotateUndo extends AbstractUndoableSceneEdit {
-
-        private Spatial spatial;
-        private Quaternion before, after;
-
-        RotateUndo(Spatial spatial, Quaternion before, Quaternion after) {
-            this.spatial = spatial;
-            this.before = before;
-            this.after = after;
-        }
-
-        @Override
-        public void sceneUndo() {
-            spatial.setLocalRotation(before);
-        }
-
-        @Override
-        public void sceneRedo() {
-            spatial.setLocalRotation(after);
-        }
-    }
-
-    private class DeleteUndo extends AbstractUndoableSceneEdit {
-
-        private Spatial spatial;
-        private Node parent;
-
-        DeleteUndo(Spatial spatial, Node parent) {
-            this.spatial = spatial;
-            this.parent = parent;
-        }
-
-        @Override
-        public void sceneUndo() {
-            parent.attachChild(spatial);
-        }
-
-        @Override
-        public void sceneRedo() {
-            spatial.removeFromParent();
-        }
-    }
-
-    private class DuplicateUndo extends AbstractUndoableSceneEdit {
-
-        private Spatial spatial;
-        private Node parent;
-
-        DuplicateUndo(Spatial spatial, Node parent) {
-            this.spatial = spatial;
-            this.parent = parent;
-        }
-
-        @Override
-        public void sceneUndo() {
-            spatial.removeFromParent();
-        }
-
-        @Override
-        public void sceneRedo() {
-            parent.attachChild(spatial);
-        }
-    }
-
-    /**
-     * Check if the selected item is a Terrain
-     * It will climb up the parent tree to see if
-     * a parent is terrain too.
-     * Recursive call.
+     * Check if the selected item is a Terrain It will climb up the parent tree
+     * to see if a parent is terrain too. Recursive call.
      */
     protected boolean isTerrain(Spatial s) {
         if (s == null) {

+ 123 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DeleteShortcut.java

@@ -0,0 +1,123 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.tools.shortcuts;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
+import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
+import com.jme3.gde.scenecomposer.SceneComposerToolController;
+import com.jme3.input.KeyInput;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.math.Vector2f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import org.openide.loaders.DataObject;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author dokthar
+ */
+public class DeleteShortcut extends ShortcutTool {
+
+    @Override
+    public boolean isActivableBy(KeyInputEvent kie) {
+        if (kie.getKeyCode() == KeyInput.KEY_X && kie.isPressed()) {
+            if (Lookup.getDefault().lookup(ShortcutManager.class).isShiftDown()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void cancel() {
+        terminate();
+    }
+
+    @Override
+    public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) {
+        super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates.
+        hideMarker();
+        if (selectedSpatial != null) {
+            delete();
+        }
+        terminate();
+    }
+
+    private void delete() {
+        Spatial selected = toolController.getSelectedSpatial();
+
+        Node parent = selected.getParent();
+        selected.removeFromParent();
+        actionPerformed(new DeleteUndo(selected, parent));
+
+        selected = null;
+        toolController.updateSelection(selected);
+
+        final JmeNode rootNode = toolController.getRootNode();
+        refreshSelected(rootNode, parent);
+    }
+
+    private void refreshSelected(final JmeNode jmeRootNode, final Node parent) {
+        java.awt.EventQueue.invokeLater(new Runnable() {
+
+            @Override
+            public void run() {
+                jmeRootNode.getChild(parent).refresh(false);
+            }
+        });
+    }
+
+    @Override
+    public void keyPressed(KeyInputEvent kie) {
+
+    }
+
+    @Override
+    public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+    }
+
+    @Override
+    public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+    }
+
+    @Override
+    public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) {
+    }
+
+    @Override
+    public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+    }
+
+    @Override
+    public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+    }
+    
+    private class DeleteUndo extends AbstractUndoableSceneEdit {
+
+        private Spatial spatial;
+        private Node parent;
+
+        DeleteUndo(Spatial spatial, Node parent) {
+            this.spatial = spatial;
+            this.parent = parent;
+        }
+
+        @Override
+        public void sceneUndo() {
+            parent.attachChild(spatial);
+        }
+
+        @Override
+        public void sceneRedo() {
+            spatial.removeFromParent();
+        }
+    }
+}

+ 141 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/DuplicateShortcut.java

@@ -0,0 +1,141 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.tools.shortcuts;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
+import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
+import com.jme3.gde.scenecomposer.SceneComposerToolController;
+import com.jme3.input.KeyInput;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.math.Vector2f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import org.openide.loaders.DataObject;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author dokthar
+ */
+public class DuplicateShortcut extends ShortcutTool {
+
+    @Override
+    public boolean isActivableBy(KeyInputEvent kie) {
+        if (kie.getKeyCode() == KeyInput.KEY_D && kie.isPressed()) {
+            if (Lookup.getDefault().lookup(ShortcutManager.class).isShiftDown()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void cancel() {
+        terminate();
+    }
+
+    @Override
+    public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) {
+        super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates.
+        hideMarker();
+        if (selectedSpatial != null) {
+            duplicate();
+            terminate();
+            
+            //then enable move shortcut
+            toolController.doKeyPressed(new KeyInputEvent(KeyInput.KEY_G, 'g', true, false));
+        } else {
+            terminate();
+        }
+    }
+
+    private void duplicate() {
+        Spatial selected = toolController.getSelectedSpatial();
+
+        Spatial clone = selected.clone();
+        clone.move(1, 0, 1);
+
+        selected.getParent().attachChild(clone);
+        actionPerformed(new DuplicateUndo(clone, selected.getParent()));
+        selected = clone;
+        final Spatial cloned = clone;
+        final JmeNode rootNode = toolController.getRootNode();
+        refreshSelected(rootNode, selected.getParent());
+
+        java.awt.EventQueue.invokeLater(new Runnable() {
+
+            @Override
+            public void run() {
+                if (cloned != null) {
+                    SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(cloned)});
+                    SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode.getChild(cloned));
+                }
+            }
+        });
+
+        toolController.updateSelection(selected);
+    }
+
+    private void refreshSelected(final JmeNode jmeRootNode, final Node parent) {
+        java.awt.EventQueue.invokeLater(new Runnable() {
+
+            @Override
+            public void run() {
+                jmeRootNode.getChild(parent).refresh(false);
+            }
+        });
+    }
+
+    @Override
+    public void keyPressed(KeyInputEvent kie) {
+
+    }
+
+    @Override
+    public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+    }
+
+    @Override
+    public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+    }
+
+    @Override
+    public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) {
+    }
+
+    @Override
+    public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+    }
+
+    @Override
+    public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+    }
+
+    private class DuplicateUndo extends AbstractUndoableSceneEdit {
+
+        private Spatial spatial;
+        private Node parent;
+
+        DuplicateUndo(Spatial spatial, Node parent) {
+            this.spatial = spatial;
+            this.parent = parent;
+        }
+
+        @Override
+        public void sceneUndo() {
+            spatial.removeFromParent();
+        }
+
+        @Override
+        public void sceneRedo() {
+            parent.attachChild(spatial);
+        }
+    }
+}

+ 227 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/MoveShortcut.java

@@ -0,0 +1,227 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.tools.shortcuts;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
+import com.jme3.gde.scenecomposer.SceneComposerToolController;
+import com.jme3.gde.scenecomposer.tools.PickManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import org.openide.loaders.DataObject;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author dokthar
+ */
+public class MoveShortcut extends ShortcutTool {
+
+    private Vector3f currentAxis;
+    private StringBuilder numberBuilder;
+    private Spatial spatial;
+    private PickManager pickManager;
+    private boolean pickEnabled;
+    private Vector3f startPosition;
+    private Vector3f finalPosition;
+
+    @Override
+
+    public boolean isActivableBy(KeyInputEvent kie) {
+        return kie.getKeyCode() == KeyInput.KEY_G;
+    }
+
+    @Override
+    public void cancel() {
+        spatial.setLocalTranslation(startPosition);
+        terminate();
+    }
+
+    private void apply() {
+        actionPerformed(new MoveUndo(toolController.getSelectedSpatial(), startPosition, finalPosition));
+        terminate();
+    }
+
+    private void init(Spatial selectedSpatial) {
+        spatial = selectedSpatial;
+        startPosition = spatial.getLocalTranslation().clone();
+        currentAxis = Vector3f.UNIT_XYZ;
+        pickManager = Lookup.getDefault().lookup(PickManager.class);
+        pickEnabled = false;
+    }
+
+    @Override
+    public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) {
+        super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates.
+        hideMarker();
+        numberBuilder = new StringBuilder();
+        if (selectedSpatial == null) {
+            terminate();
+        } else {
+            init(selectedSpatial);
+        }
+    }
+
+    @Override
+    public void keyPressed(KeyInputEvent kie) {
+        if (kie.isPressed()) {
+            Lookup.getDefault().lookup(ShortcutManager.class).activateShortcut(kie);
+
+            Vector3f axis = new Vector3f();
+            boolean axisChanged = ShortcutManager.checkAxisKey(kie, axis);
+            if (axisChanged) {
+                currentAxis = axis;
+            }
+            boolean numberChanged = ShortcutManager.checkNumberKey(kie, numberBuilder);
+            boolean enterHit = ShortcutManager.checkEnterHit(kie);
+            boolean escHit = ShortcutManager.checkEscHit(kie);
+
+            if (escHit) {
+                cancel();
+            } else if (enterHit) {
+                apply();
+            } else if (axisChanged && pickEnabled) {
+                //update pick manager
+
+                if (currentAxis.equals(Vector3f.UNIT_X)) {
+                    pickManager.setTransformation(PickManager.PLANE_XY, getTransformType(), camera);
+                } else if (currentAxis.equals(Vector3f.UNIT_Y)) {
+                    pickManager.setTransformation(PickManager.PLANE_YZ, getTransformType(), camera);
+                } else if (currentAxis.equals(Vector3f.UNIT_Z)) {
+                    pickManager.setTransformation(PickManager.PLANE_XZ, getTransformType(), camera);
+                }
+            } else if (axisChanged || numberChanged) {
+                //update transformation
+                float number = ShortcutManager.getNumberKey(numberBuilder);
+                Vector3f translation = currentAxis.mult(number);
+                finalPosition = startPosition.add(translation);
+                spatial.setLocalTranslation(finalPosition);
+
+            }
+
+        }
+    }
+
+    @Override
+    public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+        if (pressed) {
+            apply();
+        }
+    }
+
+    @Override
+    public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+        if (pressed) {
+            cancel();
+        }
+    }
+
+    @Override
+    public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) {
+
+        if (!pickEnabled) {
+            if (currentAxis.equals(Vector3f.UNIT_XYZ)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), SceneComposerToolController.TransformationType.camera, camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_X)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_Y)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_Z)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else {
+                return;
+            }
+        }
+
+        if (pickManager.updatePick(camera, screenCoord)) {
+            //pick update success
+            Vector3f diff;
+
+            if (currentAxis.equals(Vector3f.UNIT_XYZ)) {
+                diff = pickManager.getTranslation();
+            } else {
+                diff = pickManager.getTranslation(currentAxis);
+            }
+            Vector3f position = startPosition.add(diff);
+            finalPosition = position;
+            toolController.getSelectedSpatial().setLocalTranslation(position);
+            updateToolsTransformation();
+        }
+    }
+
+    @Override
+    public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+        if (pressed) {
+            apply();
+        }
+    }
+
+    @Override
+    public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+        if (pressed) {
+            cancel();
+        }
+    }
+    
+    private class MoveUndo extends AbstractUndoableSceneEdit {
+
+        private Spatial spatial;
+        private Vector3f before = new Vector3f(), after = new Vector3f();
+
+        MoveUndo(Spatial spatial, Vector3f before, Vector3f after) {
+            this.spatial = spatial;
+            this.before.set(before);
+            if (after != null) {
+                this.after.set(after);
+            }
+        }
+
+        @Override
+        public void sceneUndo() {
+            spatial.setLocalTranslation(before);
+            RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
+            if (control != null) {
+                control.setPhysicsLocation(spatial.getWorldTranslation());
+            }
+            CharacterControl character = spatial.getControl(CharacterControl.class);
+            if (character != null) {
+                character.setPhysicsLocation(spatial.getWorldTranslation());
+            }
+            //     toolController.selectedSpatialTransformed();
+        }
+
+        @Override
+        public void sceneRedo() {
+            spatial.setLocalTranslation(after);
+            RigidBodyControl control = spatial.getControl(RigidBodyControl.class);
+            if (control != null) {
+                control.setPhysicsLocation(spatial.getWorldTranslation());
+            }
+            CharacterControl character = spatial.getControl(CharacterControl.class);
+            if (character != null) {
+                character.setPhysicsLocation(spatial.getWorldTranslation());
+            }
+            //toolController.selectedSpatialTransformed();
+        }
+
+        public void setAfter(Vector3f after) {
+            this.after.set(after);
+        }
+    }
+
+}

+ 189 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/RotateShortcut.java

@@ -0,0 +1,189 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.tools.shortcuts;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
+import com.jme3.gde.scenecomposer.SceneComposerToolController;
+import com.jme3.gde.scenecomposer.tools.PickManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import org.openide.loaders.DataObject;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author dokthar
+ */
+public class RotateShortcut extends ShortcutTool {
+
+    private Vector3f currentAxis;
+    private StringBuilder numberBuilder;
+    private Spatial spatial;
+    private PickManager pickManager;
+    private boolean pickEnabled;
+    private Quaternion startRotation;
+    private Quaternion finalRotation;
+
+    @Override
+
+    public boolean isActivableBy(KeyInputEvent kie) {
+        return kie.getKeyCode() == KeyInput.KEY_R;
+    }
+
+    @Override
+    public void cancel() {
+        spatial.setLocalRotation(startRotation);
+        terminate();
+    }
+
+    private void apply() {
+        actionPerformed(new RotateUndo(toolController.getSelectedSpatial(), startRotation, finalRotation));
+        terminate();
+    }
+
+    private void init(Spatial selectedSpatial) {
+        spatial = selectedSpatial;
+        startRotation = spatial.getLocalRotation().clone();
+        currentAxis = Vector3f.UNIT_XYZ;
+        pickManager = Lookup.getDefault().lookup(PickManager.class);
+        pickEnabled = false;
+    }
+
+    @Override
+    public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) {
+        super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates.
+        hideMarker();
+        numberBuilder = new StringBuilder();
+        if (selectedSpatial == null) {
+            terminate();
+        } else {
+            init(selectedSpatial);
+        }
+    }
+
+    @Override
+    public void keyPressed(KeyInputEvent kie) {
+        if (kie.isPressed()) {
+            Lookup.getDefault().lookup(ShortcutManager.class).activateShortcut(kie);
+
+            Vector3f axis = new Vector3f();
+            boolean axisChanged = ShortcutManager.checkAxisKey(kie, axis);
+            if (axisChanged) {
+                currentAxis = axis;
+            }
+            boolean numberChanged = ShortcutManager.checkNumberKey(kie, numberBuilder);
+            boolean enterHit = ShortcutManager.checkEnterHit(kie);
+            boolean escHit = ShortcutManager.checkEscHit(kie);
+
+            if (escHit) {
+                cancel();
+            } else if (enterHit) {
+                apply();
+            } else if (axisChanged && pickEnabled) {
+                pickEnabled = false;
+                spatial.setLocalRotation(startRotation.clone());
+            } else if (axisChanged || numberChanged) {
+                //update transformation
+       /*         float number = ShortcutManager.getNumberKey(numberBuilder);
+                 Vector3f translation = currentAxis.mult(number);
+                 finalPosition = startPosition.add(translation);
+                 spatial.setLocalTranslation(finalPosition);
+                 */
+            }
+
+        }
+    }
+
+    @Override
+    public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+        if (pressed) {
+            apply();
+        }
+    }
+
+    @Override
+    public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+        if (pressed) {
+            cancel();
+        }
+    }
+
+    @Override
+    public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) {
+
+        if (!pickEnabled) {
+            if (currentAxis.equals(Vector3f.UNIT_XYZ)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), SceneComposerToolController.TransformationType.camera, camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_X)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_Y)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_Z)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else {
+                return;
+            }
+        }
+
+        if (pickManager.updatePick(camera, screenCoord)) {
+
+            Quaternion rotation = startRotation.mult(pickManager.getRotation(startRotation.inverse()));
+            toolController.getSelectedSpatial().setLocalRotation(rotation);
+            finalRotation = rotation;
+            updateToolsTransformation();
+        }
+    }
+
+    @Override
+    public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+        if (pressed) {
+            apply();
+        }
+    }
+
+    @Override
+    public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+        if (pressed) {
+            cancel();
+        }
+    }
+
+    private class RotateUndo extends AbstractUndoableSceneEdit {
+
+        private Spatial spatial;
+        private Quaternion before, after;
+
+        RotateUndo(Spatial spatial, Quaternion before, Quaternion after) {
+            this.spatial = spatial;
+            this.before = before;
+            this.after = after;
+        }
+
+        @Override
+        public void sceneUndo() {
+            spatial.setLocalRotation(before);
+            toolController.selectedSpatialTransformed();
+        }
+
+        @Override
+        public void sceneRedo() {
+            spatial.setLocalRotation(after);
+            toolController.selectedSpatialTransformed();
+        }
+    }
+}

+ 199 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ScaleShortcut.java

@@ -0,0 +1,199 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.tools.shortcuts;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
+import com.jme3.gde.scenecomposer.SceneComposerToolController;
+import com.jme3.gde.scenecomposer.tools.PickManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import org.openide.loaders.DataObject;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author dokthar
+ */
+public class ScaleShortcut extends ShortcutTool {
+
+    private Vector3f currentAxis;
+    private StringBuilder numberBuilder;
+    private Spatial spatial;
+    private PickManager pickManager;
+    private boolean pickEnabled;
+    private Vector3f startScale;
+    private Vector3f finalScale;
+
+    @Override
+
+    public boolean isActivableBy(KeyInputEvent kie) {
+        return kie.getKeyCode() == KeyInput.KEY_S;
+    }
+
+    @Override
+    public void cancel() {
+        spatial.setLocalScale(startScale);
+        terminate();
+    }
+
+    private void apply() {
+        actionPerformed(new ScaleUndo(toolController.getSelectedSpatial(), startScale, finalScale));
+        terminate();
+    }
+
+    private void init(Spatial selectedSpatial) {
+        spatial = selectedSpatial;
+        startScale = spatial.getLocalScale().clone();
+        currentAxis = Vector3f.UNIT_XYZ;
+        pickManager = Lookup.getDefault().lookup(PickManager.class);
+        pickEnabled = false;
+    }
+
+    @Override
+    public void activate(AssetManager manager, Node toolNode, Node onTopToolNode, Spatial selectedSpatial, SceneComposerToolController toolController) {
+        super.activate(manager, toolNode, onTopToolNode, selectedSpatial, toolController); //To change body of generated methods, choose Tools | Templates.
+        hideMarker();
+        numberBuilder = new StringBuilder();
+        if (selectedSpatial == null) {
+            terminate();
+        } else {
+            init(selectedSpatial);
+        }
+    }
+
+    @Override
+    public void keyPressed(KeyInputEvent kie) {
+        if (kie.isPressed()) {
+            Lookup.getDefault().lookup(ShortcutManager.class).activateShortcut(kie);
+
+            Vector3f axis = new Vector3f();
+            boolean axisChanged = ShortcutManager.checkAxisKey(kie, axis);
+            if (axisChanged) {
+                currentAxis = axis;
+            }
+            boolean numberChanged = ShortcutManager.checkNumberKey(kie, numberBuilder);
+            boolean enterHit = ShortcutManager.checkEnterHit(kie);
+            boolean escHit = ShortcutManager.checkEscHit(kie);
+
+            if (escHit) {
+                cancel();
+            } else if (enterHit) {
+                apply();
+            } else if (axisChanged && pickEnabled) {
+                pickEnabled = false;
+            } else if (axisChanged || numberChanged) {
+                //update transformation
+       /*         float number = ShortcutManager.getNumberKey(numberBuilder);
+                 Vector3f translation = currentAxis.mult(number);
+                 finalPosition = startPosition.add(translation);
+                 spatial.setLocalTranslation(finalPosition);
+                 */
+            }
+
+        }
+    }
+
+    @Override
+    public void actionPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+        if (pressed) {
+            apply();
+        }
+    }
+
+    @Override
+    public void actionSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject dataObject) {
+        if (pressed) {
+            cancel();
+        }
+    }
+
+    @Override
+    public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject dataObject, JmeSpatial selectedSpatial) {
+
+        if (!pickEnabled) {
+            if (currentAxis.equals(Vector3f.UNIT_XYZ)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), camera.getRotation(), SceneComposerToolController.TransformationType.camera, camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_X)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XY, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_Y)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_YZ, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else if (currentAxis.equals(Vector3f.UNIT_Z)) {
+                pickManager.initiatePick(toolController.getSelectedSpatial(), PickManager.PLANE_XZ, getTransformType(), camera, screenCoord);
+                pickEnabled = true;
+            } else {
+                return;
+            }
+        }
+
+        if (pickManager.updatePick(camera, screenCoord)) {
+            Vector3f scale = startScale;
+            if (currentAxis.equals(Vector3f.UNIT_XYZ)) {
+                Vector3f constraintAxis = pickManager.getStartOffset().normalize();
+                float diff = pickManager.getTranslation(constraintAxis).dot(constraintAxis);
+                diff *= 0.5f;
+                scale = startScale.add(new Vector3f(diff, diff, diff));
+            } else {
+                // Get the translation in the spatial Space
+                Quaternion worldToSpatial = toolController.getSelectedSpatial().getWorldRotation().inverse();
+                Vector3f diff = pickManager.getTranslation(worldToSpatial.mult(currentAxis));
+                diff.multLocal(0.5f);
+                scale = startScale.add(diff);
+            }
+            finalScale = scale;
+            toolController.getSelectedSpatial().setLocalScale(scale);
+            updateToolsTransformation();
+        }
+    }
+
+    @Override
+    public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+        if (pressed) {
+            apply();
+        }
+    }
+
+    @Override
+    public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
+        if (pressed) {
+            cancel();
+        }
+    }
+
+    private class ScaleUndo extends AbstractUndoableSceneEdit {
+
+        private Spatial spatial;
+        private Vector3f before, after;
+
+        ScaleUndo(Spatial spatial, Vector3f before, Vector3f after) {
+            this.spatial = spatial;
+            this.before = before;
+            this.after = after;
+        }
+
+        @Override
+        public void sceneUndo() {
+            spatial.setLocalScale(before);
+            toolController.selectedSpatialTransformed();
+        }
+
+        @Override
+        public void sceneRedo() {
+            spatial.setLocalScale(after);
+            toolController.selectedSpatialTransformed();
+        }
+    }
+}

+ 335 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutManager.java

@@ -0,0 +1,335 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.tools.shortcuts;
+
+import com.jme3.gde.scenecomposer.SceneEditTool;
+import com.jme3.input.KeyInput;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.math.Vector3f;
+import java.util.ArrayList;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author dokthar
+ */
+@ServiceProvider(service = ShortcutManager.class)
+public class ShortcutManager {
+
+    private ShortcutTool currentShortcut;
+    private ArrayList<ShortcutTool> shortcutList;
+    private boolean ctrlDown = false;
+    private boolean shiftDown = false;
+    private boolean altDown = false;
+
+    public ShortcutManager() {
+        shortcutList = new ArrayList<ShortcutTool>();
+        shortcutList.add(new MoveShortcut());
+        shortcutList.add(new RotateShortcut());
+        shortcutList.add(new ScaleShortcut());
+        shortcutList.add(new DuplicateShortcut());
+        shortcutList.add(new DeleteShortcut());
+    }
+
+    /*
+     Methodes 
+     */
+    /**
+     * This MUST be called by the shortcut tool once the modifications are done.
+     */
+    public void terminate() {
+        currentShortcut = null;
+    }
+
+    /**
+     *
+     * @return true if a shortCutTool is active, else return false.
+     */
+    public boolean isActive() {
+        return currentShortcut != null;
+    }
+
+    /**
+     * @return the ctrlDown
+     */
+    public boolean isCtrlDown() {
+        return ctrlDown;
+    }
+
+    /**
+     * @return the shiftDown
+     */
+    public boolean isShiftDown() {
+        return shiftDown;
+    }
+
+    /**
+     * @return the altDown
+     */
+    public boolean isAltDown() {
+        return altDown;
+    }
+
+    /**
+     * Set the current shortcut to <code>shortcut</code>. cancel the current
+     * shortcut if it was still active
+     *
+     * @param shortcut the ShortCutTool to set
+     */
+    public void setShortCut(ShortcutTool shortcut) {
+        if (isActive()) {
+            currentShortcut.cancel();
+        }
+        currentShortcut = shortcut;
+    }
+
+    /**
+     * Get the shortcut that can be enable with the given kei, the current
+     * shortcut cannot be enable twice. This also check for command key used to
+     * provide isCtrlDown(), isShiftDown() and isAltDown().
+     *
+     * @param kie the KeyInputEvent
+     * @return the activable shortcut else return null
+     */
+    public ShortcutTool getActivableShortcut(KeyInputEvent kie) {
+        if (checkCommandeKey(kie)) {
+            return null;
+        }
+        for (ShortcutTool s : shortcutList) {
+            if (s != currentShortcut) {
+                if (s.isActivableBy(kie)) {
+                    return s;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     *
+     * @return the current active shortcut
+     */
+    public ShortcutTool getActiveShortcut() {
+        return currentShortcut;
+    }
+
+    /**
+     *
+     * @param kie the KeyInputEvent
+     * @return true if the given Kei can enable a sortcut, else false
+     */
+    public boolean canActivateShortcut(KeyInputEvent kie) {
+        return getActivableShortcut(kie) != null;
+    }
+
+    /**
+     * Set the current shortcut with the shortcut one that can be enable with
+     * the given key
+     *
+     * @param kie the KeyInputEvent
+     * @return true is the shortcut changed, else false
+     */
+    public boolean activateShortcut(KeyInputEvent kie) {
+        ShortcutTool newShortcut = getActivableShortcut(kie);
+        if (newShortcut != null) {
+            currentShortcut = newShortcut;
+        }
+        return newShortcut != null;
+    }
+
+    /**
+     * This should be called to trigger the currentShortcut.keyPressed() method.
+     * This also check for command key used to provide isCtrlDown(),
+     * isShiftDown() and isAltDown().
+     *
+     * @param kie
+     */
+    public void doKeyPressed(KeyInputEvent kie) {
+        if (checkCommandeKey(kie)) {
+            //return;
+        } else if (isActive()) {
+            currentShortcut.keyPressed(kie);
+        }
+    }
+
+    private boolean checkCommandeKey(KeyInputEvent kie) {
+        if (checkCtrlHit(kie)) {
+            ctrlDown = kie.isPressed();
+            return true;
+        } else if (checkAltHit(kie)) {
+            altDown = kie.isPressed();
+            return true;
+        } else if (checkShiftHit(kie)) {
+            shiftDown = kie.isPressed();
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     STATIC
+     */
+    /**
+     *
+     * @param kie
+     * @return true if the given kie is KEY_RETURN
+     */
+    public static boolean checkEnterHit(KeyInputEvent kie) {
+        if (kie.getKeyCode() == KeyInput.KEY_RETURN) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *
+     * @param kie
+     * @return true if the given kie is KEY_ESCAPE
+     */
+    public static boolean checkEscHit(KeyInputEvent kie) {
+        if (kie.getKeyCode() == KeyInput.KEY_ESCAPE) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *
+     * @param kie
+     * @return true if the given kie is KEY_LCONTROL || KEY_RCONTROL
+     */
+    public static boolean checkCtrlHit(KeyInputEvent kie) {
+        if (kie.getKeyCode() == KeyInput.KEY_LCONTROL || kie.getKeyCode() == KeyInput.KEY_RCONTROL) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *
+     * @param kie
+     * @return true if the given kie is KEY_LSHIFT || KEY_RSHIFT
+     */
+    public static boolean checkShiftHit(KeyInputEvent kie) {
+        if (kie.getKeyCode() == KeyInput.KEY_LSHIFT || kie.getKeyCode() == KeyInput.KEY_RSHIFT) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *
+     * @param kie
+     * @return true if the given kie is KEY_LMENU || KEY_RMENU
+     */
+    public static boolean checkAltHit(KeyInputEvent kie) {
+        if (kie.getKeyCode() == KeyInput.KEY_LMENU || kie.getKeyCode() == KeyInput.KEY_RMENU) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * store the number kie into the numberBuilder
+     *
+     * @param kie
+     * @param numberBuilder
+     * @return true if the given kie is handled as a number key event
+     */
+    public static boolean checkNumberKey(KeyInputEvent kie, StringBuilder numberBuilder) {
+        if (kie.getKeyCode() == KeyInput.KEY_MINUS) {
+            if (numberBuilder.length() > 0) {
+                if (numberBuilder.charAt(0) == '-') {
+                    numberBuilder.replace(0, 1, "");
+                } else {
+                    numberBuilder.insert(0, '-');
+                }
+            } else {
+                numberBuilder.append('-');
+            }
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_0 || kie.getKeyCode() == KeyInput.KEY_NUMPAD0) {
+            numberBuilder.append('0');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_1 || kie.getKeyCode() == KeyInput.KEY_NUMPAD1) {
+            numberBuilder.append('1');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_2 || kie.getKeyCode() == KeyInput.KEY_NUMPAD2) {
+            numberBuilder.append('2');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_3 || kie.getKeyCode() == KeyInput.KEY_NUMPAD3) {
+            numberBuilder.append('3');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_4 || kie.getKeyCode() == KeyInput.KEY_NUMPAD4) {
+            numberBuilder.append('4');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_5 || kie.getKeyCode() == KeyInput.KEY_NUMPAD5) {
+            numberBuilder.append('5');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_6 || kie.getKeyCode() == KeyInput.KEY_NUMPAD6) {
+            numberBuilder.append('6');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_7 || kie.getKeyCode() == KeyInput.KEY_NUMPAD7) {
+            numberBuilder.append('7');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_8 || kie.getKeyCode() == KeyInput.KEY_NUMPAD8) {
+            numberBuilder.append('8');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_9 || kie.getKeyCode() == KeyInput.KEY_NUMPAD9) {
+            numberBuilder.append('9');
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_PERIOD) {
+            if (numberBuilder.indexOf(".") == -1) { // if it doesn't exist yet
+                if (numberBuilder.length() == 0
+                        || (numberBuilder.length() == 1 && numberBuilder.charAt(0) == '-')) {
+                    numberBuilder.append("0.");
+                } else {
+                    numberBuilder.append(".");
+                }
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     *
+     * @param numberBuilder the StringBuilder storing the float number
+     * @return the float value created from the given StringBuilder
+     */
+    public static float getNumberKey(StringBuilder numberBuilder) {
+        if (numberBuilder.length() == 0) {
+            return 0;
+        } else {
+            return new Float(numberBuilder.toString());
+        }
+    }
+
+    /**
+     * Check for axis input for key X,Y,Z and store the corresponding UNIT_ into
+     * the axisStore
+     *
+     * @param kie
+     * @param axisStore
+     * @return true if the given kie is handled as a Axis input
+     */
+    public static boolean checkAxisKey(KeyInputEvent kie, Vector3f axisStore) {
+        if (kie.getKeyCode() == KeyInput.KEY_X) {
+            axisStore.set(Vector3f.UNIT_X);
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_Y) {
+            axisStore.set(Vector3f.UNIT_Y);
+            return true;
+        } else if (kie.getKeyCode() == KeyInput.KEY_Z) {
+            axisStore.set(Vector3f.UNIT_Z);
+            return true;
+        }
+        return false;
+    }
+
+}

+ 29 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/tools/shortcuts/ShortcutTool.java

@@ -0,0 +1,29 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.tools.shortcuts;
+
+import com.jme3.gde.scenecomposer.SceneEditTool;
+import com.jme3.input.event.KeyInputEvent;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author dokthar
+ */
+public abstract class ShortcutTool extends SceneEditTool {
+
+    public abstract boolean isActivableBy(KeyInputEvent kie);
+
+    public abstract void cancel();
+
+    protected final void terminate() {
+        Lookup.getDefault().lookup(ShortcutManager.class).terminate();
+    }
+
+    @Override
+    public abstract void keyPressed(KeyInputEvent kie);
+   
+}