diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java
index 956fc7744..242f41773 100644
--- a/jme3-core/src/main/java/com/jme3/material/Material.java
+++ b/jme3-core/src/main/java/com/jme3/material/Material.java
@@ -702,7 +702,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
int lodLevel = geom.getLodLevel();
if (geom instanceof InstancedGeometry) {
InstancedGeometry instGeom = (InstancedGeometry) geom;
- renderer.renderMesh(mesh, lodLevel, instGeom.getCurrentNumInstances(), instGeom.getAllInstanceData());
+ int numInstances = instGeom.getActualNumInstances();
+ if (numInstances == 0) {
+ return;
+ }
+ renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData());
} else {
renderer.renderMesh(mesh, lodLevel, 1, null);
}
diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
index 86f922c2a..d9259ab99 100644
--- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
+++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
@@ -39,172 +39,49 @@ import com.jme3.export.Savable;
import com.jme3.math.Matrix3f;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
-import com.jme3.math.Transform;
-import com.jme3.renderer.Camera;
-import com.jme3.renderer.RenderManager;
-import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
-import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
-import com.jme3.scene.control.AbstractControl;
import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-/**
- * InstancedGeometry
allows rendering many similar
- * geometries efficiently through a feature called geometry
- * instancing.
- *
- *
- * All rendered geometries share material, mesh, and lod level
- * but have different world transforms or possibly other parameters.
- * The settings for all instances are inherited from this geometry's
- * {@link #setMesh(com.jme3.scene.Mesh) mesh},
- * {@link #setMaterial(com.jme3.material.Material) material} and
- * {@link #setLodLevel(int) lod level} and cannot be changed per-instance.
- *
- *
- *
- * In order to receive any per-instance parameters, the material's shader
- * must be changed to retrieve per-instance data via
- * {@link VertexBuffer#setInstanced(boolean) instanced vertex attributes}
- * or uniform arrays indexed with the GLSL built-in uniform
- * gl_InstanceID
. At the very least, they should use the
- * functions specified in Instancing.glsllib
shader library
- * to transform vertex positions and normals instead of multiplying by the
- * built-in matrix uniforms.
- *
- *
- *
- * This class can operate in two modes, {@link InstancedGeometry.Mode#Auto}
- * and {@link InstancedGeometry.Mode#Manual}. See the respective enums
- * for more information
- *
- *
- * Prior to usage, the maximum number of instances must be set via
- * {@link #setMaxNumInstances(int) } and the current number of instances set
- * via {@link #setCurrentNumInstances(int) }. The user is then
- * expected to provide transforms for all instances up to the number
- * of current instances.
- *
- *
- * @author Kirill Vainer
- */
public class InstancedGeometry extends Geometry {
- /**
- * Indicates how the per-instance data is to be specified.
- */
- public static enum Mode {
-
- /**
- * The user must specify all per-instance transforms and
- * parameters manually via
- * {@link InstancedGeometry#setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }
- * or
- * {@link InstancedGeometry#setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }.
- */
- Manual,
-
- /**
- * The user
- * {@link InstancedGeometry#setInstanceTransform(int, com.jme3.math.Transform) provides world transforms}
- * and then uses the Instancing.glsllib
transform functions in the
- * shader to transform vertex attributes to the respective spaces.
- * Additional per-instance data can be specified via
- * {@link InstancedGeometry#setManualGlobalInstanceData(com.jme3.scene.VertexBuffer[]) }.
- * {@link #setManualCameraInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }
- * cannot be used at this mode since it is computed automatically.
- */
- Auto
- }
-
- private static class InstancedGeometryControl extends AbstractControl {
-
- private InstancedGeometry geom;
-
- public InstancedGeometryControl() {
- }
-
- public InstancedGeometryControl(InstancedGeometry geom) {
- this.geom = geom;
- }
-
- @Override
- protected void controlUpdate(float tpf) {
- }
-
- @Override
- protected void controlRender(RenderManager rm, ViewPort vp) {
- geom.renderFromControl(vp.getCamera());
- }
- }
-
private static final int INSTANCE_SIZE = 16;
- private InstancedGeometry.Mode mode;
- private InstancedGeometryControl control;
- private int currentNumInstances = 1;
- private Camera lastCamera = null;
- private Matrix4f[] worldMatrices = new Matrix4f[1];
private VertexBuffer[] globalInstanceData;
+ private VertexBuffer transformInstanceData;
+ private Geometry[] geometries = new Geometry[1];
- private final HashMap instanceDataPerCam
- = new HashMap();
-
- // TODO: determine if perhaps its better to use TempVars here.
-
- private final Matrix4f tempMat4 = new Matrix4f();
- private final Matrix4f tempMat4_2 = new Matrix4f();
- private final Matrix3f tempMat3 = new Matrix3f();
- private final Quaternion tempQuat = new Quaternion();
- private final float[] tempFloatArray = new float[16];
-
+ private int firstUnusedIndex = 0;
+
/**
* Serialization only. Do not use.
*/
public InstancedGeometry() {
super();
setIgnoreTransform(true);
+ setMaxNumInstances(1);
}
/**
* Creates instanced geometry with the specified mode and name.
*
- * @param mode The {@link Mode} at which the instanced geometry operates at.
* @param name The name of the spatial.
*
- * @see Mode
* @see Spatial#Spatial(java.lang.String)
*/
- public InstancedGeometry(InstancedGeometry.Mode mode, String name) {
+ public InstancedGeometry(String name) {
super(name);
- this.mode = mode;
setIgnoreTransform(true);
- if (mode == InstancedGeometry.Mode.Auto) {
- control = new InstancedGeometryControl(this);
- addControl(control);
- }
- }
-
- /**
- * The mode with which this instanced geometry was initialized
- * with. Cannot be changed after initialization.
- *
- * @return instanced geometry mode.
- */
- public InstancedGeometry.Mode getMode() {
- return mode;
+ setMaxNumInstances(1);
}
/**
@@ -238,170 +115,54 @@ public class InstancedGeometry extends Geometry {
/**
* Specify camera specific user per-instance data.
*
- * Only applies when operating in {@link Mode#Manual}.
- * When operating in {@link Mode#Auto}, this data is computed automatically,
- * and using this method is not allowed.
- *
- * @param camera The camera for which per-instance data is to be set.
- * @param cameraInstanceData The camera's per-instance data.
- *
- * @throws IllegalArgumentException If camera is null.
- * @throws IllegalStateException If {@link #getMode() mode} is set to
- * {@link Mode#Auto}.
- *
- * @see Mode
- * @see #getCameraUserInstanceData(com.jme3.renderer.Camera)
- */
- public void setCameraUserInstanceData(Camera camera, VertexBuffer cameraInstanceData) {
- if (mode == Mode.Auto) {
- throw new IllegalStateException("Not allowed in auto mode");
- }
- if (camera == null) {
- throw new IllegalArgumentException("camera cannot be null");
- }
- instanceDataPerCam.put(camera, cameraInstanceData);
- }
-
- /**
- * Return camera specific user per-instance data.
- *
- * Only applies when operating in {@link Mode#Manual}.
- * When operating in {@link Mode#Auto}, this data is computed automatically,
- * and using this method is not allowed.
- *
- * @param camera The camera to look up the per-instance data for.
- * @return The per-instance data, or null
if none was specified
- * for the given camera.
- *
- * @throws IllegalArgumentException If camera is null.
- * @throws IllegalStateException If {@link #getMode() mode} is set to
- * {@link Mode#Auto}.
- *
- * @see Mode
- * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer)
+ * @param transformInstanceData The transforms for each instance.
*/
- public VertexBuffer getCameraUserInstanceData(Camera camera) {
- if (mode == Mode.Auto) {
- throw new IllegalStateException("Not allowed in auto mode");
- }
- if (camera == null) {
- throw new IllegalArgumentException("camera cannot be null");
- }
- return instanceDataPerCam.get(camera);
+ public void setTransformUserInstanceData(VertexBuffer transformInstanceData) {
+ this.transformInstanceData = transformInstanceData;
}
/**
- * Return a read only map with the mappings between cameras and camera
- * specific per-instance data.
- *
- * Only applies when operating in {@link Mode#Manual}.
- * When operating in {@link Mode#Auto}, this data is computed automatically,
- * and using this method is not allowed.
- *
- * @return read only map with the mappings between cameras and camera
- * specific per-instance data.
- *
- * @throws IllegalStateException If {@link #getMode() mode} is set to
- * {@link Mode#Auto}.
+ * Return user per-instance transform data.
*
- * @see Mode
- * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer)
+ * @return The per-instance transform data.
+ *
+ * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer)
*/
- public Map getAllCameraUserInstanceData() {
- if (mode == Mode.Auto) {
- throw new IllegalStateException("Not allowed in auto mode");
- }
- return Collections.unmodifiableMap(instanceDataPerCam);
+ public VertexBuffer getTransformUserInstanceData() {
+ return transformInstanceData;
}
- private void updateInstance(Matrix4f viewMatrix, Matrix4f worldMatrix, float[] store, int offset) {
- viewMatrix.mult(worldMatrix, tempMat4);
- tempMat4.toRotationMatrix(tempMat3);
+ private void updateInstance(Matrix4f worldMatrix, float[] store,
+ int offset, Matrix3f tempMat3,
+ Quaternion tempQuat) {
+ worldMatrix.toRotationMatrix(tempMat3);
tempMat3.invertLocal();
-
+
// NOTE: No need to take the transpose in order to encode
// into quaternion, the multiplication in the shader is vec * quat
// apparently...
tempQuat.fromRotationMatrix(tempMat3);
-
+
// Column-major encoding. The "W" field in each of the encoded
// vectors represents the quaternion.
- store[offset + 0] = tempMat4.m00;
- store[offset + 1] = tempMat4.m10;
- store[offset + 2] = tempMat4.m20;
+ store[offset + 0] = worldMatrix.m00;
+ store[offset + 1] = worldMatrix.m10;
+ store[offset + 2] = worldMatrix.m20;
store[offset + 3] = tempQuat.getX();
- store[offset + 4] = tempMat4.m01;
- store[offset + 5] = tempMat4.m11;
- store[offset + 6] = tempMat4.m21;
+ store[offset + 4] = worldMatrix.m01;
+ store[offset + 5] = worldMatrix.m11;
+ store[offset + 6] = worldMatrix.m21;
store[offset + 7] = tempQuat.getY();
- store[offset + 8] = tempMat4.m02;
- store[offset + 9] = tempMat4.m12;
- store[offset + 10] = tempMat4.m22;
+ store[offset + 8] = worldMatrix.m02;
+ store[offset + 9] = worldMatrix.m12;
+ store[offset + 10] = worldMatrix.m22;
store[offset + 11] = tempQuat.getZ();
- store[offset + 12] = tempMat4.m03;
- store[offset + 13] = tempMat4.m13;
- store[offset + 14] = tempMat4.m23;
+ store[offset + 12] = worldMatrix.m03;
+ store[offset + 13] = worldMatrix.m13;
+ store[offset + 14] = worldMatrix.m23;
store[offset + 15] = tempQuat.getW();
}
- private void renderFromControl(Camera cam) {
- if (mode != Mode.Auto) {
- return;
- }
-
- // Get the instance data VBO for this camera.
- VertexBuffer instanceDataVB = instanceDataPerCam.get(cam);
- FloatBuffer instanceData;
-
- if (instanceDataVB == null) {
- // This is a new camera, create instance data VBO for it.
- instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
- instanceDataVB = new VertexBuffer(Type.InstanceData);
- instanceDataVB.setInstanced(true);
- instanceDataVB.setupData(Usage.Stream, INSTANCE_SIZE, Format.Float, instanceData);
- instanceDataPerCam.put(cam, instanceDataVB);
- } else {
- // Retrieve the current instance data buffer.
- instanceData = (FloatBuffer) instanceDataVB.getData();
- }
-
- Matrix4f viewMatrix = cam.getViewMatrix();
-
- instanceData.limit(instanceData.capacity());
- instanceData.position(0);
-
- assert currentNumInstances <= worldMatrices.length;
-
- for (int i = 0; i < currentNumInstances; i++) {
- Matrix4f worldMatrix = worldMatrices[i];
- if (worldMatrix == null) {
- worldMatrix = Matrix4f.IDENTITY;
- }
- updateInstance(viewMatrix, worldMatrix, tempFloatArray, 0);
- instanceData.put(tempFloatArray);
- }
-
- instanceData.flip();
-
- this.lastCamera = cam;
- instanceDataVB.updateData(instanceDataVB.getData());
- }
-
- /**
- * Set the current number of instances to be rendered.
- *
- * @param currentNumInstances the current number of instances to be rendered.
- *
- * @throws IllegalArgumentException If current number of instances is
- * greater than the maximum number of instances.
- */
- public void setCurrentNumInstances(int currentNumInstances) {
- if (currentNumInstances > worldMatrices.length) {
- throw new IllegalArgumentException("currentNumInstances cannot be larger than maxNumInstances");
- }
- this.currentNumInstances = currentNumInstances;
- }
-
/**
* Set the maximum amount of instances that can be rendered by this
* instanced geometry when mode is set to auto.
@@ -415,88 +176,164 @@ public class InstancedGeometry extends Geometry {
* @throws IllegalStateException If mode is set to manual.
* @throws IllegalArgumentException If maxNumInstances is zero or negative
*/
- public void setMaxNumInstances(int maxNumInstances) {
- if (mode == Mode.Manual) {
- throw new IllegalStateException("Not allowed in manual mode");
- }
+ public final void setMaxNumInstances(int maxNumInstances) {
if (maxNumInstances < 1) {
throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
}
- this.worldMatrices = new Matrix4f[maxNumInstances];
+ Geometry[] originalGeometries = geometries;
+ this.geometries = new Geometry[maxNumInstances];
- if (currentNumInstances > maxNumInstances) {
- currentNumInstances = maxNumInstances;
+ if (originalGeometries != null) {
+ System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length);
}
- // Resize instance data for each of the cameras.
- for (VertexBuffer instanceDataVB : instanceDataPerCam.values()) {
- FloatBuffer instanceData = (FloatBuffer) instanceDataVB.getData();
- if (instanceData.capacity() / INSTANCE_SIZE != worldMatrices.length) {
- // Delete old data.
- BufferUtils.destroyDirectBuffer(instanceData);
-
- // Resize instance data for this camera.
- // Create new data with new length.
- instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
- instanceDataVB.updateData(instanceData);
- }
+ // Resize instance data.
+ if (transformInstanceData != null) {
+ BufferUtils.destroyDirectBuffer(transformInstanceData.getData());
+ transformInstanceData.updateData(BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE));
+ } else if (transformInstanceData == null) {
+ transformInstanceData = new VertexBuffer(Type.InstanceData);
+ transformInstanceData.setInstanced(true);
+ transformInstanceData.setupData(Usage.Stream,
+ INSTANCE_SIZE,
+ Format.Float,
+ BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE));
}
}
public int getMaxNumInstances() {
- return worldMatrices.length;
+ return geometries.length;
}
- public int getCurrentNumInstances() {
- return currentNumInstances;
+ public int getActualNumInstances() {
+ return firstUnusedIndex;
}
- public void setInstanceTransform(int instanceIndex, Matrix4f worldTransform) {
- if (mode == Mode.Manual) {
- throw new IllegalStateException("Not allowed in manual mode");
+ private void swap(int idx1, int idx2) {
+ Geometry g = geometries[idx1];
+ geometries[idx1] = geometries[idx2];
+ geometries[idx2] = g;
+
+ if (geometries[idx1] != null) {
+ InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1);
}
- if (worldTransform == null) {
- throw new IllegalArgumentException("worldTransform cannot be null");
+ if (geometries[idx2] != null) {
+ InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2);
}
- if (instanceIndex < 0) {
- throw new IllegalArgumentException("instanceIndex cannot be smaller than zero");
+ }
+
+ private void sanitize(boolean insideEntriesNonNull) {
+ if (firstUnusedIndex >= geometries.length) {
+ throw new AssertionError();
}
- if (instanceIndex >= currentNumInstances) {
- throw new IllegalArgumentException("instanceIndex cannot be larger than currentNumInstances");
+ for (int i = 0; i < geometries.length; i++) {
+ if (i < firstUnusedIndex) {
+ if (geometries[i] == null) {
+ if (insideEntriesNonNull) {
+ throw new AssertionError();
+ }
+ } else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) {
+ throw new AssertionError();
+ }
+ } else {
+ if (geometries[i] != null) {
+ throw new AssertionError();
+ }
+ }
}
- // TODO: Determine if need to make a copy of matrix or just doing this
- // is fine.
- worldMatrices[instanceIndex] = worldTransform;
}
- public void setInstanceTransform(int instanceIndex, Transform worldTransform) {
- if (worldTransform == null) {
- throw new IllegalArgumentException("worldTransform cannot be null");
+ public void updateInstances() {
+ FloatBuffer fb = (FloatBuffer) transformInstanceData.getData();
+ fb.limit(fb.capacity());
+ fb.position(0);
+
+ TempVars vars = TempVars.get();
+ {
+ float[] temp = vars.matrixWrite;
+
+ for (int i = 0; i < firstUnusedIndex; i++) {
+ Geometry geom = geometries[i];
+
+ if (geom == null) {
+ geom = geometries[firstUnusedIndex - 1];
+
+ if (geom == null) {
+ throw new AssertionError();
+ }
+
+ swap(i, firstUnusedIndex - 1);
+
+ while (geometries[firstUnusedIndex -1] == null) {
+ firstUnusedIndex--;
+ }
+ }
+
+ Matrix4f worldMatrix = geom.getWorldMatrix();
+ updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1);
+ fb.put(temp);
+ }
}
+ vars.release();
- // Compute the world transform matrix.
- tempMat4.loadIdentity();
- tempMat4.setRotationQuaternion(worldTransform.getRotation());
- tempMat4.setTranslation(worldTransform.getTranslation());
- tempMat4_2.loadIdentity();
- tempMat4_2.scale(worldTransform.getScale());
- tempMat4.multLocal(tempMat4_2);
+ fb.flip();
- setInstanceTransform(instanceIndex, tempMat4.clone());
+ if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) {
+ throw new AssertionError();
+ }
+
+ transformInstanceData.updateData(fb);
+ }
+
+ public void deleteInstance(Geometry geom) {
+ int idx = InstancedNode.getGeometryStartIndex2(geom);
+ InstancedNode.setGeometryStartIndex2(geom, -1);
+
+ geometries[idx] = null;
+
+ if (idx == firstUnusedIndex - 1) {
+ // Deleting the last element.
+ // Move index back.
+ firstUnusedIndex--;
+ while (geometries[firstUnusedIndex] == null) {
+ firstUnusedIndex--;
+ if (firstUnusedIndex < 0) {
+ break;
+ }
+ }
+ firstUnusedIndex++;
+ } else {
+ // Deleting element in the middle
+ }
+ }
+
+ public void addInstance(Geometry geometry) {
+ if (geometry == null) {
+ throw new IllegalArgumentException("geometry cannot be null");
+ }
+
+ // Take an index from the end.
+ if (firstUnusedIndex + 1 >= geometries.length) {
+ // No more room.
+ setMaxNumInstances(getMaxNumInstances() * 2);
+ }
+
+ int freeIndex = firstUnusedIndex;
+ firstUnusedIndex++;
+
+ geometries[freeIndex] = geometry;
+ InstancedNode.setGeometryStartIndex2(geometry, freeIndex);
}
public VertexBuffer[] getAllInstanceData() {
- VertexBuffer instanceDataForCam = instanceDataPerCam.get(lastCamera);
ArrayList allData = new ArrayList();
-
- if (instanceDataForCam != null) {
- allData.add(instanceDataForCam);
+ if (transformInstanceData != null) {
+ allData.add(transformInstanceData);
}
if (globalInstanceData != null) {
allData.addAll(Arrays.asList(globalInstanceData));
}
-
return allData.toArray(new VertexBuffer[allData.size()]);
}
@@ -504,30 +341,20 @@ public class InstancedGeometry extends Geometry {
public void write(JmeExporter exporter) throws IOException {
super.write(exporter);
OutputCapsule capsule = exporter.getCapsule(this);
- capsule.write(currentNumInstances, "cur_num_instances", 1);
- capsule.write(mode, "instancing_mode", InstancedGeometry.Mode.Auto);
- if (mode == Mode.Auto) {
- capsule.write(worldMatrices, "world_matrices", null);
- }
+ //capsule.write(currentNumInstances, "cur_num_instances", 1);
+ capsule.write(geometries, "geometries", null);
}
@Override
public void read(JmeImporter importer) throws IOException {
super.read(importer);
InputCapsule capsule = importer.getCapsule(this);
- currentNumInstances = capsule.readInt("cur_num_instances", 1);
- mode = capsule.readEnum("instancing_mode", InstancedGeometry.Mode.class,
- InstancedGeometry.Mode.Auto);
-
- if (mode == Mode.Auto) {
- Savable[] matrixSavables = capsule.readSavableArray("world_matrices", null);
- worldMatrices = new Matrix4f[matrixSavables.length];
- for (int i = 0; i < worldMatrices.length; i++) {
- worldMatrices[i] = (Matrix4f) matrixSavables[i];
- }
+ //currentNumInstances = capsule.readInt("cur_num_instances", 1);
- control = getControl(InstancedGeometryControl.class);
- control.geom = this;
+ Savable[] geometrySavables = capsule.readSavableArray("geometries", null);
+ geometries = new Geometry[geometrySavables.length];
+ for (int i = 0; i < geometrySavables.length; i++) {
+ geometries[i] = (Geometry) geometrySavables[i];
}
}
}
diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
new file mode 100644
index 000000000..fa636c87e
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
@@ -0,0 +1,259 @@
+package com.jme3.scene.instancing;
+
+import com.jme3.material.Material;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.BatchNode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.GeometryGroupNode;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.UserData;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import java.util.HashMap;
+
+public class InstancedNode extends GeometryGroupNode {
+
+ static int getGeometryStartIndex2(Geometry geom) {
+ return getGeometryStartIndex(geom);
+ }
+
+ static void setGeometryStartIndex2(Geometry geom, int startIndex) {
+ setGeometryStartIndex(geom, startIndex);
+ }
+
+ private static class InstanceTypeKey implements Cloneable {
+
+ Mesh mesh;
+ Material material;
+ int lodLevel;
+
+ public InstanceTypeKey(Mesh mesh, Material material, int lodLevel) {
+ this.mesh = mesh;
+ this.material = material;
+ this.lodLevel = lodLevel;
+ }
+
+ public InstanceTypeKey(){
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 41 * hash + this.mesh.hashCode();
+ hash = 41 * hash + this.material.hashCode();
+ hash = 41 * hash + this.lodLevel;
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ final InstanceTypeKey other = (InstanceTypeKey) obj;
+ if (this.mesh != other.mesh) {
+ return false;
+ }
+ if (this.material != other.material) {
+ return false;
+ }
+ if (this.lodLevel != other.lodLevel) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public InstanceTypeKey clone() {
+ try {
+ return (InstanceTypeKey) super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ private static class InstancedNodeControl extends AbstractControl {
+
+ private InstancedNode node;
+
+ public InstancedNodeControl() {
+ }
+
+ public InstancedNodeControl(InstancedNode node) {
+ this.node = node;
+ }
+
+ @Override
+ public Control cloneForSpatial(Spatial spatial) {
+ return this;
+ // WARNING: Sets wrong control on spatial. Will be
+ // fixed automatically by InstancedNode.clone() method.
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ node.renderFromControl();
+ }
+ }
+
+ protected final HashMap igByGeom
+ = new HashMap();
+
+ private final InstanceTypeKey lookUp = new InstanceTypeKey();
+
+ private final HashMap instancesMap =
+ new HashMap();
+
+ public InstancedNode() {
+ super();
+ // NOTE: since we are deserializing,
+ // the control is going to be added automatically here.
+ }
+
+ public InstancedNode(String name) {
+ super(name);
+ addControl(new InstancedNodeControl(this));
+ }
+
+ private void renderFromControl() {
+ for (InstancedGeometry ig : instancesMap.values()) {
+ ig.updateInstances();
+ }
+ }
+
+ private static boolean isInstancedGeometry(Geometry geom) {
+ return geom instanceof InstancedGeometry;
+ }
+
+ private InstancedGeometry lookUpByGeometry(Geometry geom) {
+ lookUp.mesh = geom.getMesh();
+ lookUp.material = geom.getMaterial();
+ lookUp.lodLevel = geom.getLodLevel();
+
+ InstancedGeometry ig = instancesMap.get(lookUp);
+
+ if (ig == null) {
+ ig = new InstancedGeometry(
+ "material-" + lookUp.material.getMaterialDef().getName() + ","
+ + "lod-" + lookUp.lodLevel);
+ ig.setMaterial(lookUp.material);
+ ig.setMesh(lookUp.mesh);
+ ig.setUserData(UserData.JME_PHYSICSIGNORE, true);
+ ig.setCullHint(CullHint.Never);
+ instancesMap.put(lookUp.clone(), ig);
+ attachChild(ig);
+ }
+
+ return ig;
+ }
+
+ private void removeFromInstancedGeometry(Geometry geom) {
+ InstancedGeometry ig = igByGeom.remove(geom);
+ if (ig != null) {
+ ig.deleteInstance(geom);
+ }
+ }
+
+ private void ungroupSceneGraph(Spatial s) {
+ if (s instanceof Node) {
+ for (Spatial sp : ((Node) s).getChildren()) {
+ ungroupSceneGraph(sp);
+ }
+ } else if (s instanceof Geometry) {
+ Geometry g = (Geometry) s;
+ if (g.isGrouped()) {
+ // Will invoke onGeometryUnassociated automatically.
+ g.unassociateFromGroupNode();
+ if (InstancedNode.getGeometryStartIndex(g) != -1) {
+ throw new AssertionError();
+ }
+ }
+ }
+ }
+
+ @Override
+ public Spatial detachChildAt(int index) {
+ Spatial s = super.detachChildAt(index);
+ if (s instanceof Node) {
+ ungroupSceneGraph(s);
+ }
+ return s;
+ }
+
+ private void instance(Spatial n) {
+ if (n instanceof Geometry) {
+ Geometry g = (Geometry) n;
+ if (!g.isGrouped() && g.getBatchHint() != BatchHint.Never) {
+ InstancedGeometry ig = lookUpByGeometry(g);
+ igByGeom.put(g, ig);
+ g.associateWithGroupNode(this, 0);
+ ig.addInstance(g);
+ }
+ } else if (n instanceof Node) {
+ for (Spatial child : ((Node) n).getChildren()) {
+ if (child instanceof GeometryGroupNode) {
+ continue;
+ }
+ instance(child);
+ }
+ }
+ }
+
+ public void instance() {
+ instance(this);
+ }
+
+ @Override
+ public Node clone(boolean cloneMaterials) {
+ InstancedNode clone = (InstancedNode)super.clone(cloneMaterials);
+ if (instancesMap.size() > 0) {
+ // Remove all instanced geometries from the clone
+ for (int i = 0; i < clone.children.size(); i++) {
+ if (clone.children.get(i) instanceof InstancedGeometry) {
+ clone.children.remove(i);
+ }
+ }
+
+ // Clear state (which is incorrect)
+ clone.igByGeom.clear();
+ clone.instancesMap.clear();
+ clone.instance();
+ }
+ return clone;
+ }
+
+ private void majorChange(Geometry geom) {
+ InstancedGeometry oldIG = igByGeom.get(geom);
+ InstancedGeometry newIG = lookUpByGeometry(geom);
+ if (oldIG != newIG) {
+ oldIG.deleteInstance(geom);
+ newIG.addInstance(geom);
+ igByGeom.put(geom, newIG);
+ }
+ }
+
+ @Override
+ public void onTransformChange(Geometry geom) {
+ // Handled automatically
+ }
+
+ @Override
+ public void onMaterialChange(Geometry geom) {
+ majorChange(geom);
+ }
+
+ @Override
+ public void onMeshChange(Geometry geom) {
+ majorChange(geom);
+ }
+
+ @Override
+ public void onGeoemtryUnassociated(Geometry geom) {
+ removeFromInstancedGeometry(geom);
+ }
+}
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
index a2e2172c1..63b7470b2 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
@@ -10,6 +10,8 @@ MaterialDef Debug Normals {
WorldParameters {
WorldViewProjectionMatrix
+ ViewProjectionMatrix
+ ViewMatrix
ProjectionMatrix
}
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
index 5a57426d4..40d64cb7e 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
@@ -59,7 +59,8 @@ MaterialDef Unshaded {
WorldParameters {
WorldViewProjectionMatrix
- ProjectionMatrix
+ ViewProjectionMatrix
+ ViewMatrix
}
Defines {
diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
index 4bf3fc1e9..ae34935c5 100644
--- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
+++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
@@ -23,6 +23,7 @@ uniform mat4 g_ViewMatrix;
uniform mat4 g_ProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat4 g_WorldViewProjectionMatrix;
+uniform mat4 g_ViewProjectionMatrix;
uniform mat3 g_NormalMatrix;
#if defined INSTANCING
@@ -37,29 +38,36 @@ uniform mat3 g_NormalMatrix;
// 2 vertex attributes which now can be used for additional per-vertex data.
attribute mat4 inInstanceData;
-// Extract the world view matrix out of the instance data, leaving out the
+// Extract the world matrix out of the instance data, leaving out the
// quaternion at the end.
-mat4 worldViewMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0),
+mat4 worldMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0),
vec4(inInstanceData[1].xyz, 0.0),
vec4(inInstanceData[2].xyz, 0.0),
vec4(inInstanceData[3].xyz, 1.0));
+vec4 TransformWorld(vec4 position)
+{
+ return (worldMatrix * position);
+}
vec4 TransformWorldView(vec4 position)
{
- return worldViewMatrix * position;
+ return g_ViewMatrix * TransformWorld(position);
}
vec4 TransformWorldViewProjection(vec4 position)
{
- return g_ProjectionMatrix * TransformWorldView(position);
+ return g_ViewProjectionMatrix * TransformWorld(position);
}
vec3 TransformNormal(vec3 vec)
{
vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
inInstanceData[2].w, inInstanceData[3].w);
- return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
+
+ vec3 worldNormal = vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
+
+ return (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz;
}
// Prevent user from using g_** matrices which will have invalid data in this case.
diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java
new file mode 100644
index 000000000..c99406122
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2009-2012 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.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.instancing.InstancedGeometry;
+import com.jme3.scene.instancing.InstancedNode;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.system.AppSettings;
+
+public class TestInstanceNode extends SimpleApplication {
+
+ private Mesh mesh1;
+ private Mesh mesh2;
+ private final Material[] materials = new Material[6];
+ private InstancedNode instancedNode;
+ private float time = 0;
+
+ public static void main(String[] args){
+ TestInstanceNode app = new TestInstanceNode();
+ AppSettings settings = new AppSettings(true);
+ settings.setVSync(false);
+ app.setSettings(settings);
+ app.start();
+ }
+
+ private Geometry createInstance(float x, float z) {
+ Mesh mesh;
+ if (FastMath.nextRandomInt(0, 1) == 1) mesh = mesh2;
+ else mesh = mesh1;
+ Geometry geometry = new Geometry("randomGeom", mesh);
+ geometry.setMaterial(materials[FastMath.nextRandomInt(0, materials.length - 1)]);
+ geometry.setLocalTranslation(x, 0, z);
+ return geometry;
+ }
+
+ @Override
+ public void simpleInitApp() {
+ mesh1 = new Sphere(13, 13, 0.4f, true, false);
+ mesh2 = new Box(0.4f, 0.4f, 0.4f);
+
+ materials[0] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ materials[0].setBoolean("UseInstancing", true);
+ materials[0].setColor("Color", ColorRGBA.Red);
+
+ materials[1] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ materials[1].setBoolean("UseInstancing", true);
+ materials[1].setColor("Color", ColorRGBA.Green);
+
+ materials[2] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ materials[2].setBoolean("UseInstancing", true);
+ materials[2].setColor("Color", ColorRGBA.Blue);
+
+ materials[3] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ materials[3].setBoolean("UseInstancing", true);
+ materials[3].setColor("Color", ColorRGBA.Cyan);
+
+ materials[4] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ materials[4].setBoolean("UseInstancing", true);
+ materials[4].setColor("Color", ColorRGBA.Magenta);
+
+ materials[5] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ materials[5].setBoolean("UseInstancing", true);
+ materials[5].setColor("Color", ColorRGBA.Yellow);
+
+ instancedNode = new InstancedNode("instanced_node");
+ instancedNode.setCullHint(CullHint.Never);
+
+ rootNode.attachChild(instancedNode);
+
+ int extent = 30;
+
+ for (int y = -extent; y < extent; y++) {
+ for (int x = -extent; x < extent; x++) {
+ Geometry instance = createInstance(x, y);
+
+ float height = (smoothstep(0, 1, FastMath.nextRandomFloat()) * 2.5f) - 1.25f;
+ instance.setUserData("height", height);
+ instance.setUserData("dir", 1f);
+
+ instancedNode.attachChild(instance);
+ }
+ }
+
+ instancedNode.instance();
+
+ cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f));
+ cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f));
+ flyCam.setMoveSpeed(15);
+ //flyCam.setEnabled(false);
+ }
+
+ private float smoothstep(float edge0, float edge1, float x) {
+ // Scale, bias and saturate x to 0..1 range
+ x = FastMath.clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
+ // Evaluate polynomial
+ return x * x * (3 - 2 * x);
+ }
+
+
+ @Override
+ public void simpleUpdate(float tpf) {
+ time += tpf;
+
+ if (time > 1f) {
+ time = 0f;
+
+ for (Spatial instance : instancedNode.getChildren()) {
+ if (!(instance instanceof InstancedGeometry)) {
+ Geometry geom = (Geometry) instance;
+ geom.setMaterial(materials[FastMath.nextRandomInt(0, materials.length - 1)]);
+
+ Mesh mesh;
+ if (FastMath.nextRandomInt(0, 1) == 1) mesh = mesh2;
+ else mesh = mesh1;
+ geom.setMesh(mesh);
+ }
+ }
+ }
+
+ for (Spatial child : instancedNode.getChildren()) {
+ if (!(child instanceof InstancedGeometry)) {
+ float val = child.getUserData("height");
+ float dir = child.getUserData("dir");
+
+ val += (dir + ((FastMath.nextRandomFloat() * 0.5f) - 0.25f)) * tpf;
+
+ if (val > 1f) {
+ val = 1f;
+ dir = -dir;
+ } else if (val < 0f) {
+ val = 0f;
+ dir = -dir;
+ }
+
+ Vector3f translation = child.getLocalTranslation();
+ translation.y = (smoothstep(0, 1, val) * 2.5f) - 1.25f;
+
+ child.setUserData("height", val);
+ child.setUserData("dir", dir);
+
+ child.setLocalTranslation(translation);
+ }
+ }
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java
deleted file mode 100644
index 2496b4cb2..000000000
--- a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (c) 2009-2012 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.input.KeyInput;
-import com.jme3.input.controls.ActionListener;
-import com.jme3.input.controls.AnalogListener;
-import com.jme3.input.controls.KeyTrigger;
-import com.jme3.material.Material;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Vector3f;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
-import com.jme3.scene.Spatial.CullHint;
-import com.jme3.scene.instancing.InstancedGeometry;
-import com.jme3.scene.shape.Sphere;
-
-public class TestInstancing extends SimpleApplication {
-
- private InstancedGeometry instancedGeometry;
- private Node instancedGeoms;
- private Material material;
- private boolean enabled = true;
-
- public static void main(String[] args){
- TestInstancing app = new TestInstancing();
- //app.setShowSettings(false);
- //app.setDisplayFps(false);
- //app.setDisplayStatView(false);
- app.start();
- }
-
- private Geometry createInstance(float x, float z) {
- // Note: it doesn't matter what mesh or material we set here.
- Geometry geometry = new Geometry("randomGeom", instancedGeometry.getMesh());
- geometry.setMaterial(instancedGeometry.getMaterial());
- geometry.setLocalTranslation(x, 0, z);
- return geometry;
- }
-
- @Override
- public void simpleInitApp() {
- initInputs();
-
- Sphere sphere = new Sphere(10, 10, 0.5f, true, false);
- material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
- material.setBoolean("UseInstancing", true);
-
- instancedGeometry = new InstancedGeometry(InstancedGeometry.Mode.Auto, "instanced_geom");
- instancedGeometry.setMaxNumInstances(60 * 60);
- instancedGeometry.setCurrentNumInstances(60 * 60);
- instancedGeometry.setCullHint(CullHint.Never);
- instancedGeometry.setMesh(sphere);
- instancedGeometry.setMaterial(material);
- rootNode.attachChild(instancedGeometry);
-
- instancedGeoms = new Node("instances_node");
-
- // Important: Do not render these geometries, only
- // use their world transforms to instance them via
- // InstancedGeometry.
- instancedGeoms.setCullHint(CullHint.Always);
-
- for (int y = -30; y < 30; y++) {
- for (int x = -30; x < 30; x++) {
- Geometry instance = createInstance(x, y);
- instancedGeoms.attachChild(instance);
- }
- }
-
- rootNode.attachChild(instancedGeoms);
- rootNode.setCullHint(CullHint.Never);
-
- int instanceIndex = 0;
- for (Spatial child : instancedGeoms.getChildren()) {
- if (instanceIndex < instancedGeometry.getMaxNumInstances()) {
- instancedGeometry.setInstanceTransform(instanceIndex++, child.getWorldTransform());
- }
- }
-
- instancedGeometry.setCurrentNumInstances(instanceIndex);
-
- cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f));
- cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f));
- flyCam.setMoveSpeed(15);
- }
-
- private void initInputs() {
- inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
-
- ActionListener acl = new ActionListener() {
-
- public void onAction(String name, boolean keyPressed, float tpf) {
- if (name.equals("toggle") && keyPressed) {
- if (enabled) {
- enabled = false;
- instancedGeoms.setCullHint(CullHint.Dynamic);
- instancedGeometry.setCullHint(CullHint.Always);
- material.setBoolean("UseInstancing", false);
- System.out.println("Instancing OFF");
- } else {
- enabled = true;
- instancedGeoms.setCullHint(CullHint.Always);
- instancedGeometry.setCullHint(CullHint.Never);
- material.setBoolean("UseInstancing", true);
- System.out.println("Instancing ON");
- }
- }
- }
- };
-
- inputManager.addListener(acl, "toggle");
- }
-}