Sfoglia il codice sorgente

Merge pull request #9 from jMonkeyEngine/master

Update fork
joliver82 4 anni fa
parent
commit
066ba48e96

+ 5 - 4
.github/workflows/main.yml

@@ -163,7 +163,6 @@ jobs:
           
           # Build
           ./gradlew  -PuseCommitHashAsVersionName=true --no-daemon  -PbuildNativeProjects=true -Dmaven.repo.local="$PWD/dist/maven" \
-          build \
           :jme3-bullet-native:build
                   
       # Upload natives to be used later by the BuildJMonkey job
@@ -180,11 +179,13 @@ jobs:
     name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }}
     runs-on: ${{ matrix.os }}    
     strategy:
-      fail-fast: true
+      fail-fast: false
       matrix:
-        os: [ubuntu-18.04,windows-2019,macOS-latest]
+        os: [ubuntu-18.04,ubuntu-20.04,windows-2019,macOS-latest]
         jdk: [8.x.x,11.x.x]
         include:
+          - os: ubuntu-20.04
+            osName: linux-next
           - os: ubuntu-18.04
             osName: linux
             deploy: true
@@ -242,7 +243,7 @@ jobs:
         shell: bash
         run: |
           # Build
-          ./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true build
+          ./gradlew -i -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true build
           
           if [ "${{ matrix.deploy }}" = "true" ];
           then  

+ 11 - 0
jme3-core/src/main/java/com/jme3/app/state/BaseAppState.java

@@ -171,6 +171,17 @@ public abstract class BaseAppState implements AppState {
         return getStateManager().getState( type, failOnMiss );
     }
 
+    public final <T extends AppState> T getState( String id, Class<T> type ) {
+        return getState(id, type, false);
+    }
+    
+    public final <T extends AppState> T getState( String id, Class<T> type, boolean failOnMiss ) {
+        if( failOnMiss ) {
+            return getStateManager().stateForId(id, type);
+        }
+        return getStateManager().getState(id, type);
+    }
+
     @Override
     public final void setEnabled( boolean enabled )
     {

+ 5 - 3
jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2015 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -64,8 +64,10 @@ public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
         int lodLevel = geom.getLodLevel();
         if (geom instanceof InstancedGeometry) {
             InstancedGeometry instGeom = (InstancedGeometry) geom;
-            renderer.renderMesh(mesh, lodLevel, instGeom.getActualNumInstances(),
-                    instGeom.getAllInstanceData());
+            int numVisibleInstances = instGeom.getNumVisibleInstances();
+            if (numVisibleInstances > 0) {
+                renderer.renderMesh(mesh, lodLevel, numVisibleInstances, instGeom.getAllInstanceData());
+            }
         } else {
             renderer.renderMesh(mesh, lodLevel, 1, null);
         }

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

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2020 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -207,7 +207,7 @@ public class Camera implements Savable, Cloneable {
     /**
      * Serialization only. Do not use.
      */
-    public Camera() {
+    protected Camera() {
         worldPlane = new Plane[MAX_WORLD_PLANES];
         for (int i = 0; i < MAX_WORLD_PLANES; i++) {
             worldPlane[i] = new Plane();

+ 6 - 0
jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java

@@ -41,6 +41,12 @@ import java.nio.FloatBuffer;
 
 public class WireFrustum extends Mesh {
 
+    /**
+     * This constructor is for serialization only. Do not use.
+     */
+    protected WireFrustum() {
+    }
+
     public WireFrustum(Vector3f[] points){
         initGeom(this, points);
     }

+ 40 - 6
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2019 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,6 +31,7 @@
  */
 package com.jme3.scene.instancing;
 
+import com.jme3.bounding.BoundingBox;
 import com.jme3.bounding.BoundingVolume;
 import com.jme3.collision.Collidable;
 import com.jme3.collision.CollisionResults;
@@ -67,7 +68,7 @@ public class InstancedGeometry extends Geometry {
     private Geometry[] geometries = new Geometry[1];
 
     private int firstUnusedIndex = 0;
-    private int numCulledGeometries = 0;
+    private int numVisibleInstances = 0;
     private Camera cam;
 
     public InstancedGeometry() {
@@ -213,8 +214,28 @@ public class InstancedGeometry extends Geometry {
         return geometries.length;
     }
 
-    public int getActualNumInstances() {
-        return firstUnusedIndex - numCulledGeometries;
+    /**
+     * @return The number of instances are visible by camera.
+     */
+    public int getNumVisibleInstances() {
+        return numVisibleInstances;
+    }
+
+    /**
+     * @return The number of instances are in this {@link InstancedGeometry}
+     */
+    public int getNumInstances() {
+        int count = 0;
+        for (int i = 0; i < geometries.length; i++) {
+            if (geometries[i] != null) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public boolean isEmpty() {
+        return getNumInstances() == 0;
     }
 
     private void swap(int idx1, int idx2) {
@@ -256,7 +277,7 @@ public class InstancedGeometry extends Geometry {
         fb.limit(fb.capacity());
         fb.position(0);
 
-        numCulledGeometries = 0;
+        int numCulledGeometries = 0;
         TempVars vars = TempVars.get();
         {
             float[] temp = vars.matrixWrite;
@@ -300,7 +321,8 @@ public class InstancedGeometry extends Geometry {
 
         fb.flip();
 
-        if (fb.limit() / INSTANCE_SIZE != (firstUnusedIndex - numCulledGeometries)) {
+        numVisibleInstances = firstUnusedIndex - numCulledGeometries;
+        if (fb.limit() / INSTANCE_SIZE != numVisibleInstances) {
             throw new AssertionError();
         }
 
@@ -370,6 +392,9 @@ public class InstancedGeometry extends Geometry {
             }
         }
 
+        if (resultBound == null) {
+            resultBound = new BoundingBox(getWorldTranslation(), 0f, 0f, 0f);
+        }
         this.worldBound = resultBound;
     }
 
@@ -432,4 +457,13 @@ public class InstancedGeometry extends Geometry {
             geometries[i] = (Geometry) geometrySavables[i];
         }
     }
+
+    /**
+     *  Destroy internal buffers.
+     */
+    protected void cleanup() {
+        BufferUtils.destroyDirectBuffer(transformInstanceData.getData());
+        transformInstanceData = null;
+        geometries = null;
+    }
 }

+ 16 - 1
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014-2020 jMonkeyEngine
+ * Copyright (c) 2014-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -218,6 +218,7 @@ public class InstancedNode extends GeometryGroupNode {
                     + "lod-" + lookUp.lodLevel);
             ig.setMaterial(lookUp.material);
             ig.setMesh(lookUp.mesh);
+            if (lookUp.lodLevel > 0) ig.setLodLevel(lookUp.lodLevel);
             ig.setUserData(UserData.JME_PHYSICSIGNORE, true);
             ig.setCullHint(CullHint.Never);
             ig.setShadowMode(RenderQueue.ShadowMode.Inherit);
@@ -247,6 +248,9 @@ public class InstancedNode extends GeometryGroupNode {
         InstancedGeometry ig = igByGeom.remove(geom);
         if (ig != null) {
             ig.deleteInstance(geom);
+            if (ig.isEmpty()) {
+                detachChild(ig);
+            }
         }
     }
 
@@ -258,6 +262,9 @@ public class InstancedNode extends GeometryGroupNode {
                 throw new AssertionError();
             }
             oldIG.deleteInstance(geom);
+            if (oldIG.isEmpty()) {
+                detachChild(oldIG);
+            }
             newIG.addInstance(geom);
             igByGeom.put(geom, newIG);
         }
@@ -286,6 +293,14 @@ public class InstancedNode extends GeometryGroupNode {
         Spatial s = super.detachChildAt(index);
         if (s instanceof Node) {
             ungroupSceneGraph(s);
+        } else if (s instanceof InstancedGeometry) {
+            InstancedGeometry ig = (InstancedGeometry) s;
+            lookUp.mesh = ig.getMesh();
+            lookUp.material = ig.getMaterial();
+            lookUp.lodLevel = ig.getLodLevel();
+
+            instancesMap.remove(lookUp, ig);
+            ig.cleanup();
         }
         return s;
     }

+ 240 - 0
jme3-core/src/test/java/com/jme3/scene/debug/TestCloneMesh.java

@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2021 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.scene.debug;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.DesktopAssetManager;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.math.Vector3f;
+import com.jme3.util.clone.Cloner;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test cloning/saving/loading debug meshes of various types.
+ *
+ * @author Stephen Gold [email protected]
+ */
+public class TestCloneMesh {
+    // *************************************************************************
+    // new methods exposed
+
+    /**
+     * Test cloning/saving/loading an Arrow.
+     */
+    @Test
+    public void testCloneArrow() {
+        Arrow arrow = new Arrow(new Vector3f(1f, 1f, 1f));
+
+        Arrow deepClone = Cloner.deepClone(arrow);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, arrow);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        Arrow saveAndLoad = BinaryExporter.saveAndLoad(assetManager, arrow);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+
+    /**
+     * Test cloning/saving/loading a Grid.
+     */
+    @Test
+    public void testCloneGrid() {
+        Grid grid = new Grid(5, 5, 1f);
+
+        Grid deepClone = Cloner.deepClone(grid);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, grid);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        Grid saveAndLoad = BinaryExporter.saveAndLoad(assetManager, grid);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+
+    /**
+     * Test cloning/saving/loading a SkeletonDebugger.
+     */
+    public void testCloneSkeletonDebugger() {
+        Bone[] boneArray = new Bone[2];
+        boneArray[0] = new Bone("rootBone");
+        boneArray[1] = new Bone("leafBone");
+        boneArray[0].addChild(boneArray[1]);
+        Skeleton skeleton = new Skeleton(boneArray);
+        skeleton.setBindingPose();
+        SkeletonDebugger skeletonDebugger
+                = new SkeletonDebugger("sd", skeleton);
+
+        SkeletonDebugger deepClone = Cloner.deepClone(skeletonDebugger);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, skeletonDebugger);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        SkeletonDebugger saveAndLoad
+                = BinaryExporter.saveAndLoad(assetManager, skeletonDebugger);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+
+    /**
+     * Test cloning/saving/loading a SkeletonInterBoneWire.
+     */
+    public void testCloneSkeletonInterBoneWire() {
+        Bone[] boneArray = new Bone[2];
+        boneArray[0] = new Bone("rootBone");
+        boneArray[1] = new Bone("leafBone");
+        boneArray[0].addChild(boneArray[1]);
+        Skeleton skeleton = new Skeleton(boneArray);
+        skeleton.setBindingPose();
+        Map<Integer, Float> boneLengths = new HashMap<>();
+        boneLengths.put(0, 2f);
+        boneLengths.put(1, 1f);
+        SkeletonInterBoneWire sibw
+                = new SkeletonInterBoneWire(skeleton, boneLengths);
+
+        SkeletonInterBoneWire deepClone = Cloner.deepClone(sibw);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, sibw);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        SkeletonInterBoneWire saveAndLoad
+                = BinaryExporter.saveAndLoad(assetManager, sibw);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+
+    /**
+     * Test cloning/saving/loading a SkeletonPoints.
+     */
+    public void testCloneSkeletonPoints() {
+        Bone[] boneArray = new Bone[2];
+        boneArray[0] = new Bone("rootBone");
+        boneArray[1] = new Bone("leafBone");
+        boneArray[0].addChild(boneArray[1]);
+        Skeleton skeleton = new Skeleton(boneArray);
+        skeleton.setBindingPose();
+        SkeletonPoints skeletonPoints = new SkeletonPoints(skeleton);
+
+        SkeletonPoints deepClone = Cloner.deepClone(skeletonPoints);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, skeletonPoints);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        SkeletonPoints saveAndLoad
+                = BinaryExporter.saveAndLoad(assetManager, skeletonPoints);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+
+    /**
+     * Test cloning/saving/loading a SkeletonWire.
+     */
+    public void testCloneSkeletonWire() {
+        Bone[] boneArray = new Bone[2];
+        boneArray[0] = new Bone("rootBone");
+        boneArray[1] = new Bone("leafBone");
+        boneArray[0].addChild(boneArray[1]);
+        Skeleton skeleton = new Skeleton(boneArray);
+        SkeletonWire skeletonWire = new SkeletonWire(skeleton);
+
+        SkeletonWire deepClone = Cloner.deepClone(skeletonWire);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, skeletonWire);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        SkeletonWire saveAndLoad
+                = BinaryExporter.saveAndLoad(assetManager, skeletonWire);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+
+    /**
+     * Test cloning/saving/loading a WireBox.
+     */
+    @Test
+    public void testCloneWireBox() {
+        WireBox box = new WireBox(0.5f, 0.5f, 0.5f);
+
+        WireBox deepClone = Cloner.deepClone(box);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, box);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        WireBox saveAndLoad = BinaryExporter.saveAndLoad(assetManager, box);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+
+    /**
+     * Test cloning/saving/loading a WireSphere.
+     */
+    @Test
+    public void testCloneWireSphere() {
+        WireSphere sphere = new WireSphere(1f);
+
+        WireSphere deepClone = Cloner.deepClone(sphere);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, sphere);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        WireSphere saveAndLoad
+                = BinaryExporter.saveAndLoad(assetManager, sphere);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+
+    /**
+     * Test cloning/saving/loading a WireFrustum.
+     */
+    @Test
+    public void testIssue1462() {
+        Vector3f[] vertices = new Vector3f[8];
+        for (int vertexIndex = 0; vertexIndex < 8; vertexIndex++) {
+            vertices[vertexIndex] = new Vector3f();
+        }
+        WireFrustum wireFrustum = new WireFrustum(vertices);
+        WireFrustum deepClone = Cloner.deepClone(wireFrustum);
+        Assert.assertNotNull(deepClone);
+        Assert.assertNotEquals(deepClone, wireFrustum);
+
+        AssetManager assetManager = new DesktopAssetManager();
+        WireFrustum saveAndLoad
+                = BinaryExporter.saveAndLoad(assetManager, wireFrustum);
+        Assert.assertNotNull(saveAndLoad);
+        Assert.assertNotEquals(deepClone, saveAndLoad);
+    }
+}

+ 195 - 0
jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancedNodeAttachDetachWithPicking.java

@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2009-2021 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 jme3test.scene.instancing;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.font.BitmapText;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.instancing.InstancedNode;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.system.AppSettings;
+
+
+/**
+ * A test case for using instancing with ray casting.
+ *
+ * Based on distance from camera, swap in/out more/less detailed geometry to/from an InstancedNode.
+ *
+ * @author duncanj
+ */
+public class TestInstancedNodeAttachDetachWithPicking extends SimpleApplication {
+    public static void main(String[] args) {
+        TestInstancedNodeAttachDetachWithPicking app = new TestInstancedNodeAttachDetachWithPicking();
+        AppSettings settings = new AppSettings(true);
+        settings.setVSync(false);
+        app.setSettings(settings);
+        app.start();
+    }
+
+    private InstancedNode instancedNode;
+
+    private Vector3f[] locations = new Vector3f[10];
+    private Geometry[] spheres = new Geometry[10];
+    private Geometry[] boxes = new Geometry[10];
+
+    @Override
+    public void simpleInitApp() {
+        addPointLight();
+        addAmbientLight();
+
+        Material material = createInstancedLightingMaterial();
+
+        instancedNode = new InstancedNode("theParentInstancedNode");
+        rootNode.attachChild(instancedNode);
+        Sphere sphereMesh = new Sphere(16, 16, 1f);
+        Box boxMesh = new Box(0.7f, 0.7f, 0.7f);
+        // create 10 spheres & boxes, positioned along Z-axis successively further from the camera
+        for (int i = 0; i < 10; i++) {
+            Vector3f location = new Vector3f(0, -3, -(i*5));
+            locations[i] = location;
+
+            Geometry sphere = new Geometry("sphere", sphereMesh);
+            sphere.setMaterial(material);
+            sphere.setLocalTranslation(location);
+            instancedNode.attachChild(sphere);       // initially just add the spheres to the InstancedNode
+            spheres[i] = sphere;
+
+            Geometry box = new Geometry("box", boxMesh);
+            box.setMaterial(material);
+            box.setLocalTranslation(location);
+            boxes[i] = box;
+        }
+        instancedNode.instance();
+
+        flyCam.setMoveSpeed(30);
+
+
+        addCrossHairs();
+
+        // when you left-click, print the distance to the object to system.out
+        inputManager.addMapping("leftClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if( isPressed ) {
+                    CollisionResult result = pickFromCamera();
+                    if( result != null ) {
+                        System.out.println("Picked = " + result.getGeometry() + ", Distance = "+result.getDistance());
+                    }
+                }
+            }
+        }, "leftClick");
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        // Each frame, determine the distance to each sphere/box from the camera.
+        // If the object is > 25 units away, switch in the Box.  If it's nearer, switch in the Sphere.
+        // Normally we wouldn't do this every frame, only when player has moved a sufficient distance, etc.
+
+
+        boolean modified = false;
+        for (int i = 0; i < 10; i++) {
+            Vector3f location = locations[i];
+            float distance = location.distance(cam.getLocation());
+
+            if(distance > 25.0f && boxes[i].getParent() == null) {
+                modified = true;
+                instancedNode.attachChild(boxes[i]);
+                instancedNode.detachChild(spheres[i]);
+            } else if(distance <= 25.0f && spheres[i].getParent() == null) {
+                modified = true;
+                instancedNode.attachChild(spheres[i]);
+                instancedNode.detachChild(boxes[i]);
+            }
+        }
+
+        if(modified) {
+            instancedNode.instance();
+        }
+    }
+
+    private Material createInstancedLightingMaterial() {
+        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        material.setBoolean("UseMaterialColors", true);
+        material.setBoolean("UseInstancing", true);
+        material.setColor("Ambient", ColorRGBA.Red);
+        material.setColor("Diffuse", ColorRGBA.Red);
+        material.setColor("Specular", ColorRGBA.Red);
+        material.setFloat("Shininess", 1.0f);
+        return material;
+    }
+
+    private void addAmbientLight() {
+        AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f));
+        rootNode.addLight(ambientLight);
+    }
+
+    private void addPointLight() {
+        PointLight pointLight = new PointLight();
+        pointLight.setColor(ColorRGBA.White);
+        pointLight.setRadius(100f);
+        pointLight.setPosition(new Vector3f(10f, 10f, 0));
+        rootNode.addLight(pointLight);
+    }
+
+    private void addCrossHairs() {
+        BitmapText ch = new BitmapText(guiFont, false);
+        ch.setSize(guiFont.getCharSet().getRenderedSize()+4);
+        ch.setText("+"); // crosshairs
+        ch.setColor(ColorRGBA.White);
+        ch.setLocalTranslation( // center
+                settings.getWidth() / 2 - ch.getLineWidth() / 2,
+                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+        guiNode.attachChild(ch);
+    }
+
+    private CollisionResult pickFromCamera() {
+        CollisionResults results = new CollisionResults();
+        Ray ray = new Ray(cam.getLocation(), cam.getDirection());
+        instancedNode.collideWith(ray, results);
+        return results.getClosestCollision();
+    }
+}

+ 176 - 0
jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancedNodeAttachDetachWithShadowFilter.java

@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2021 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 jme3test.scene.instancing;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.instancing.InstancedNode;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.shadow.DirectionalLightShadowFilter;
+import com.jme3.shadow.EdgeFilteringMode;
+import com.jme3.system.AppSettings;
+
+/**
+ * A test case for using instancing with shadow filter.
+ *
+ * Based on distance from camera, swap in/out more/less detailed geometry to/from an InstancedNode.
+ *
+ * @author duncanj
+ */
+public class TestInstancedNodeAttachDetachWithShadowFilter extends SimpleApplication {
+    public static void main(String[] args) {
+        TestInstancedNodeAttachDetachWithShadowFilter app = new TestInstancedNodeAttachDetachWithShadowFilter();
+        AppSettings settings = new AppSettings(true);
+        settings.setVSync(false);
+        app.setSettings(settings);
+        app.start();
+    }
+
+    private FilterPostProcessor filterPostProcessor;
+    private InstancedNode instancedNode;
+
+    private Vector3f[] locations = new Vector3f[10];
+    private Geometry[] spheres = new Geometry[10];
+    private Geometry[] boxes = new Geometry[10];
+
+    @Override
+    public void simpleInitApp() {
+        filterPostProcessor = new FilterPostProcessor(assetManager);
+        getViewPort().addProcessor(filterPostProcessor);
+
+        addDirectionalLight();
+        addAmbientLight();
+
+        Material instancingMaterial = createLightingMaterial(true, ColorRGBA.LightGray);
+
+        instancedNode = new InstancedNode("theParentInstancedNode");
+        instancedNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
+        rootNode.attachChild(instancedNode);
+
+        // create 10 spheres & boxes, along the z-axis, successively further from the camera
+        Mesh sphereMesh = new Sphere(32, 32, 1f);
+        Mesh boxMesh = new Box(0.7f, 0.7f, 0.7f);
+        for (int z = 0; z < 10; z++) {
+            Vector3f location = new Vector3f(0, -3, -(z * 4));
+            locations[z] = location;
+
+            Geometry sphere = new Geometry("sphere", sphereMesh);
+            sphere.setMaterial(instancingMaterial);
+            sphere.setLocalTranslation(location);
+            instancedNode.attachChild(sphere);       // initially just add the spheres to the InstancedNode
+            spheres[z] = sphere;
+
+            Geometry box = new Geometry("box", boxMesh);
+            box.setMaterial(instancingMaterial);
+            box.setLocalTranslation(location);
+            boxes[z] = box;
+        }
+
+        instancedNode.instance();
+
+
+        Geometry floor = new Geometry("floor", new Box(20, 0.1f, 40));
+        floor.setMaterial(createLightingMaterial(false, ColorRGBA.Yellow));
+        floor.setLocalTranslation(5, -5, 0);
+        floor.setShadowMode(RenderQueue.ShadowMode.Receive);
+        rootNode.attachChild(floor);
+
+        flyCam.setMoveSpeed(30);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        // Each frame, determine the distance to each sphere/box from the camera.
+        // If the object is > 25 units away, switch in the Box.  If it's nearer, switch in the Sphere.
+        // Normally we wouldn't do this every frame, only when player has moved a sufficient distance, etc.
+
+        boolean modified = false;
+        for (int i = 0; i < 10; i++) {
+            Vector3f location = locations[i];
+            float distance = location.distance(cam.getLocation());
+
+            if(distance > 25.0f && boxes[i].getParent() == null) {
+                modified = true;
+                instancedNode.attachChild(boxes[i]);
+                instancedNode.detachChild(spheres[i]);
+            } else if(distance <= 25.0f && spheres[i].getParent() == null) {
+                modified = true;
+                instancedNode.attachChild(spheres[i]);
+                instancedNode.detachChild(boxes[i]);
+            }
+        }
+
+        if(modified) {
+            instancedNode.instance();
+        }
+    }
+
+    private Material createLightingMaterial(boolean useInstancing, ColorRGBA color) {
+        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        material.setBoolean("UseMaterialColors", true);
+        material.setBoolean("UseInstancing", useInstancing);
+        material.setColor("Ambient", color);
+        material.setColor("Diffuse", color);
+        material.setColor("Specular", color);
+        material.setFloat("Shininess", 1.0f);
+        return material;
+    }
+
+    private void addAmbientLight() {
+        AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f));
+        rootNode.addLight(ambientLight);
+    }
+
+    private void addDirectionalLight() {
+        DirectionalLight light = new DirectionalLight();
+
+        light.setColor(ColorRGBA.White);
+        light.setDirection(new Vector3f(-1, -1, -1));
+
+        DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 1);
+        dlsf.setLight(light);
+        dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
+        filterPostProcessor.addFilter(dlsf);
+
+        rootNode.addLight(light);
+    }
+}

+ 20 - 0
jme3-lwjgl3/build.gradle

@@ -16,19 +16,39 @@ dependencies {
     compile "org.lwjgl:lwjgl-openal:${lwjglVersion}"
     compile "org.lwjgl:lwjgl-opencl:${lwjglVersion}"
     compile "org.lwjgl:lwjgl-opengl:${lwjglVersion}"
+
     runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-windows"
+    runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-windows-x86"
     runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-linux"
+    runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-linux-arm32"
+    runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-linux-arm64"
     runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-macos"
+
     runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-windows"
+    runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-windows-x86"
     runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-linux"
+    runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-linux-arm32"
+    runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-linux-arm64"
     runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-macos"
+
     runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-windows"
+    runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-windows-x86"
     runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-linux"
+    runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-linux-arm32"
+    runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-linux-arm64"
     runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-macos"
+
     runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-windows"
+    runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-windows-x86"
     runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-linux"
+    runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-linux-arm32"
+    runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-linux-arm64"
     runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-macos"
+
     runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-windows"
+    runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-windows-x86"
     runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-linux"
+    runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-linux-arm32"
+    runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-linux-arm64"
     runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-macos"
 }

+ 2 - 2
jme3-vr/src/main/java/com/jme3/app/VRApplication.java

@@ -251,7 +251,7 @@ public abstract class VRApplication implements Application, SystemListener {
         
         guiNode.setQueueBucket(Bucket.Gui);
         guiNode.setCullHint(CullHint.Never);
-        dummyCam = new Camera();
+        dummyCam = new Camera(0, 0);
         
         initStateManager();
 
@@ -1548,4 +1548,4 @@ public abstract class VRApplication implements Application, SystemListener {
     public AppProfiler getAppProfiler() {
         return null;
     }
-}
+}

+ 1 - 1
jme3-vr/src/main/java/com/jme3/app/VREnvironment.java

@@ -384,7 +384,7 @@ public class VREnvironment {
     				if ((settings != null) && (settings.getWidth() != 0) && (settings.getHeight() != 0)){
     		        	dummyCam = new Camera(settings.getWidth(), settings.getHeight());
     		        } else {
-    		        	dummyCam = new Camera();
+    		        	dummyCam = new Camera(0, 0);
     		        }
     			}
     		} else {